Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* WeightChanger contract * add null controller for factory sample * Update .eslintrc.js * formatting * rm prettier from lint * lint * update yarn.lock * lint * WeightChanger cleanup * Unit tests * not-rely-on-time solhint error turned off * reorganized files * Removed extra interfaces folder * edits to weightchanger and factory * Updated unit tests * README added * lint fix * Fix workflows * Revert "Fix workflows" This reverts commit 82c0d02. * Adding compostability library * move to _ensureEnabled format. this change will be added to NullControllerFactory in an upcoming PR * Partial fix of PR comments * slight code cleanup * tests with weight checks at each interval * Factory Access Control tests added * Removed packageManager section from package.josn * prettier settings added back * testWeightChange function added to reduce duplicate code * prettier json 4 space tabs * prettier format fix * indent fix * tab added * formatting fix * remove unnecessary return values from _updateWeights() * Factory update * Updated README * changed isDisabled() to public * use state variable _disabled for check * Natspec comments added * Natspec param fix * Changed _verify functions to _check * remove return uint256 --------- Co-authored-by: gerg <gerrrrrrrg@gmail.com>
- Loading branch information
1 parent
b92b903
commit 6c14b23
Showing
5 changed files
with
583 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# WeightChangerController | ||
|
||
## Summary | ||
WeightChangerController is a Managed Pool Controller that has the ability gradually update token weights to a set of predefined weights. | ||
|
||
## Details | ||
The WeightChangerController allows users to update the weights of a Managed Pool's tokens to one of the predefined weights sets over the course of 7 days. | ||
|
||
## Access Control | ||
### WeightChangerController | ||
The controller has no access control; all public functions can be executed by any account. | ||
|
||
### WeightChangerControllerFactory | ||
The factory has one permissioned function: `disable()`. Using OZ's Ownable, the factory restricts permission to only the contract `owner`. Ownable was chosen as it is a very simple concept that requires little explanation; however, it may be desirable to grant this permission to more than a single `owner`. Using a solution such as Balancer's [SingletonAuthentication](https://github.com/balancer/balancer-v2-monorepo/blob/3e99500640449585e8da20d50687376bcf70462f/pkg/solidity-utils/contracts/helpers/SingletonAuthentication.sol) could be a useful system for many controller factories. | ||
|
||
## Managed Pool Functions | ||
The following list is a list of permissioned functions in a Managed Pool that a controller could potentially call. The WeightChangerController can call the functions below that are denoted with a checked box: | ||
|
||
- Gradual Updates | ||
- [ ] `pool.updateSwapFeeGradually(...)` | ||
- [x] `pool.updateWeightsGradually(...)` | ||
- Enable/Disable Interactions | ||
- [ ] `pool.setSwapEnabled(...)` | ||
- [ ] `pool.setJoinExitEnabled(...)` | ||
- LP Allowlist Management | ||
- [ ] `pool.setMustAllowlistLPs(...)` | ||
- [ ] `pool.addAllowedAddress(...)` | ||
- [ ] `pool.removeAllowedAddress(...)` | ||
- Add/Remove Token | ||
- [ ] `pool.addToken(...)` | ||
- [ ] `pool.removeToken(...)` | ||
- Circuit Breaker Management | ||
- [ ] `pool.setCircuitBreakers(...)` | ||
- Management Fee | ||
- [ ] `pool.setManagementAumFeePercentage(...)` |
161 changes: 161 additions & 0 deletions
161
pkg/mpc-examples/contracts/01-weight-changer/WeightChangerController.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.7.0; | ||
|
||
// This contract shows an example of how a managed pool controller can modify a pools weight | ||
// Gradual weight updates over 7 days for each change | ||
import "@balancer-labs/v2-interfaces/contracts/pool-utils/ILastCreatedPoolFactory.sol"; | ||
import "@balancer-labs/v2-interfaces/contracts/pool-utils/IManagedPool.sol"; | ||
import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; | ||
import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol"; | ||
import "@balancer-labs/v2-pool-utils/contracts/lib/ComposablePoolLib.sol"; | ||
import "../interfaces/IManagedPoolFactory.sol"; | ||
|
||
// solhint-disable not-rely-on-time | ||
|
||
contract WeightChangerController { | ||
using FixedPoint for uint256; | ||
IERC20[] private _tokens; | ||
|
||
uint256 private constant _REWEIGHT_DURATION = 7 days; | ||
|
||
// Minimum and maximum weight limits | ||
uint256 private constant _MIN_WEIGHT = 1e16; // 1% | ||
uint256 private constant _MAX_WEIGHT = 99e16; // 99% | ||
|
||
IVault private immutable _vault; | ||
bytes32 private immutable _poolId; | ||
|
||
constructor(IVault vault) { | ||
// Get poolId from the factory | ||
bytes32 poolId = IManagedPool(ILastCreatedPoolFactory(msg.sender).getLastCreatedPool()).getPoolId(); | ||
|
||
// Set the global tokens variables | ||
(IERC20[] memory tokens, , ) = vault.getPoolTokens(poolId); | ||
_setTokens(tokens); | ||
|
||
_vault = vault; | ||
_poolId = poolId; | ||
} | ||
|
||
/** | ||
* @dev Starts the gradual reweight process to bring the token's weights to 50/50. | ||
* @dev Gradual reweight will start when this function is called and take _REWEIGHT_DURATION to complete. | ||
*/ | ||
function make5050() public { | ||
uint256[] memory newWeights = new uint256[](2); | ||
newWeights[0] = 50e16; | ||
newWeights[1] = 50e16; | ||
_updateWeights(block.timestamp, block.timestamp + _REWEIGHT_DURATION, getTokens(), newWeights); | ||
} | ||
|
||
/** | ||
* @dev Starts the gradual reweight process to bring the token's weights to 80/20. | ||
* @dev Gradual reweight will start when this function is called and take _REWEIGHT_DURATION to complete. | ||
*/ | ||
function make8020() public { | ||
uint256[] memory newWeights = new uint256[](2); | ||
newWeights[0] = 80e16; | ||
newWeights[1] = 20e16; | ||
_updateWeights(block.timestamp, block.timestamp + _REWEIGHT_DURATION, getTokens(), newWeights); | ||
} | ||
|
||
/** | ||
* @dev Starts the gradual reweight process to bring the token's weights to 99/01. | ||
* @dev Gradual reweight will start when this function is called and take _REWEIGHT_DURATION to complete. | ||
*/ | ||
function make9901() public { | ||
uint256[] memory newWeights = new uint256[](2); | ||
newWeights[0] = 99e16; | ||
newWeights[1] = 1e16; | ||
_updateWeights(block.timestamp, block.timestamp + _REWEIGHT_DURATION, getTokens(), newWeights); | ||
} | ||
|
||
// === Public Getters === | ||
function getMaximumWeight() public pure returns (uint256) { | ||
return _MAX_WEIGHT; | ||
} | ||
|
||
function getMinimumWeight() public pure returns (uint256) { | ||
return _MIN_WEIGHT; | ||
} | ||
|
||
function getCurrentWeights() public view returns (uint256[] memory) { | ||
return _getPool().getNormalizedWeights(); | ||
} | ||
|
||
function getReweightDuration() public pure returns (uint256) { | ||
return _REWEIGHT_DURATION; | ||
} | ||
|
||
function getTokens() public view returns (IERC20[] memory) { | ||
return _tokens; | ||
} | ||
|
||
function getPoolId() public view returns (bytes32) { | ||
return _poolId; | ||
} | ||
|
||
function getVault() public view returns (IVault) { | ||
return _vault; | ||
} | ||
|
||
/// === Private and Internal === | ||
function _checkWeight(uint256 normalizedWeight) internal pure { | ||
require(normalizedWeight >= _MIN_WEIGHT, "Weight under minimum"); | ||
require(normalizedWeight <= _MAX_WEIGHT, "Weight over maximum"); | ||
} | ||
|
||
function _checkWeights(uint256[] memory normalizedWeights) internal pure { | ||
uint256 normalizedSum = 0; | ||
for (uint256 i = 0; i < normalizedWeights.length; i++) { | ||
_checkWeight(normalizedWeights[i]); | ||
normalizedSum = normalizedSum.add(normalizedWeights[i]); | ||
} | ||
|
||
require(normalizedSum == FixedPoint.ONE, "Weights must sum to one"); | ||
} | ||
|
||
function _getPoolFromId(bytes32 poolId) internal pure returns (IManagedPool) { | ||
// 12 byte logical shift left to remove the nonce and specialization setting. We don't need to mask, | ||
// since the logical shift already sets the upper bits to zero. | ||
return IManagedPool(address(uint256(poolId) >> (12 * 8))); | ||
} | ||
|
||
function _getPool() internal view returns (IManagedPool) { | ||
return _getPoolFromId(getPoolId()); | ||
} | ||
|
||
function _setTokens(IERC20[] memory tokens) internal { | ||
_tokens = ComposablePoolLib.dropBptFromTokens(tokens); | ||
} | ||
|
||
/** | ||
* @dev Updates the weights of the managed pool. | ||
* @param startTime The timestamp, in seconds, at when the gradual weight update process starts. | ||
* @param endTime The timestamp, in seconds, at when the gradual weight update process is complete. | ||
* @param tokens An array of tokens, IERC20, that make up the managed pool. | ||
* @param weights The desired end weights of the pool tokens. Must correspond with the tokens parameter. | ||
*/ | ||
function _updateWeights( | ||
uint256 startTime, | ||
uint256 endTime, | ||
IERC20[] memory tokens, | ||
uint256[] memory weights | ||
) internal { | ||
_checkWeights(weights); | ||
_getPool().updateWeightsGradually(startTime, endTime, tokens, weights); | ||
} | ||
} |
153 changes: 153 additions & 0 deletions
153
pkg/mpc-examples/contracts/01-weight-changer/WeightChangerControllerFactory.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.7.0; | ||
pragma experimental ABIEncoderV2; | ||
|
||
import "@balancer-labs/v2-interfaces/contracts/pool-utils/IManagedPool.sol"; | ||
import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; | ||
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/Create2.sol"; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
import "./WeightChangerController.sol"; | ||
import "../interfaces/IManagedPoolFactory.sol"; | ||
|
||
/** | ||
* @title WeightChangerControllerFactory | ||
* @notice Factory for a Managed Pool and Weight Changer Controller. | ||
* @dev Determines controller deployment address, deploys pool (w/ controller address as argument), then controller. | ||
*/ | ||
contract WeightChangerControllerFactory is Ownable { | ||
mapping(address => bool) public isControllerFromFactory; | ||
|
||
address public managedPoolFactory; | ||
IVault public balancerVault; | ||
bool private _disabled; | ||
|
||
uint256 private _nextControllerSalt; | ||
address private _lastCreatedPool; | ||
|
||
// This struct is a subset of IManagedPoolFactory.NewPoolParams which omits arguments | ||
// that this factory will override and are therefore unnecessary to provide. It will | ||
// ultimately be used to populate IManagedPoolFactory.NewPoolParams. | ||
struct MinimalPoolParams { | ||
string name; | ||
string symbol; | ||
IERC20[] tokens; | ||
uint256[] normalizedWeights; | ||
uint256 swapFeePercentage; | ||
bool swapEnabledOnStart; | ||
uint256 managementAumFeePercentage; | ||
uint256 aumFeeId; | ||
} | ||
|
||
event ControllerCreated(address indexed controller, IVault vault, bytes32 poolId); | ||
event Disabled(); | ||
|
||
constructor(IVault vault, address factory) { | ||
balancerVault = vault; | ||
managedPoolFactory = factory; | ||
} | ||
|
||
/** | ||
* @dev Return the address of the most recently created pool. | ||
*/ | ||
function getLastCreatedPool() external view returns (address) { | ||
return _lastCreatedPool; | ||
} | ||
|
||
function create(MinimalPoolParams calldata minimalParams) external { | ||
_ensureEnabled(); | ||
|
||
bytes32 controllerSalt = bytes32(_nextControllerSalt); | ||
_nextControllerSalt += 1; | ||
|
||
bytes memory controllerCreationCode = abi.encodePacked( | ||
type(WeightChangerController).creationCode, | ||
abi.encode(balancerVault) | ||
); | ||
address expectedControllerAddress = Create2.computeAddress(controllerSalt, keccak256(controllerCreationCode)); | ||
|
||
// Build arguments to deploy pool from factory. | ||
address[] memory assetManagers = new address[](minimalParams.tokens.length); | ||
for (uint256 i = 0; i < assetManagers.length; i++) { | ||
assetManagers[i] = expectedControllerAddress; | ||
} | ||
|
||
// Populate IManagedPoolFactory.NewPoolParams with arguments from MinimalPoolParams and | ||
// other arguments that this factory provides itself. | ||
IManagedPoolFactory.NewPoolParams memory fullParams; | ||
fullParams.name = minimalParams.name; | ||
fullParams.symbol = minimalParams.symbol; | ||
fullParams.tokens = minimalParams.tokens; | ||
fullParams.normalizedWeights = minimalParams.normalizedWeights; | ||
// Asset Managers set to the controller address, not known by deployer until creation. | ||
fullParams.assetManagers = assetManagers; | ||
fullParams.swapFeePercentage = minimalParams.swapFeePercentage; | ||
fullParams.swapEnabledOnStart = minimalParams.swapEnabledOnStart; | ||
// Factory enforces public LPs for MPs with WeightChanger. | ||
fullParams.mustAllowlistLPs = false; | ||
fullParams.managementAumFeePercentage = minimalParams.managementAumFeePercentage; | ||
fullParams.aumFeeId = minimalParams.aumFeeId; | ||
|
||
IManagedPool pool = IManagedPool( | ||
IManagedPoolFactory(managedPoolFactory).create(fullParams, expectedControllerAddress) | ||
); | ||
_lastCreatedPool = address(pool); | ||
|
||
address actualControllerAddress = Create2.deploy(0, controllerSalt, controllerCreationCode); | ||
require(expectedControllerAddress == actualControllerAddress, "Deploy failed"); | ||
|
||
// Log controller locally. | ||
isControllerFromFactory[actualControllerAddress] = true; | ||
|
||
// Log controller publicly. | ||
emit ControllerCreated(actualControllerAddress, balancerVault, pool.getPoolId()); | ||
} | ||
|
||
/** | ||
* @dev Allow the owner to disable the factory, preventing future deployments. | ||
* @notice owner is initially the factory deployer, but this role can be transferred. | ||
* @dev The onlyOwner access control paradigm is an example. Any access control can | ||
* be implemented to allow for different needs. | ||
*/ | ||
function disable() external onlyOwner { | ||
_ensureEnabled(); | ||
_disabled = true; | ||
emit Disabled(); | ||
} | ||
|
||
/** | ||
* @dev Query whether this controller factory is disabled. | ||
*/ | ||
function isDisabled() external view returns (bool) { | ||
return _disabled || _isPoolFactoryDisabled(); | ||
} | ||
|
||
/** | ||
* @dev Query whether the pool factory is disabled. | ||
*/ | ||
function _isPoolFactoryDisabled() internal view returns (bool) { | ||
return IManagedPoolFactory(managedPoolFactory).isDisabled(); | ||
} | ||
|
||
/** | ||
* @dev Revert if the factory is disabled. | ||
*/ | ||
function _ensureEnabled() internal view { | ||
require(!_disabled, "Controller factory disabled"); | ||
require(!IManagedPoolFactory(managedPoolFactory).isDisabled(), "Pool factory disabled"); | ||
} | ||
} |
Oops, something went wrong.