diff --git a/contracts/Strategy.sol b/contracts/Strategy.sol index 3465d522..c3937992 100644 --- a/contracts/Strategy.sol +++ b/contracts/Strategy.sol @@ -170,20 +170,25 @@ contract Strategy is IStrategy, IStrategyManagement, StrategyTokenFees, Initiali _tempRouter = router; } - function updateTimelock(bytes4 functionSelector, uint256 delay) external override { + function updateTimelock(bytes4 selector, uint256 delay) external { _onlyManager(); - _startTimelock(this.updateTimelock.selector, abi.encode(functionSelector, delay)); + _startTimelock( + keccak256(abi.encode(this.updateTimelock.selector)), // identifier + abi.encode(keccak256(abi.encode(selector)), delay)); // payload emit UpdateTimelock(delay, false); } - function finalizeTimelock() external override { - if (!_timelockIsReady(this.updateTimelock.selector)) { - TimelockData memory td = _timelockData(this.updateTimelock.selector); + function finalizeTimelock() external { + bytes32 key = keccak256(abi.encode(this.updateTimelock.selector)); + if (!_timelockIsReady(key)) { + TimelockData memory td = _timelockData(key); _require(td.delay == 0, uint256(0xb3e5dea2190e00) /* error_macro_for("finalizeTimelock: timelock is not ready.") */); } - (bytes4 selector, uint256 delay) = abi.decode(_getTimelockValue(this.updateTimelock.selector), (bytes4, uint256)); - _setTimelock(selector, delay); - _resetTimelock(this.updateTimelock.selector); + bytes memory value = _getTimelockValue(key); + require(value.length != 0, "timelock never started."); + (bytes32 identifier, uint256 delay) = abi.decode(value, (bytes32, uint256)); + _setTimelock(identifier, delay); + _resetTimelock(key); emit UpdateTimelock(delay, true); } @@ -358,15 +363,18 @@ contract Strategy is IStrategy, IStrategyManagement, StrategyTokenFees, Initiali */ function updateTradeData(address item, TradeData memory data) external override { _onlyManager(); - _startTimelock(this.updateTradeData.selector, abi.encode(item, data)); + _startTimelock( + keccak256(abi.encode(this.updateTradeData.selector)), // identifier + abi.encode(item, data)); // payload emit UpdateTradeData(item, false); } function finalizeUpdateTradeData() external { - _require(_timelockIsReady(this.updateTradeData.selector), uint256(0xb3e5dea2190e06) /* error_macro_for("finalizeUpdateTradeData: timelock not ready.") */); - (address item, TradeData memory data) = abi.decode(_getTimelockValue(this.updateTradeData.selector), (address, TradeData)); + bytes32 key = keccak256(abi.encode(this.updateTradeData.selector)); + _require(_timelockIsReady(key), uint256(0xb3e5dea2190e06) /* error_macro_for("finalizeUpdateTradeData: timelock not ready.") */); + (address item, TradeData memory data) = abi.decode(_getTimelockValue(key), (address, TradeData)); _tradeData[item] = data; - _resetTimelock(this.updateTradeData.selector); + _resetTimelock(key); emit UpdateTradeData(item, true); } @@ -564,7 +572,7 @@ contract Strategy is IStrategy, IStrategyManagement, StrategyTokenFees, Initiali return exists.doesExist(bytes32(uint256(token))); } - function _timelockData(bytes4 functionSelector) internal override returns(TimelockData storage) { - return __timelockData[functionSelector]; + function _timelockData(bytes32 identifier) internal override returns(TimelockData storage) { + return __timelockData[identifier]; } } diff --git a/contracts/StrategyController.sol b/contracts/StrategyController.sol index 9ccbd3b4..0da2f36c 100644 --- a/contracts/StrategyController.sol +++ b/contracts/StrategyController.sol @@ -10,6 +10,7 @@ import "./libraries/SafeERC20.sol"; import "./libraries/ControllerLibrary.sol"; import "./interfaces/IStrategyController.sol"; import "./interfaces/IStrategyProxyFactory.sol"; +import "./helpers/Timelocks.sol"; import "./helpers/Require.sol"; import "./StrategyControllerStorage.sol"; @@ -18,7 +19,7 @@ import "./StrategyControllerStorage.sol"; * @dev Whitelisted routers are able to execute different swapping strategies as long as total strategy value doesn't drop below the defined slippage amount * @dev To avoid someone from repeatedly skimming off this slippage value, rebalance threshold should be set sufficiently high */ -contract StrategyController is IStrategyController, StrategyControllerStorage, Initializable, Require { +contract StrategyController is IStrategyController, StrategyControllerStorage, Initializable, Timelocks, Require { using SafeMath for uint256; using SignedSafeMath for int256; using SafeERC20 for IERC20; @@ -36,6 +37,7 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I event NewValue(address indexed strategy, TimelockCategory category, uint256 newValue, bool indexed finalized); event StrategyOpen(address indexed strategy); event StrategySet(address indexed strategy); + event RebalanceParametersUpdated(uint256 indexed rebalanceTimelockPeriod, uint256 indexed rebalanceThreshold, bool indexed finalized); // hack! these events are called in the `ControllerLibrary` // but cannot be tracked unless they are defined here! @@ -52,6 +54,11 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I * @dev Called to initialize proxy */ function initialize() external initializer { + _rebalanceTimelockPeriod = 5 minutes; + _rebalanceThresholdScalar = 2000; + bytes32 key = keccak256(abi.encode(this.updateRebalanceParameters.selector)); + _setTimelock(key, _rebalanceTimelockPeriod); + updateAddresses(); _require(address(this)== ControllerLibrary.self(), uint256(0x1bb63a90056c00) /* error_macro_for("Sanity check that Library shares context.") */); } @@ -174,7 +181,13 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I _isInitialized(address(strategy)); _setStrategyLock(strategy); _onlyManager(strategy); - ControllerLibrary.rebalance(strategy, router, oracle(), _weth, _strategyStates[address(strategy)].rebalanceSlippage, data); + + bytes32 key = keccak256(abi.encode(this.rebalance.selector, strategy)); + _require(_timelockIsReady(key), uint256(0x1bb63a90056c04) /* error_macro_for("rebalance timelock not ready.") */); + _setTimelock(key, _rebalanceTimelockPeriod); // in case factory updateRebalanceParameters + _startTimelock(key, new bytes(0)); + + ControllerLibrary.rebalance(strategy, router, oracle(), _weth, _strategyStates[address(strategy)].rebalanceSlippage, _rebalanceThresholdScalar, data); _removeStrategyLock(strategy); } @@ -200,7 +213,7 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I _onlyManager(strategy); ITokenRegistry.ItemDetails memory itemDetails = oracle().tokenRegistry().itemDetails(address(-1)); address adapter = itemDetails.tradeData.adapters[0]; - _require(adapter != address(0), uint256(0x1bb63a90056c04) /* error_macro_for("Invalid adapter") */); + _require(adapter != address(0), uint256(0x1bb63a90056c05) /* error_macro_for("Invalid adapter") */); ControllerLibrary.repositionSynths(strategy, adapter, token, _susd); _removeStrategyLock(strategy); } @@ -223,9 +236,9 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I lock.timestamp == 0 || block.timestamp > lock.timestamp.add(uint256(_strategyStates[address(strategy)].timelock)), - uint256(0x1bb63a90056c05) /* error_macro_for("Timelock active") */ + uint256(0x1bb63a90056c06) /* error_macro_for("Timelock active") */ ); - _require(ControllerLibrary.verifyStructure(address(strategy), strategyItems), uint256(0x1bb63a90056c06) /* error_macro_for("Invalid structure") */); + _require(ControllerLibrary.verifyStructure(address(strategy), strategyItems), uint256(0x1bb63a90056c07) /* error_macro_for("Invalid structure") */); lock.category = TimelockCategory.RESTRUCTURE; lock.timestamp = block.timestamp; lock.data = abi.encode(strategyItems); @@ -253,16 +266,16 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I strategy.settleSynths(); StrategyState memory strategyState = _strategyStates[address(strategy)]; Timelock storage lock = _timelocks[address(strategy)]; - _require(lock.timestamp > 0, uint256(0x1bb63a90056c07) /* error_macro_for("No changes queued") */); + _require(lock.timestamp > 0, uint256(0x1bb63a90056c08) /* error_macro_for("No changes queued") */); _require( !strategyState.social || block.timestamp >= lock.timestamp.add(uint256(strategyState.timelock)), - uint256(0x1bb63a90056c08) /* error_macro_for("Timelock active") */ + uint256(0x1bb63a90056c09) /* error_macro_for("Timelock active") */ ); - _require(lock.category == TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c09) /* error_macro_for("Wrong category") */); + _require(lock.category == TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c0a) /* error_macro_for("Wrong category") */); (StrategyItem[] memory strategyItems) = abi.decode(lock.data, (StrategyItem[])); - _require(ControllerLibrary.verifyStructure(address(strategy), strategyItems), uint256(0x1bb63a90056c0a) /* error_macro_for("Invalid structure") */); + _require(ControllerLibrary.verifyStructure(address(strategy), strategyItems), uint256(0x1bb63a90056c0b) /* error_macro_for("Invalid structure") */); _finalizeStructure(strategy, router, strategyItems, data); delete lock.category; delete lock.timestamp; @@ -289,9 +302,9 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I lock.timestamp == 0 || block.timestamp > lock.timestamp.add(uint256(_strategyStates[address(strategy)].timelock)), - uint256(0x1bb63a90056c0b) /* error_macro_for("Timelock active") */ + uint256(0x1bb63a90056c0c) /* error_macro_for("Timelock active") */ ); - _require(category != TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c0c) /* error_macro_for("updateValue: category is RESTRUCTURE.") */); + _require(category != TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c0d) /* error_macro_for("updateValue: category is RESTRUCTURE.") */); _checkAndEmit(address(strategy), category, newValue, false); lock.category = category; lock.timestamp = block.timestamp; @@ -308,12 +321,12 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I _setStrategyLock(strategy); StrategyState storage strategyState = _strategyStates[address(strategy)]; Timelock storage lock = _timelocks[address(strategy)]; - _require(lock.timestamp > 0, uint256(0x1bb63a90056c0d) /* error_macro_for("No changes queued") */); - _require(lock.category != TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c0e) /* error_macro_for("Wrong category") */); + _require(lock.timestamp > 0, uint256(0x1bb63a90056c0e) /* error_macro_for("No changes queued") */); + _require(lock.category != TimelockCategory.RESTRUCTURE, uint256(0x1bb63a90056c0f) /* error_macro_for("Wrong category") */); _require( !strategyState.social || block.timestamp >= lock.timestamp.add(uint256(strategyState.timelock)), - uint256(0x1bb63a90056c0f) /* error_macro_for("Timelock active") */ + uint256(0x1bb63a90056c10) /* error_macro_for("Timelock active") */ ); uint256 newValue = abi.decode(lock.data, (uint256)); if (lock.category == TimelockCategory.TIMELOCK) { @@ -345,7 +358,7 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I _setStrategyLock(strategy); _onlyManager(strategy); StrategyState storage strategyState = _strategyStates[address(strategy)]; - _require(!strategyState.social, uint256(0x1bb63a90056c10) /* error_macro_for("Strategy already open") */); + _require(!strategyState.social, uint256(0x1bb63a90056c11) /* error_macro_for("Strategy already open") */); strategyState.social = true; emit StrategyOpen(address(strategy)); _removeStrategyLock(strategy); @@ -360,7 +373,7 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I _setStrategyLock(strategy); _onlyManager(strategy); StrategyState storage strategyState = _strategyStates[address(strategy)]; - _require(!strategyState.set, uint256(0x1bb63a90056c11) /* error_macro_for("Strategy already set") */); + _require(!strategyState.set, uint256(0x1bb63a90056c12) /* error_macro_for("Strategy already set") */); strategyState.set = true; emit StrategySet(address(strategy)); _removeStrategyLock(strategy); @@ -401,6 +414,24 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I } } + function updateRebalanceParameters(uint256 rebalanceTimelockPeriod, uint256 rebalanceThresholdScalar) external override { + _require(msg.sender == factory, uint256(0x1bb63a90056c13) /* error_macro_for("Not factory") */); + _startTimelock( + keccak256(abi.encode(this.updateRebalanceParameters.selector)), // identifier + abi.encode(rebalanceTimelockPeriod, rebalanceThresholdScalar)); // payload + emit RebalanceParametersUpdated(rebalanceTimelockPeriod, rebalanceThresholdScalar, false); + } + + function finalizeRebalanceParameters() public { + bytes32 key = keccak256(abi.encode(this.updateRebalanceParameters.selector)); + _require(_timelockIsReady(key), uint256(0x1bb63a90056c14) /* error_macro_for("updateRebalanceParameters timelock not ready.") */); + (uint256 rebalanceTimelockPeriod, uint256 rebalanceThresholdScalar) = abi.decode(_getTimelockValue(key), (uint256, uint256)); + _resetTimelock(key); + _rebalanceTimelockPeriod = rebalanceTimelockPeriod; + _rebalanceThresholdScalar = rebalanceThresholdScalar; + emit RebalanceParametersUpdated(rebalanceTimelockPeriod, rebalanceThresholdScalar, true); + } + function oracle() public view override returns (IOracle) { return IOracle(_oracle); } @@ -417,6 +448,10 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I return _pool; } + function rebalanceThresholdScalar() external view override returns(uint256) { + return _rebalanceThresholdScalar; + } + function _withdrawWETH( IStrategy strategy, IStrategyRouter router, @@ -449,6 +484,9 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I IStrategy(strategy).updateRebalanceThreshold(state.rebalanceThreshold); if (state.social) emit StrategyOpen(strategy); if (state.set) emit StrategySet(strategy); + bytes32 key = keccak256(abi.encode(this.rebalance.selector, strategy)); + _setTimelock(key, _rebalanceTimelockPeriod); + _startTimelock(key, new bytes(0)); } function _deposit( @@ -463,7 +501,7 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I ) private { address weth; if (msg.value > 0) { - _require(amount == 0, uint256(0x1bb63a90056c12) /* error_macro_for("Ambiguous amount") */); + _require(amount == 0, uint256(0x1bb63a90056c15) /* error_macro_for("Ambiguous amount") */); amount = msg.value; weth = _weth; IWETH(weth).deposit{value: amount}(); @@ -504,8 +542,8 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I ControllerLibrary.verifyFormerDebt(address(strategy), newDebt, currentDebt); } // Check balance - (bool balancedAfter, uint256 totalAfter, ) = ControllerLibrary.verifyBalance(strategy, o); - _require(balancedAfter, uint256(0x1bb63a90056c13) /* error_macro_for("Not balanced") */); + (bool balancedAfter, uint256 totalAfter, ) = ControllerLibrary.verifyBalance(strategy, oracle(), 0); + _require(balancedAfter, uint256(0x1bb63a90056c16) /* error_macro_for("Not balanced") */); _checkSlippage(totalAfter, totalBefore, _strategyStates[address(strategy)].restructureSlippage); strategy.updateTokenValue(totalAfter, strategy.totalSupply()); } @@ -513,20 +551,20 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I function _checkSlippage(uint256 slippedValue, uint256 referenceValue, uint256 slippage) private pure { _require( slippedValue >= referenceValue.mul(slippage).div(DIVISOR), - uint256(0x1bb63a90056c14) /* error_macro_for("Too much slippage") */ + uint256(0x1bb63a90056c17) /* error_macro_for("Too much slippage") */ ); } function _checkDivisor(uint256 value) private pure { - _require(value <= DIVISOR, uint256(0x1bb63a90056c15) /* error_macro_for("Out of bounds") */); + _require(value <= DIVISOR, uint256(0x1bb63a90056c18) /* error_macro_for("Out of bounds") */); } function _checkFee(uint256 value) private pure { - _require(value <= FEE_BOUND, uint256(0x1bb63a90056c16) /* error_macro_for("Fee too high") */); + _require(value <= FEE_BOUND, uint256(0x1bb63a90056c19) /* error_macro_for("Fee too high") */); } function _checkTimelock(uint256 value) private { - _require(value <= 30 days, uint256(0x1bb63a90056c17) /* error_macro_for("Timelock is too long") */); + _require(value <= 30 days, uint256(0x1bb63a90056c1a) /* error_macro_for("Timelock is too long") */); } function _checkAndEmit(address strategy, TimelockCategory category, uint256 value, bool finalized) private { @@ -559,21 +597,21 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I * @notice Checks that strategy is initialized */ function _isInitialized(address strategy) private view { - _require(initialized(strategy), uint256(0x1bb63a90056c18) /* error_macro_for("Not initialized") */); + _require(initialized(strategy), uint256(0x1bb63a90056c1b) /* error_macro_for("Not initialized") */); } /** * @notice Checks that router is whitelisted */ function _onlyApproved(address account) private view { - _require(whitelist().approved(account), uint256(0x1bb63a90056c19) /* error_macro_for("Not approved") */); + _require(whitelist().approved(account), uint256(0x1bb63a90056c1c) /* error_macro_for("Not approved") */); } /** * @notice Checks if msg.sender is manager */ function _onlyManager(IStrategy strategy) private view { - _require(msg.sender == strategy.manager(), uint256(0x1bb63a90056c1a) /* error_macro_for("Not manager") */); + _require(msg.sender == strategy.manager(), uint256(0x1bb63a90056c1d) /* error_macro_for("Not manager") */); } /** @@ -582,15 +620,19 @@ contract StrategyController is IStrategyController, StrategyControllerStorage, I function _socialOrManager(IStrategy strategy) private view { _require( msg.sender == strategy.manager() || _strategyStates[address(strategy)].social, - uint256(0x1bb63a90056c1b) /* error_macro_for("Not manager") */ + uint256(0x1bb63a90056c1e) /* error_macro_for("Not manager") */ ); } function _notSet(address strategy) private view { - _require(!_strategyStates[strategy].set, uint256(0x1bb63a90056c1c) /* error_macro_for("Strategy cannot change") */); + _require(!_strategyStates[strategy].set, uint256(0x1bb63a90056c1f) /* error_macro_for("Strategy cannot change") */); + } + + function _timelockData(bytes32 identifier) internal override returns(TimelockData storage) { + return __timelockData[identifier]; } receive() external payable { - _require(msg.sender == _weth, uint256(0x1bb63a90056c1d) /* error_macro_for("Not WETH") */); + _require(msg.sender == _weth, uint256(0x1bb63a90056c20) /* error_macro_for("Not WETH") */); } } diff --git a/contracts/StrategyControllerStorage.sol b/contracts/StrategyControllerStorage.sol index 6f63e2a9..4035be8b 100644 --- a/contracts/StrategyControllerStorage.sol +++ b/contracts/StrategyControllerStorage.sol @@ -13,7 +13,11 @@ contract StrategyControllerStorage is StrategyTypes { mapping(address => StrategyState) internal _strategyStates; mapping(address => Timelock) internal _timelocks; address internal _pool; + mapping(bytes32 => TimelockData) internal __timelockData; + + uint256 internal _rebalanceTimelockPeriod; + uint256 internal _rebalanceThresholdScalar; // Gap for future storage changes - uint256[49] private __gap; + uint256[46] private __gap; } diff --git a/contracts/StrategyProxyFactory.sol b/contracts/StrategyProxyFactory.sol index 846968d1..e98aaea9 100644 --- a/contracts/StrategyProxyFactory.sol +++ b/contracts/StrategyProxyFactory.sol @@ -197,6 +197,10 @@ contract StrategyProxyFactory is IStrategyProxyFactory, StrategyProxyFactoryStor emit NewStreamingFee(uint256(fee)); } + function updateRebalanceParameters(uint256 rebalanceTimelockPeriod, uint256 rebalanceThresholdScalar) external onlyOwner { + IStrategyController(controller).updateRebalanceParameters(rebalanceTimelockPeriod, rebalanceThresholdScalar); + } + /* * @dev This function is called by StrategyProxyAdmin */ diff --git a/contracts/StrategyTokenStorage.sol b/contracts/StrategyTokenStorage.sol index 5cf12bf0..5734f0eb 100644 --- a/contracts/StrategyTokenStorage.sol +++ b/contracts/StrategyTokenStorage.sol @@ -34,7 +34,7 @@ contract StrategyTokenStorage is StrategyTypes { address[] internal _debt; mapping(address => int256) internal _percentage; mapping(address => TradeData) internal _tradeData; - mapping(bytes4 => TimelockData) internal __timelockData; + mapping(bytes32 => TimelockData) internal __timelockData; uint256 internal _managementFee; uint256 internal _managementFeeRate; diff --git a/contracts/helpers/Timelocks.sol b/contracts/helpers/Timelocks.sol index 5f6dc290..70be4d8e 100644 --- a/contracts/helpers/Timelocks.sol +++ b/contracts/helpers/Timelocks.sol @@ -5,48 +5,50 @@ import "./StrategyTypes.sol"; abstract contract Timelocks is StrategyTypes { - event TimelockSet(bytes4 selector, uint256 value); + event TimelockSet(bytes32 identifier, uint256 value); event UpdateTimelock(uint256 delay, bool finalized); bytes constant public UNSET_VALUE = abi.encode(keccak256("Timelocks: unset value.")); // updgradable implementations would benefit from the ability to set new timelocks. - function updateTimelock(bytes4 selector, uint256 delay) external virtual; - function finalizeTimelock() external virtual; + // not mandatory so suppressing "virtual". See EmergencyEstimator and StrategyController + // for an example and non-example + //function updateTimelock(bytes32 identifier, uint256 delay) external virtual; + //function finalizeTimelock() external virtual; // delay value is not validated but is assumed to be sensible // since this function is internal, this way `_timelockIsReady` will not overflow - function _setTimelock(bytes4 selector, uint256 delay) internal { - TimelockData storage td = _timelockData(selector); + function _setTimelock(bytes32 identifier, uint256 delay) internal { + TimelockData storage td = _timelockData(identifier); require(delay <= uint128(-1), "_setTimelock: delay out of range."); td.delay = uint128(delay); td.value = UNSET_VALUE; - emit TimelockSet(selector, delay); + emit TimelockSet(identifier, delay); } - function _timelockData(bytes4 functionSelector) internal virtual returns(TimelockData storage); + function _timelockData(bytes32 identifier) internal virtual returns(TimelockData storage); - function _startTimelock(bytes4 selector, bytes memory value) internal { - TimelockData storage td = _timelockData(selector); + function _startTimelock(bytes32 identifier, bytes memory value) internal { + TimelockData storage td = _timelockData(identifier); td.timestamp = uint128(block.timestamp); td.value = value; } - function _timelockIsReady(bytes4 selector) internal returns(bool) { - TimelockData memory td = _timelockData(selector); + function _timelockIsReady(bytes32 identifier) internal returns(bool) { + TimelockData memory td = _timelockData(identifier); if (td.timestamp == 0) return false; if (uint128(block.timestamp) >= td.timestamp + td.delay) return true; } // unchecked, assumes caller has checked `isReady` - function _getTimelockValue(bytes4 selector) internal returns(bytes memory) { - return _timelockData(selector).value; + function _getTimelockValue(bytes32 identifier) internal returns(bytes memory) { + return _timelockData(identifier).value; } - function _resetTimelock(bytes4 selector) internal { - TimelockData storage td = _timelockData(selector); + function _resetTimelock(bytes32 identifier) internal { + TimelockData storage td = _timelockData(identifier); td.timestamp = 0; td.value = UNSET_VALUE; } diff --git a/contracts/implementations/recovery/StrategyControllerPaused.sol b/contracts/implementations/recovery/StrategyControllerPaused.sol index fa83243d..7d26a59d 100644 --- a/contracts/implementations/recovery/StrategyControllerPaused.sol +++ b/contracts/implementations/recovery/StrategyControllerPaused.sol @@ -272,6 +272,10 @@ contract StrategyControllerPaused is IStrategyController, StrategyControllerStor } } + function updateRebalanceParameters(uint256 rebalanceTimelockPeriod, uint256 rebalanceThresholdScalar) external override { + revert("StrategyControllerPaused."); + } + function oracle() public view override returns (IOracle) { return IOracle(_oracle); } @@ -288,6 +292,10 @@ contract StrategyControllerPaused is IStrategyController, StrategyControllerStor return _pool; } + function rebalanceThresholdScalar() external view override returns(uint256) { + return _rebalanceThresholdScalar; + } + // Internal Strategy Functions /** * @notice Deposit eth or weth into strategy diff --git a/contracts/interfaces/IStrategyController.sol b/contracts/interfaces/IStrategyController.sol index 995d24bb..59d335cf 100644 --- a/contracts/interfaces/IStrategyController.sol +++ b/contracts/interfaces/IStrategyController.sol @@ -68,6 +68,8 @@ interface IStrategyController is StrategyTypes { uint256 newValue ) external; + function updateRebalanceParameters(uint256 rebalanceTimelockPeriod, uint256 rebalanceThresholdScalar) external; + function finalizeValue(IStrategy strategy) external; function openStrategy(IStrategy strategy) external; @@ -90,4 +92,6 @@ interface IStrategyController is StrategyTypes { function weth() external view returns (address); function pool() external view returns (address); + + function rebalanceThresholdScalar() external view returns(uint256); } diff --git a/contracts/libraries/ControllerLibrary.sol b/contracts/libraries/ControllerLibrary.sol index 6db68463..88fa2ab9 100644 --- a/contracts/libraries/ControllerLibrary.sol +++ b/contracts/libraries/ControllerLibrary.sol @@ -153,18 +153,19 @@ library ControllerLibrary { IOracle oracle, address weth, uint256 rebalanceSlippage, + uint256 rebalanceThresholdScalar, bytes memory data ) public { _onlyApproved(address(router)); strategy.settleSynths(); - (bool balancedBefore, uint256 totalBefore, int256[] memory estimates) = verifyBalance(strategy, oracle); + (bool balancedBefore, uint256 totalBefore, int256[] memory estimates) = verifyBalance(strategy, oracle, rebalanceThresholdScalar); require(!balancedBefore, "Balanced"); if (router.category() != IStrategyRouter.RouterCategory.GENERIC) data = abi.encode(totalBefore, estimates); // Rebalance _useRouter(strategy, router, router.rebalance, weth, data); // Recheck total - (bool balancedAfter, uint256 totalAfter, ) = verifyBalance(strategy, oracle); + (bool balancedAfter, uint256 totalAfter, ) = verifyBalance(strategy, oracle, 0); require(balancedAfter, "Not balanced"); _checkSlippage(totalAfter, totalBefore, rebalanceSlippage); strategy.updateTokenValue(totalAfter, strategy.totalSupply()); @@ -364,10 +365,17 @@ library ControllerLibrary { * whether the strategy is balanced. Necessary to confirm the balance * before and after a rebalance to ensure nothing fishy happened */ - function verifyBalance(IStrategy strategy, IOracle oracle) public view returns (bool, uint256, int256[] memory) { - (uint256 total, int256[] memory estimates) = - oracle.estimateStrategy(IStrategy(strategy)); + function verifyBalance(IStrategy strategy, IOracle oracle, uint256 rebalanceThresholdScalar) public view returns (bool, uint256, int256[] memory) { uint256 threshold = strategy.rebalanceThreshold(); + if (rebalanceThresholdScalar > 0) { // wider threshold + threshold = threshold.mul(rebalanceThresholdScalar) / uint256(DIVISOR); + } + return _verifyBalance(strategy, oracle, threshold); + } + + function _verifyBalance(IStrategy strategy, IOracle oracle, uint256 threshold) private view returns (bool, uint256, int256[] memory) { + (uint256 total, int256[] memory estimates) = + oracle.estimateStrategy(strategy); bool balanced = true; address[] memory strategyItems = strategy.items(); diff --git a/contracts/oracles/estimators/EmergencyEstimator.sol b/contracts/oracles/estimators/EmergencyEstimator.sol index 17df3739..87bcde5b 100644 --- a/contracts/oracles/estimators/EmergencyEstimator.sol +++ b/contracts/oracles/estimators/EmergencyEstimator.sol @@ -12,27 +12,32 @@ contract EmergencyEstimator is IEstimator, Ownable, Timelocks { using SignedSafeMath for int256; mapping(address => int256) public estimates; - mapping(bytes4 => TimelockData) private __timelockData; + mapping(bytes32 => TimelockData) private __timelockData; event EstimateSet(address token, int256 amount, bool finalized); constructor() public { - _setTimelock(this.updateEstimate.selector, 5 minutes); + _setTimelock( + keccak256(abi.encode(this.updateEstimate.selector)), // identifier + 5 minutes); } - function updateTimelock(bytes4 functionSelector, uint256 delay) external override onlyOwner { - _startTimelock(this.updateTimelock.selector, abi.encode(functionSelector, delay)); + function updateTimelock(bytes32 identifier, uint256 delay) external onlyOwner { + _startTimelock( + keccak256(abi.encode(this.updateTimelock.selector)), // identifier + abi.encode(identifier, delay)); // payload emit UpdateTimelock(delay, false); } - function finalizeTimelock() external override { - if (!_timelockIsReady(this.updateTimelock.selector)) { - TimelockData memory td = _timelockData(this.updateTimelock.selector); + function finalizeTimelock() external { + bytes32 key = keccak256(abi.encode(this.updateTimelock.selector)); + if (!_timelockIsReady(key)) { + TimelockData memory td = _timelockData(key); require(td.delay == 0, "finalizeTimelock: timelock is not ready."); } - (bytes4 selector, uint256 delay) = abi.decode(_getTimelockValue(this.updateTimelock.selector), (bytes4, uint256)); - _setTimelock(selector, delay); - _resetTimelock(this.updateTimelock.selector); + (bytes32 identifier, uint256 delay) = abi.decode(_getTimelockValue(key), (bytes4, uint256)); + _setTimelock(identifier, delay); + _resetTimelock(key); emit UpdateTimelock(delay, true); } @@ -41,14 +46,16 @@ contract EmergencyEstimator is IEstimator, Ownable, Timelocks { } function updateEstimate(address token, int256 amount) external onlyOwner { - _startTimelock(this.updateEstimate.selector, abi.encode(token, amount)); + _startTimelock( + keccak256(abi.encode(this.updateEstimate.selector)), // identifier + abi.encode(token, amount)); // payload emit EstimateSet(token, amount, false); } function finalizeSetEstimate() external { - require(_timelockIsReady(this.updateEstimate.selector), "finalizeSetEstimate: timelock not ready."); - (address token, int256 amount) = abi.decode(_getTimelockValue(this.updateEstimate.selector), (address, int256)); - _resetTimelock(this.updateEstimate.selector); + require(_timelockIsReady(keccak256(abi.encode(this.updateEstimate.selector))), "finalizeSetEstimate: timelock not ready."); + (address token, int256 amount) = abi.decode(_getTimelockValue(keccak256(abi.encode(this.updateEstimate.selector))), (address, int256)); + _resetTimelock(keccak256(abi.encode(this.updateEstimate.selector))); estimates[token] = amount; emit EstimateSet(token, amount, true); } @@ -62,7 +69,7 @@ contract EmergencyEstimator is IEstimator, Ownable, Timelocks { return int256(balance).mul(estimates[token]).div(int256(10**uint256(IERC20NonStandard(token).decimals()))); } - function _timelockData(bytes4 functionSelector) internal override returns(TimelockData storage) { - return __timelockData[functionSelector]; + function _timelockData(bytes32 identifier) internal override returns(TimelockData storage) { + return __timelockData[identifier]; } } diff --git a/contracts/test/LibraryWrapper.sol b/contracts/test/LibraryWrapper.sol index 3a0fee0a..6966ba18 100644 --- a/contracts/test/LibraryWrapper.sol +++ b/contracts/test/LibraryWrapper.sol @@ -8,6 +8,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IOracle.sol"; import "../interfaces/IStrategy.sol"; import "../interfaces/IStrategyController.sol"; +import "../libraries/ControllerLibrary.sol"; import "../libraries/StrategyLibrary.sol"; import "../helpers/StrategyTypes.sol"; @@ -17,22 +18,22 @@ contract LibraryWrapper is StrategyTypes{ IOracle public oracle; IStrategy public strategy; + IStrategyController public controller; - constructor(address oracle_, address strategy_) public { + constructor(address oracle_, address strategy_, address controller_) public { oracle = IOracle(oracle_); strategy = IStrategy(strategy_); + controller = IStrategyController(controller_); } - function isBalanced() external view returns (bool) { - return - _checkBalance( - strategy.rebalanceThreshold() - ); + function isBalanced() external view returns (bool balanced) { + (balanced,,) = ControllerLibrary.verifyBalance(strategy, oracle, controller.rebalanceThresholdScalar()); + return balanced; } - function isRebalanceNeeded(uint256 alertThreshold) external view returns (bool) { - bool balanced = _checkBalance(alertThreshold); - return !balanced; + function isBalancedInner() external view returns (bool balanced) { + (balanced,,) = ControllerLibrary.verifyBalance(strategy, oracle, 0); + return balanced; } function getRange(int256 expectedValue, uint256 range) external pure returns (int256) { @@ -83,53 +84,4 @@ contract LibraryWrapper is StrategyTypes{ ); } } - - function _checkBalance( - uint256 threshold - ) internal view returns (bool) { - (uint256 total, int256[] memory estimates) = - oracle.estimateStrategy(strategy); - bool balanced = true; - address[] memory strategyItems = strategy.items(); - for (uint256 i = 0; i < strategyItems.length; i++) { - int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, address(strategy), strategyItems[i]); - if (expectedValue > 0) { - int256 rebalanceRange = StrategyLibrary.getRange(expectedValue, threshold); - if (estimates[i] > expectedValue.add(rebalanceRange)) { - balanced = false; - break; - } - if (estimates[i] < expectedValue.sub(rebalanceRange)) { - balanced = false; - break; - } - } else { - // Token has an expected value of 0, so any value can cause the contract - // to be 'unbalanced' so we need an alternative way to determine balance. - // Min percent = 0.1%. If token value is above, consider it unbalanced - if (estimates[i] > StrategyLibrary.getRange(int256(total), 1)) { - balanced = false; - break; - } - } - } - if (balanced) { - address[] memory strategyDebt = strategy.debt(); - for (uint256 i = 0; i < strategyDebt.length; i++) { - int256 expectedValue = StrategyLibrary.getExpectedTokenValue(total, address(strategy), strategyDebt[i]); - int256 rebalanceRange = StrategyLibrary.getRange(expectedValue, threshold); - uint256 index = strategyItems.length + i; - // Debt - if (estimates[index] < expectedValue.add(rebalanceRange)) { - balanced = false; - break; - } - if (estimates[index] > expectedValue.sub(rebalanceRange)) { - balanced = false; - break; - } - } - } - return balanced; - } } diff --git a/errors/errors.json b/errors/errors.json index b91a3ec6..5cb8c505 100644 --- a/errors/errors.json +++ b/errors/errors.json @@ -29,32 +29,35 @@ "1bb63a90056c01": "Not factory", "1bb63a90056c02": "Strategy restructuring", "1bb63a90056c03": "withdrawETH: call failed.", - "1bb63a90056c04": "Invalid adapter", - "1bb63a90056c05": "Timelock active", - "1bb63a90056c06": "Invalid structure", - "1bb63a90056c07": "No changes queued", - "1bb63a90056c08": "Timelock active", - "1bb63a90056c09": "Wrong category", - "1bb63a90056c0a": "Invalid structure", - "1bb63a90056c0b": "Timelock active", - "1bb63a90056c0c": "updateValue: category is RESTRUCTURE.", - "1bb63a90056c0d": "No changes queued", - "1bb63a90056c0e": "Wrong category", - "1bb63a90056c0f": "Timelock active", - "1bb63a90056c10": "Strategy already open", - "1bb63a90056c11": "Strategy already set", - "1bb63a90056c12": "Ambiguous amount", - "1bb63a90056c13": "Not balanced", - "1bb63a90056c14": "Too much slippage", - "1bb63a90056c15": "Out of bounds", - "1bb63a90056c16": "Fee too high", - "1bb63a90056c17": "Timelock is too long", - "1bb63a90056c18": "Not initialized", - "1bb63a90056c19": "Not approved", - "1bb63a90056c1a": "Not manager", - "1bb63a90056c1b": "Not manager", - "1bb63a90056c1c": "Strategy cannot change", - "1bb63a90056c1d": "Not WETH" + "1bb63a90056c04": "rebalance timelock not ready.", + "1bb63a90056c05": "Invalid adapter", + "1bb63a90056c06": "Timelock active", + "1bb63a90056c07": "Invalid structure", + "1bb63a90056c08": "No changes queued", + "1bb63a90056c09": "Timelock active", + "1bb63a90056c0a": "Wrong category", + "1bb63a90056c0b": "Invalid structure", + "1bb63a90056c0c": "Timelock active", + "1bb63a90056c0d": "updateValue: category is RESTRUCTURE.", + "1bb63a90056c0e": "No changes queued", + "1bb63a90056c0f": "Wrong category", + "1bb63a90056c10": "Timelock active", + "1bb63a90056c11": "Strategy already open", + "1bb63a90056c12": "Strategy already set", + "1bb63a90056c13": "Not factory", + "1bb63a90056c14": "updateRebalanceParameters timelock not ready.", + "1bb63a90056c15": "Ambiguous amount", + "1bb63a90056c16": "Not balanced", + "1bb63a90056c17": "Too much slippage", + "1bb63a90056c18": "Out of bounds", + "1bb63a90056c19": "Fee too high", + "1bb63a90056c1a": "Timelock is too long", + "1bb63a90056c1b": "Not initialized", + "1bb63a90056c1c": "Not approved", + "1bb63a90056c1d": "Not manager", + "1bb63a90056c1e": "Not manager", + "1bb63a90056c1f": "Strategy cannot change", + "1bb63a90056c20": "Not WETH" } }, { diff --git a/lib/deploy.ts b/lib/deploy.ts index cf459659..3cfba29d 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -88,7 +88,8 @@ export class Platform { controller: Contract oracles: Oracles administration: Administration - library: Contract + strategyLibrary: Contract + controllerLibrary: Contract strategyLibraries: any // public constructor( @@ -96,14 +97,16 @@ export class Platform { controller: Contract, oracles: Oracles, administration: Administration, - library: Contract, + strategyLibrary: Contract, + controllerLibrary: Contract, strategyLibraries: any ) { this.strategyFactory = strategyFactory this.controller = controller this.oracles = oracles this.administration = administration - this.library = library + this.strategyLibrary = strategyLibrary + this.controllerLibrary = controllerLibrary this.strategyLibraries = strategyLibraries } @@ -439,7 +442,15 @@ export async function deployPlatform( platformProxyAdmin, } - return new Platform(factory, controller, oracles, administration, strategyLibrary, strategyLibraries) + return new Platform( + factory, + controller, + oracles, + administration, + strategyLibrary, + controllerLibrary, + strategyLibraries + ) } export async function deployUniswapV2Adapter( diff --git a/lib/enso.ts b/lib/enso.ts index 05612b21..16dc6609 100644 --- a/lib/enso.ts +++ b/lib/enso.ts @@ -1,4 +1,4 @@ -import { ethers, waffle } from "hardhat"; +import { ethers, waffle } from 'hardhat' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { BigNumber, Contract } from 'ethers' import BalancerFactory from '../artifacts/contracts/test/Balancer.sol/Balancer.json' @@ -31,13 +31,13 @@ import { deployLoopRouter, deployMulticallRouter, deployBatchDepositRouter, - Platform + Platform, } from './deploy' import { MAINNET_ADDRESSES, ESTIMATOR_CATEGORY } from './constants' const { AddressZero, WeiPerEther } = ethers.constants -const NULL_CONTRACT = new Contract(AddressZero, [], ethers.provider) +const NULL_CONTRACT = new Contract(AddressZero, [], ethers.provider) export const wethPerToken = (numTokens: number) => BigNumber.from(WeiPerEther).mul(100 * (numTokens - 1)) @@ -149,35 +149,19 @@ export class EnsoBuilder { let registry = {} as Contract switch (this.network) { case Networks.LocalTestnet: - [factory, registry] = await deployBalancer(this.signer, this.tokens) + ;[factory, registry] = await deployBalancer(this.signer, this.tokens) balancer = new Balancer(factory, registry) break case Networks.Mainnet: - factory = new Contract( - MAINNET_ADDRESSES.BALANCER_FACTORY, - BalancerFactory.abi, - this.signer - ) - registry = new Contract( - MAINNET_ADDRESSES.BALANCER_REGISTRY, - BalancerRegistry.abi, - this.signer - ) + factory = new Contract(MAINNET_ADDRESSES.BALANCER_FACTORY, BalancerFactory.abi, this.signer) + registry = new Contract(MAINNET_ADDRESSES.BALANCER_REGISTRY, BalancerRegistry.abi, this.signer) balancer = new Balancer(factory, registry) break case Networks.ExternalTestnet: throw Error('External testnet not implemented yet') default: - factory = new Contract( - MAINNET_ADDRESSES.BALANCER_FACTORY, - BalancerFactory.abi, - this.signer - ) - registry = new Contract( - MAINNET_ADDRESSES.BALANCER_REGISTRY, - BalancerRegistry.abi, - this.signer - ) + factory = new Contract(MAINNET_ADDRESSES.BALANCER_FACTORY, BalancerFactory.abi, this.signer) + registry = new Contract(MAINNET_ADDRESSES.BALANCER_REGISTRY, BalancerRegistry.abi, this.signer) balancer = new Balancer(factory, registry) break } @@ -204,9 +188,12 @@ export class EnsoBuilder { case Networks.LocalTestnet: this.tokens = await deployTokens(this.signer, this.defaults.numTokens, this.defaults.wethSupply) if (this.tokens === undefined) throw Error('Failed to deploy erc20 tokens') - uniswapV2Factory = await deployUniswapV2(this.signer, this.tokens); - [uniswapV3Factory, ] = await deployUniswapV3(this.signer, this.tokens) - uniswapV3Router = await waffle.deployContract(this.signer, UniswapV3Router, [uniswapV3Factory.address, this.tokens[0].address]) + uniswapV2Factory = await deployUniswapV2(this.signer, this.tokens) + ;[uniswapV3Factory] = await deployUniswapV3(this.signer, this.tokens) + uniswapV3Router = await waffle.deployContract(this.signer, UniswapV3Router, [ + uniswapV3Factory.address, + this.tokens[0].address, + ]) break case Networks.Mainnet: this.tokens[0] = new Contract(MAINNET_ADDRESSES.WETH, WETH9.abi, this.signer) @@ -248,8 +235,8 @@ export class EnsoBuilder { this.addRouter('batch') } this.routers = await Promise.all( - this.routers.map(async r => { - await r.deploy(this.signer, ensoPlatform.controller, ensoPlatform.library) + this.routers.map(async (r) => { + await r.deploy(this.signer, ensoPlatform.controller, ensoPlatform.strategyLibrary) await ensoPlatform.administration.whitelist.connect(this.signer).approve(r.contract?.address) return r }) @@ -269,45 +256,86 @@ export class EnsoBuilder { } // Deploy adapters if (this.adapters?.aaveV2 !== undefined) { - await this.adapters.aaveV2.deploy(this.signer, ensoPlatform.administration.whitelist, [new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], this.signer), ensoPlatform.controller, weth, ensoPlatform.oracles.registries.tokenRegistry, ESTIMATOR_CATEGORY.AAVE_V2]) + await this.adapters.aaveV2.deploy(this.signer, ensoPlatform.administration.whitelist, [ + new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], this.signer), + ensoPlatform.controller, + weth, + ensoPlatform.oracles.registries.tokenRegistry, + ESTIMATOR_CATEGORY.AAVE_V2, + ]) } if (this.adapters?.aaveV2Debt !== undefined) { - await this.adapters.aaveV2Debt.deploy(this.signer, ensoPlatform.administration.whitelist, [new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], this.signer), weth]) + await this.adapters.aaveV2Debt.deploy(this.signer, ensoPlatform.administration.whitelist, [ + new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], this.signer), + weth, + ]) } if (this.adapters?.balancer !== undefined) { balancer = await this.deployBalancer() - await this.adapters.balancer.deploy(this.signer, ensoPlatform.administration.whitelist, [balancer.registry, weth]) + await this.adapters.balancer.deploy(this.signer, ensoPlatform.administration.whitelist, [ + balancer.registry, + weth, + ]) } if (this.adapters?.compound !== undefined) { - await this.adapters.compound.deploy(this.signer, ensoPlatform.administration.whitelist, [new Contract(MAINNET_ADDRESSES.COMPOUND_COMPTROLLER, [], this.signer), weth, ensoPlatform.oracles.registries.tokenRegistry, ESTIMATOR_CATEGORY.COMPOUND]) + await this.adapters.compound.deploy(this.signer, ensoPlatform.administration.whitelist, [ + new Contract(MAINNET_ADDRESSES.COMPOUND_COMPTROLLER, [], this.signer), + weth, + ensoPlatform.oracles.registries.tokenRegistry, + ESTIMATOR_CATEGORY.COMPOUND, + ]) } if (this.adapters?.curve !== undefined) { - await this.adapters.curve.deploy(this.signer, ensoPlatform.administration.whitelist, [new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], this.signer), weth]) + await this.adapters.curve.deploy(this.signer, ensoPlatform.administration.whitelist, [ + new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], this.signer), + weth, + ]) } if (this.adapters?.curveLP !== undefined) { await this.adapters.curveLP.deploy(this.signer, ensoPlatform.administration.whitelist, [ new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], this.signer), ensoPlatform.oracles.registries.curveDepositZapRegistry, - weth + weth, ]) } if (this.adapters?.curveGauge !== undefined) { - await this.adapters.curveGauge.deploy(this.signer, ensoPlatform.administration.whitelist, [weth, ensoPlatform.oracles.registries.tokenRegistry, ESTIMATOR_CATEGORY.CURVE_GAUGE]) + await this.adapters.curveGauge.deploy(this.signer, ensoPlatform.administration.whitelist, [ + weth, + ensoPlatform.oracles.registries.tokenRegistry, + ESTIMATOR_CATEGORY.CURVE_GAUGE, + ]) } if (this.adapters?.synthetix !== undefined) { - await this.adapters.synthetix.deploy(this.signer, ensoPlatform.administration.whitelist, [new Contract(MAINNET_ADDRESSES.SYNTHETIX_ADDRESS_PROVIDER, [], this.signer), weth]) + await this.adapters.synthetix.deploy(this.signer, ensoPlatform.administration.whitelist, [ + new Contract(MAINNET_ADDRESSES.SYNTHETIX_ADDRESS_PROVIDER, [], this.signer), + weth, + ]) } if (this.adapters?.uniswap !== undefined) { - await this.adapters.uniswap.deploy(this.signer, ensoPlatform.administration.whitelist, [uniswapV2Factory, weth]) + await this.adapters.uniswap.deploy(this.signer, ensoPlatform.administration.whitelist, [ + uniswapV2Factory, + weth, + ]) } if (this.adapters?.uniswapV2 !== undefined) { - await this.adapters.uniswapV2.deploy(this.signer, ensoPlatform.administration.whitelist, [uniswapV2Factory, weth]) + await this.adapters.uniswapV2.deploy(this.signer, ensoPlatform.administration.whitelist, [ + uniswapV2Factory, + weth, + ]) } if (this.adapters?.uniswapV3 !== undefined) { - await this.adapters.uniswapV3.deploy(this.signer, ensoPlatform.administration.whitelist, [ensoPlatform.oracles.registries.uniswapV3Registry, uniswapV3Router, weth]) + await this.adapters.uniswapV3.deploy(this.signer, ensoPlatform.administration.whitelist, [ + ensoPlatform.oracles.registries.uniswapV3Registry, + uniswapV3Router, + weth, + ]) } if (this.adapters?.yearnV2 !== undefined) { - await this.adapters.yearnV2.deploy(this.signer, ensoPlatform.administration.whitelist, [weth, ensoPlatform.oracles.registries.tokenRegistry, ESTIMATOR_CATEGORY.YEARN_V2]) + await this.adapters.yearnV2.deploy(this.signer, ensoPlatform.administration.whitelist, [ + weth, + ensoPlatform.oracles.registries.tokenRegistry, + ESTIMATOR_CATEGORY.YEARN_V2, + ]) } // Goes last since it depends on other adapters if (this.adapters?.leverage !== undefined) { @@ -317,12 +345,16 @@ export class EnsoBuilder { this.adapters?.aaveV2Debt.contract || NULL_CONTRACT, new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], this.signer), usdc, - weth + weth, ]) } - const fullRouterIndex = this.routers.findIndex(router => router.type == Routers.Full) + const fullRouterIndex = this.routers.findIndex((router) => router.type == Routers.Full) if (this.adapters?.metastrategy !== undefined && fullRouterIndex > -1) { - await this.adapters.metastrategy.deploy(this.signer, ensoPlatform.administration.whitelist, [ensoPlatform.controller, this.routers[fullRouterIndex].contract || NULL_CONTRACT, weth]) + await this.adapters.metastrategy.deploy(this.signer, ensoPlatform.administration.whitelist, [ + ensoPlatform.controller, + this.routers[fullRouterIndex].contract || NULL_CONTRACT, + weth, + ]) } // Safety check @@ -409,7 +441,7 @@ export enum Adapters { Uniswap = 'uniswap', UniswapV2 = 'uniswapv2', UniswapV3 = 'uniswapv3', - YEarnV2 = 'yearnv2' + YEarnV2 = 'yearnv2', } export class Adapter { @@ -418,7 +450,7 @@ export class Adapter { constructor(adapterType: string) { const isAdapter = Object.values(Adapters).findIndex((v: string) => v === adapterType.toLowerCase()) !== -1 if (isAdapter) { - this.type = adapterType.toLowerCase() + this.type = adapterType.toLowerCase() } else { throw Error('Invalid adapter selected!') } @@ -430,16 +462,28 @@ export class Adapter { this.contract = await deployAaveV2DebtAdapter(signer, parameters[0], parameters[1]) } else if (this.type === Adapters.AaveV2) { if (parameters.length == 5) - this.contract = await deployAaveV2Adapter(signer, parameters[0], parameters[1], parameters[2], parameters[3], parameters[4]) + this.contract = await deployAaveV2Adapter( + signer, + parameters[0], + parameters[1], + parameters[2], + parameters[3], + parameters[4] + ) } else if (this.type === Adapters.Balancer) { if (parameters.length == 2) this.contract = await deployBalancerAdapter(signer, parameters[0], parameters[1]) } else if (this.type === Adapters.Compound) { if (parameters.length == 4) - this.contract = await deployCompoundAdapter(signer, parameters[0], parameters[1], parameters[2], parameters[3]) + this.contract = await deployCompoundAdapter( + signer, + parameters[0], + parameters[1], + parameters[2], + parameters[3] + ) } else if (this.type === Adapters.Curve) { - if (parameters.length == 2) - this.contract = await deployCurveAdapter(signer, parameters[0], parameters[1]) + if (parameters.length == 2) this.contract = await deployCurveAdapter(signer, parameters[0], parameters[1]) } else if (this.type === Adapters.CurveLP) { if (parameters.length == 3) this.contract = await deployCurveLPAdapter(signer, parameters[0], parameters[1], parameters[2]) @@ -448,11 +492,19 @@ export class Adapter { this.contract = await deployCurveGaugeAdapter(signer, parameters[0], parameters[1], parameters[2]) } else if (this.type === Adapters.Leverage) { if (parameters.length == 6) - this.contract = await deployLeverage2XAdapter(signer, parameters[0], parameters[1], parameters[2], parameters[3], parameters[4], parameters[5]) + this.contract = await deployLeverage2XAdapter( + signer, + parameters[0], + parameters[1], + parameters[2], + parameters[3], + parameters[4], + parameters[5] + ) } else if (this.type === Adapters.Synthetix) { if (parameters.length == 2) this.contract = await deploySynthetixAdapter(signer, parameters[0], parameters[1]) - } else if (this.type === Adapters.MetaStrategy){ + } else if (this.type === Adapters.MetaStrategy) { if (parameters.length == 3) this.contract = await deployMetaStrategyAdapter(signer, parameters[0], parameters[1], parameters[2]) } else if (this.type === Adapters.Uniswap) { @@ -476,7 +528,7 @@ export enum Routers { Multicall, Loop, Full, - Batch + Batch, } // TODO: implement encoding for each Router (chain calldata for each type of router MulticallRouter is IRouter, LoopRouter is IRouter etc..) @@ -508,7 +560,12 @@ export class Router { if (this.type == Routers.Multicall) { this.contract = await deployMulticallRouter(signer, controller) } else if (this.type == Routers.Full) { - this.contract = await deployFullRouter(signer, new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], ethers.provider), controller, library) + this.contract = await deployFullRouter( + signer, + new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], ethers.provider), + controller, + library + ) } else if (this.type == Routers.Batch) { this.contract = await deployBatchDepositRouter(signer, controller, library) } else { diff --git a/lib/mainnet.ts b/lib/mainnet.ts index 1f4c1d62..04c6a5b7 100644 --- a/lib/mainnet.ts +++ b/lib/mainnet.ts @@ -2,11 +2,16 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { Contract } from 'ethers' import { Platform, Administration, Oracles } from './deploy' import deployments from '../deployments.json' +import hre from 'hardhat' +const { ethers } = hre +const { constants } = ethers +const { AddressZero } = constants import PlatformProxyAdmin from '../artifacts/contracts/PlatformProxyAdmin.sol/PlatformProxyAdmin.json' import StrategyController from '../artifacts/contracts/StrategyController.sol/StrategyController.json' import StrategyProxyFactory from '../artifacts/contracts/StrategyProxyFactory.sol/StrategyProxyFactory.json' import StrategyLibrary from '../artifacts/contracts/libraries/StrategyLibrary.sol/StrategyLibrary.json' +import ControllerLibrary from '../artifacts/contracts/libraries/ControllerLibrary.sol/ControllerLibrary.json' import EnsoOracle from '../artifacts/contracts/oracles/EnsoOracle.sol/EnsoOracle.json' import UniswapV3Oracle from '../artifacts/contracts/oracles/protocols/UniswapV3Oracle.sol/UniswapV3Oracle.json' import ChainlinkOracle from '../artifacts/contracts/oracles/protocols/ChainlinkOracle.sol/ChainlinkOracle.json' @@ -55,7 +60,7 @@ export class LiveEnvironment { platform: Platform, adapters: LiveAdapters, routers: LiveRouters, - estimators: Estimators, + estimators: Estimators ) { this.signer = signer this.platform = platform @@ -65,10 +70,9 @@ export class LiveEnvironment { } } - export type LiveAdapters = { aaveV2: Contract - aaveV2Debt: Contract + aaveV2Debt: Contract balancer: Contract compound: Contract curve: Contract @@ -97,14 +101,14 @@ export enum AdapterTypes { Synthetix = 'synthetix', UniswapV2 = 'uniswapv2', UniswapV3 = 'uniswapv3', - YEarnV2 = 'yearnv2' + YEarnV2 = 'yearnv2', } export enum RouterTypes { Multicall = 'multicall', Loop = 'loop', Full = 'full', - Batch = 'batch' + Batch = 'batch', } export type LiveRouters = { @@ -128,39 +132,39 @@ export type Estimators = { } export function liveEstimators(signer: SignerWithAddress) { - if (!deployments.mainnet) throw Error("Deployment addresses not found") - const addrs = deployments.mainnet; - const defaultEstimator = new Contract(addrs.DefaultEstimator, BasicEstimator.abi, signer) - const chainlink = new Contract(addrs.ChainlinkEstimator, BasicEstimator.abi, signer) - const strategy = new Contract(addrs.StrategyEstimator, StrategyEstimator.abi, signer) - const emergency = new Contract(addrs.EmergencyEstimator, EmergencyEstimator.abi, signer) - const aaveV2 = new Contract(addrs.AaveV2Estimator, AaveV2Estimator.abi, signer) - const aaveV2Debt = new Contract(addrs.AaveV2DebtEstimator, AaveV2DebtEstimator.abi, signer) - const compound = new Contract(addrs.CompoundEstimator, CompoundEstimator.abi, signer) - const curveLP = new Contract(addrs.CurveLPEstimator, CurveLPEstimator.abi, signer) - const curveGauge = new Contract(addrs.CurveGaugeEstimator, CurveGaugeEstimator.abi, signer) - const yearnV2 = new Contract(addrs.YEarnV2Estimator, YEarnV2Estimator.abi, signer) - const estimators: Estimators = { - defaultEstimator, - chainlink, - strategy, - emergency, - aaveV2, - aaveV2Debt, - compound, - curveLP, - curveGauge, - yearnV2 - } - return estimators + if (!deployments.mainnet) throw Error('Deployment addresses not found') + const addrs = deployments.mainnet + const defaultEstimator = new Contract(addrs.DefaultEstimator, BasicEstimator.abi, signer) + const chainlink = new Contract(addrs.ChainlinkEstimator, BasicEstimator.abi, signer) + const strategy = new Contract(addrs.StrategyEstimator, StrategyEstimator.abi, signer) + const emergency = new Contract(addrs.EmergencyEstimator, EmergencyEstimator.abi, signer) + const aaveV2 = new Contract(addrs.AaveV2Estimator, AaveV2Estimator.abi, signer) + const aaveV2Debt = new Contract(addrs.AaveV2DebtEstimator, AaveV2DebtEstimator.abi, signer) + const compound = new Contract(addrs.CompoundEstimator, CompoundEstimator.abi, signer) + const curveLP = new Contract(addrs.CurveLPEstimator, CurveLPEstimator.abi, signer) + const curveGauge = new Contract(addrs.CurveGaugeEstimator, CurveGaugeEstimator.abi, signer) + const yearnV2 = new Contract(addrs.YEarnV2Estimator, YEarnV2Estimator.abi, signer) + const estimators: Estimators = { + defaultEstimator, + chainlink, + strategy, + emergency, + aaveV2, + aaveV2Debt, + compound, + curveLP, + curveGauge, + yearnV2, + } + return estimators } - - export function livePlatform(signer: SignerWithAddress): Platform { - if (!deployments.mainnet) throw Error("Deployment addresses not found") - const addrs = deployments.mainnet; - const strategyLibrary = new Contract(addrs.StrategyLibrary, StrategyLibrary.abi, signer) + if (!deployments.mainnet) throw Error('Deployment addresses not found') + const addrs = deployments.mainnet + const strategyLibrary = new Contract(addrs.StrategyLibrary, StrategyLibrary.abi, signer) + const controllerLibrary = new Contract(AddressZero, ControllerLibrary.abi, signer) // FIXME ControllerLibrary address when deployed + const tokenRegistry = new Contract(addrs.TokenRegistry, TokenRegistry.abi, signer) const curveDepositZapRegistry = new Contract(addrs.CurveDepositZapRegistry, CurveDepositZapRegistry.abi, signer) const uniswapV3Registry = new Contract(addrs.UniswapV3Registry, UniswapV3Registry.abi, signer) @@ -179,27 +183,27 @@ export function livePlatform(signer: SignerWithAddress): Platform { ensoOracle, protocols: { uniswapOracle, - chainlinkOracle + chainlinkOracle, }, registries: { tokenRegistry, curveDepositZapRegistry, uniswapV3Registry, - chainlinkRegistry - } + chainlinkRegistry, + }, } // Admin const administration: Administration = { whitelist, - platformProxyAdmin + platformProxyAdmin, } - return new Platform(factory, controller, oracles, administration, strategyLibrary, {}) // last param, strategyLibraries, currently not deployed on mainnet + return new Platform(factory, controller, oracles, administration, strategyLibrary, controllerLibrary, {}) // last params, strategyLibraries, controllerLibrary, currently not deployed on mainnet } export function liveAdapters(signer: SignerWithAddress): LiveAdapters { - const addrs = deployments.mainnet; + const addrs = deployments.mainnet const aaveV2 = new Contract(addrs.AaveV2Adapter, AaveV2Adapter.abi, signer) const aaveV2Debt = new Contract(addrs.AaveV2DebtAdapter, AaveV2DebtAdapter.abi, signer) const balancer = new Contract(addrs.BalancerAdapter, BalancerAdapter.abi, signer) @@ -230,14 +234,14 @@ export function liveAdapters(signer: SignerWithAddress): LiveAdapters { synthetix, uniswapV2, uniswapV3, - yearnV2 + yearnV2, } return liveAdapters } export function liveRouters(signer: SignerWithAddress): LiveRouters { - if (!deployments.mainnet) throw Error("Deployment addresses not found") - const addrs = deployments.mainnet; + if (!deployments.mainnet) throw Error('Deployment addresses not found') + const addrs = deployments.mainnet const multicall = new Contract(addrs.MulticallRouter, MulticallRouter.abi, signer) const loop = new Contract(addrs.LoopRouter, LoopRouter.abi, signer) const full = new Contract(addrs.FullRouter, FullRouter.abi, signer) @@ -246,22 +250,22 @@ export function liveRouters(signer: SignerWithAddress): LiveRouters { multicall, loop, full, - batch + batch, } return routers } export function getLiveContracts(signer: SignerWithAddress): LiveEnvironment { - const platform = livePlatform(signer); - const adapters = liveAdapters(signer); - const routers = liveRouters(signer); - const estimators = liveEstimators(signer); + const platform = livePlatform(signer) + const adapters = liveAdapters(signer) + const routers = liveRouters(signer) + const estimators = liveEstimators(signer) const liveContracts: LiveEnvironment = { signer, platform, adapters, routers, - estimators + estimators, } return liveContracts } diff --git a/test/aave-adapter.ts b/test/aave-adapter.ts index 7d78447a..70b80f18 100644 --- a/test/aave-adapter.ts +++ b/test/aave-adapter.ts @@ -26,6 +26,7 @@ import { MAINNET_ADDRESSES, ESTIMATOR_CATEGORY, ITEM_CATEGORY } from '../lib/con import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -51,7 +52,7 @@ describe('AaveAdapter', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, whitelist: Contract, uniswapAdapter: Contract, aaveV2Adapter: Contract, @@ -85,7 +86,7 @@ describe('AaveAdapter', function () { controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary whitelist = platform.administration.whitelist const aaveAddressProvider = new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], accounts[0]) const synthetixAddressProvider = new Contract(MAINNET_ADDRESSES.SYNTHETIX_ADDRESS_PROVIDER, [], accounts[0]) @@ -96,12 +97,14 @@ describe('AaveAdapter', function () { collateralToken = tokens.aWETH collateralToken2 = tokens.aCRV - fullRouter = await deployFullRouter(accounts[0], aaveAddressProvider, controller, library) + uniswapAdapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) + await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) + + fullRouter = await deployFullRouter(accounts[0], aaveAddressProvider, controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(fullRouter.address) multicallRouter = await deployMulticallRouter(accounts[0], controller) await whitelist.connect(accounts[0]).approve(multicallRouter.address) - uniswapAdapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) - await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) + aaveV2Adapter = await deployAaveV2Adapter( accounts[0], aaveAddressProvider, @@ -199,7 +202,7 @@ describe('AaveAdapter', function () { const strategyState: InitialState = { timelock: BigNumber.from(60), rebalanceThreshold: BigNumber.from(50), - rebalanceSlippage: BigNumber.from(997), + rebalanceSlippage: BigNumber.from(990), restructureSlippage: BigNumber.from(980), // Restucturing from this strategy requires higher slippage tolerance managementFee: BigNumber.from(0), social: false, @@ -222,10 +225,11 @@ describe('AaveAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -247,6 +251,7 @@ describe('AaveAdapter', function () { }) it('Should purchase a token, requiring a rebalance of strategy', async function () { + // simulates a large sway in the market over some period // Approve the user to use the adapter const value = WeiPerEther.mul(5000) await weth.connect(accounts[19]).deposit({ value: value }) @@ -260,6 +265,7 @@ describe('AaveAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller .connect(accounts[1]) .rebalance(strategy.address, fullRouter.address, '0x', { gasLimit: '5000000' }) @@ -296,6 +302,7 @@ describe('AaveAdapter', function () { // so during this rebalance, this stkAAVE is just sold on uniswap for WETH. // If later, we register stkAAVE itself as a "claimable", the strategy // could maintain a balance in stkAAVE from which it would claim rewards in AAVE + await increaseTime(5 * 60 + 1) const tx = await controller .connect(accounts[1]) .rebalance(strategy.address, fullRouter.address, '0x', { gasLimit: '5000000' }) @@ -333,7 +340,7 @@ describe('AaveAdapter', function () { strategy.address, fullRouter.address, amount, - '979', // note the high slippage! + '977', // note the high slippage! '0x', { gasLimit: '5000000' } ) @@ -352,7 +359,7 @@ describe('AaveAdapter', function () { strategy.address, fullRouter.address, amount, - '963', // note the high slippage! + '960', // note the high slippage! '0x', { gasLimit: '5000000' } ) @@ -364,20 +371,7 @@ describe('AaveAdapter', function () { }) it('Should restructure - basic', async function () { - const positions = [ - { - token: collateralToken, - percentage: BigNumber.from(500), - adapters: [aaveV2Adapter.address], - path: [], - }, - { - token: collateralToken2, - percentage: BigNumber.from(500), - adapters: [uniswapAdapter.address, aaveV2Adapter.address], - path: [tokens.crv], - }, - ] + const positions = [{ token: tokens.usdt, percentage: BigNumber.from(1000), adapters: [uniswapAdapter.address] }] strategyItems = prepareStrategy(positions, uniswapAdapter.address) await controller.connect(accounts[1]).restructure(strategy.address, strategyItems) }) @@ -472,6 +466,7 @@ describe('AaveAdapter', function () { //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) const amount = BigNumber.from('5000000000000000') const ethBalanceBefore = await accounts[1].getBalance() + // note the high slippage! const tx = await controller .connect(accounts[1]) .withdrawETH(strategy.address, fullRouter.address, amount, '970', '0x', { gasLimit: '5000000' }) @@ -486,9 +481,10 @@ describe('AaveAdapter', function () { //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) const amount = BigNumber.from('5000000000000000') const wethBalanceBefore = await weth.balanceOf(accounts[1].address) + // note the high slippage! const tx = await controller .connect(accounts[1]) - .withdrawWETH(strategy.address, fullRouter.address, amount, '950', '0x', { gasLimit: '5000000' }) // Slippage has to set really low, not sure why + .withdrawWETH(strategy.address, fullRouter.address, amount, '960', '0x', { gasLimit: '5000000' }) const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -550,10 +546,11 @@ describe('AaveAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -633,10 +630,11 @@ describe('AaveAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -723,10 +721,11 @@ describe('AaveAdapter', function () { strategy = await Strategy.attach(strategyAddress) const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -814,10 +813,15 @@ describe('AaveAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - let metaWrapper = await LibraryWrapper.connect(accounts[0]).deploy(oracle.address, strategyAddress) + let metaWrapper = await LibraryWrapper.connect(accounts[0]).deploy( + oracle.address, + strategyAddress, + controller.address + ) await metaWrapper.deployed() //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) diff --git a/test/balancer-adapter.ts b/test/balancer-adapter.ts index 8a1b44cf..0400ebc4 100644 --- a/test/balancer-adapter.ts +++ b/test/balancer-adapter.ts @@ -6,6 +6,7 @@ import * as deployer from '../lib/deploy' import { prepareStrategy, Position, StrategyItem, InitialState } from '../lib/encode' import { BigNumber, Contract, Event } from 'ethers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { increaseTime } from '../lib/utils' const { AddressZero, WeiPerEther, MaxUint256 } = constants const NUM_TOKENS = 3 @@ -21,7 +22,7 @@ describe('BalancerAdapter', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, router: Contract, balancerAdapter: Contract, uniswapAdapter: Contract, @@ -44,13 +45,13 @@ describe('BalancerAdapter', function () { controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary const whitelist = platform.administration.whitelist uniswapAdapter = await deployer.deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) balancerAdapter = await deployer.deployBalancerAdapter(accounts[0], balancerRegistry, weth) await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) await whitelist.connect(accounts[0]).approve(balancerAdapter.address) - router = await deployer.deployLoopRouter(accounts[0], controller, library) + router = await deployer.deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) }) @@ -85,10 +86,11 @@ describe('BalancerAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems, weth) @@ -155,6 +157,7 @@ describe('BalancerAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/batch-router.ts b/test/batch-router.ts index 9b34c988..237ca4f3 100644 --- a/test/batch-router.ts +++ b/test/batch-router.ts @@ -18,6 +18,7 @@ import { import { displayBalances } from '../lib/logging' import { DEFAULT_DEPOSIT_SLIPPAGE } from '../lib/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { increaseTime } from '../lib/utils' const NUM_TOKENS = 15 @@ -34,7 +35,7 @@ describe('BatchDepositRouter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -50,10 +51,10 @@ describe('BatchDepositRouter', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(adapter.address) - router = await deployBatchDepositRouter(accounts[0], controller, library) + router = await deployBatchDepositRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) }) @@ -119,10 +120,11 @@ describe('BatchDepositRouter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() await displayBalances( @@ -150,6 +152,7 @@ describe('BatchDepositRouter', function () { }) it('Should fail to rebalance: router revert', async function () { + await increaseTime(5 * 60 + 1) await expect( controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') ).to.be.revertedWith('Rebalance not supported') diff --git a/test/compound-adapter.ts b/test/compound-adapter.ts index 42682c60..7b489e63 100644 --- a/test/compound-adapter.ts +++ b/test/compound-adapter.ts @@ -20,6 +20,7 @@ import { DEFAULT_DEPOSIT_SLIPPAGE, MAINNET_ADDRESSES, ESTIMATOR_CATEGORY, ITEM_C import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -34,7 +35,7 @@ describe('CompoundAdapter', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, uniswapAdapter: Contract, compoundAdapter: Contract, strategy: Contract, @@ -54,13 +55,13 @@ describe('CompoundAdapter', function () { controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary const whitelist = platform.administration.whitelist const { tokenRegistry } = platform.oracles.registries await tokens.registerTokens(accounts[0], strategyFactory) - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) uniswapAdapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) @@ -145,10 +146,11 @@ describe('CompoundAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() // await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -169,6 +171,7 @@ describe('CompoundAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -189,6 +192,7 @@ describe('CompoundAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/curve-adapter.ts b/test/curve-adapter.ts index 3b1f0fbb..99450e97 100644 --- a/test/curve-adapter.ts +++ b/test/curve-adapter.ts @@ -26,6 +26,7 @@ import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' import UniswapV2Pair from '@uniswap/v2-core/build/UniswapV2Pair.json' import UniswapV3Factory from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -48,7 +49,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, uniswapV2Adapter: Contract, uniswapV2Factory: Contract, uniswapV3Adapter: Contract, @@ -81,7 +82,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { controller = platform.controller oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary const { tokenRegistry, curveDepositZapRegistry, chainlinkRegistry, uniswapV3Registry } = platform.oracles.registries @@ -95,7 +96,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { ) const addressProvider = new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], accounts[0]) - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) uniswapV2Adapter = await deployUniswapV2Adapter(accounts[0], uniswapV2Factory, weth) await whitelist.connect(accounts[0]).approve(uniswapV2Adapter.address) @@ -362,10 +363,11 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -571,7 +573,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const strategyState: InitialState = { timelock: BigNumber.from(60), rebalanceThreshold: BigNumber.from(50), - rebalanceSlippage: BigNumber.from(997), + rebalanceSlippage: BigNumber.from(990), restructureSlippage: BigNumber.from(980), //Slippage is set low because of low-liquidity in EURS' UniV2 pool managementFee: BigNumber.from(0), social: false, @@ -593,10 +595,11 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -605,7 +608,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter - const value = WeiPerEther.mul(500) + const value = WeiPerEther.mul(800) await weth.connect(accounts[19]).deposit({ value: value }) await weth.connect(accounts[19]).approve(uniswapV2Adapter.address, value) await uniswapV2Adapter @@ -617,6 +620,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -672,6 +676,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -723,10 +728,11 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -735,7 +741,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter - const value = WeiPerEther.mul(500) + const value = WeiPerEther.mul(800) await weth.connect(accounts[19]).deposit({ value: value }) await weth.connect(accounts[19]).approve(uniswapV2Adapter.address, value) await uniswapV2Adapter @@ -747,6 +753,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -767,6 +774,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -812,10 +820,11 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -824,7 +833,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { it('Should purchase a token, requiring a rebalance of strategy', async function () { // Approve the user to use the adapter - const value = WeiPerEther.mul(500) + const value = WeiPerEther.mul(1000) await weth.connect(accounts[19]).deposit({ value: value }) await weth.connect(accounts[19]).approve(uniswapV2Adapter.address, value) await uniswapV2Adapter @@ -836,6 +845,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -856,6 +866,7 @@ describe('CurveLPAdapter + CurveGaugeAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/experimental-strategy.ts b/test/experimental-strategy.ts index 382913b4..174e1905 100644 --- a/test/experimental-strategy.ts +++ b/test/experimental-strategy.ts @@ -23,6 +23,7 @@ import { MAINNET_ADDRESSES, ESTIMATOR_CATEGORY } from '../lib/constants' import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -36,7 +37,7 @@ describe('Experimental Strategy', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, uniswapV2Adapter: Contract, curveLPAdapter: Contract, yearnAdapter: Contract, @@ -63,7 +64,7 @@ describe('Experimental Strategy', function () { controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary const whitelist = platform.administration.whitelist const curveAddressProvider = new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], accounts[0]) const aaveAddressProvider = new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], accounts[0]) @@ -71,7 +72,7 @@ describe('Experimental Strategy', function () { const { tokenRegistry, curveDepositZapRegistry, chainlinkRegistry } = platform.oracles.registries await tokens.registerTokens(accounts[0], strategyFactory, undefined, chainlinkRegistry, curveDepositZapRegistry) - router = await deployFullRouter(accounts[0], aaveAddressProvider, controller, library) + router = await deployFullRouter(accounts[0], aaveAddressProvider, controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) uniswapV2Adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(uniswapV2Adapter.address) @@ -135,10 +136,11 @@ describe('Experimental Strategy', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -168,6 +170,7 @@ describe('Experimental Strategy', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/flash-loan.ts b/test/flash-loan.ts index 5bf0219b..6b46cc88 100644 --- a/test/flash-loan.ts +++ b/test/flash-loan.ts @@ -23,6 +23,7 @@ import { Contract, BigNumber } from 'ethers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' const { constants, getContractFactory, getSigners } = ethers const { AddressZero, WeiPerEther } = constants +import { increaseTime } from '../lib/utils' const NUM_TOKENS = 4 @@ -38,7 +39,7 @@ describe('Flash Loan', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, sushiAdapter: Contract, sushiFactory: Contract, uniswapAdapter: Contract, @@ -56,7 +57,7 @@ describe('Flash Loan', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary uniswapAdapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) sushiAdapter = await deployUniswapV2Adapter(accounts[0], sushiFactory, weth) await whitelist.connect(accounts[0]).approve(uniswapAdapter.address) @@ -107,10 +108,11 @@ describe('Flash Loan', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyConfig.strategyItems, weth) @@ -154,6 +156,7 @@ describe('Flash Loan', function () { ) const calls = [...rebalanceCalls, ...flashLoanCalls] const data = await multicallRouter.encodeCalls(calls) + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data) const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/kyber-adapter.ts b/test/kyber-adapter.ts index 5bcecf92..f2b10e32 100644 --- a/test/kyber-adapter.ts +++ b/test/kyber-adapter.ts @@ -22,6 +22,7 @@ import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' import UniswapV3Factory from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json' import IDMMFactory from '../artifacts/contracts/interfaces/kyber/IDMMFactory.sol/IDMMFactory.json' import IDMMRouter02 from '../artifacts/contracts/interfaces/kyber/IDMMRouter02.sol/IDMMRouter02.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -37,7 +38,7 @@ describe('KyberSwapAdapter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, kyberAdapter: Contract, uniswapV2Adapter: Contract, strategy: Contract, @@ -63,7 +64,7 @@ describe('KyberSwapAdapter', function () { controller = platform.controller oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary const { curveDepositZapRegistry, chainlinkRegistry, uniswapV3Registry } = platform.oracles.registries await tokens.registerTokens( @@ -74,7 +75,7 @@ describe('KyberSwapAdapter', function () { curveDepositZapRegistry ) - router = await deployLoopRouter(owner, controller, library) + router = await deployLoopRouter(owner, controller, platform.strategyLibrary) await whitelist.connect(owner).approve(router.address) kyberAdapter = await deployKyberSwapAdapter(owner, kyberFactory, kyberRouter, weth) await whitelist.connect(owner).approve(kyberAdapter.address) @@ -115,10 +116,11 @@ describe('KyberSwapAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -148,6 +150,7 @@ describe('KyberSwapAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -168,6 +171,7 @@ describe('KyberSwapAdapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/leverage-adapter.ts b/test/leverage-adapter.ts index 2b385bd6..cbb972b6 100644 --- a/test/leverage-adapter.ts +++ b/test/leverage-adapter.ts @@ -43,7 +43,7 @@ describe('Leverage2XAdapter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, uniswapAdapter: Contract, aaveV2Adapter: Contract, aaveV2DebtAdapter: Contract, @@ -63,7 +63,7 @@ describe('Leverage2XAdapter', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary const { tokenRegistry } = platform.oracles.registries await tokens.registerTokens(accounts[0], strategyFactory) @@ -156,10 +156,11 @@ describe('Leverage2XAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() await displayBalances( @@ -242,10 +243,11 @@ describe('Leverage2XAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() await displayBalances( diff --git a/test/library.ts b/test/library.ts index 3d387711..9b583455 100644 --- a/test/library.ts +++ b/test/library.ts @@ -9,7 +9,7 @@ const { AddressZero, WeiPerEther } = constants const NUM_TOKENS = 15 -describe('StrategyLibrary', function () { +describe('ControllerLibrary', function () { let tokens: Contract[], weth: Contract, accounts: SignerWithAddress[], @@ -19,7 +19,7 @@ describe('StrategyLibrary', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategyItems: StrategyItem[], wrapper: Contract @@ -39,10 +39,10 @@ describe('StrategyLibrary', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(adapter.address) - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) const positions = [ @@ -84,10 +84,11 @@ describe('StrategyLibrary', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) diff --git a/test/live-estimates.ts b/test/live-estimates.ts index 7016c560..82d040ec 100644 --- a/test/live-estimates.ts +++ b/test/live-estimates.ts @@ -100,7 +100,7 @@ describe('Live Estimates', function () { accounts[0], new Contract(MAINNET_ADDRESSES.AAVE_ADDRESS_PROVIDER, [], accounts[0]), controller, - enso.platform.library + enso.platform.strategyLibrary ) // Whitelist await enso.platform.administration.whitelist.connect(owner).approve(router.address) diff --git a/test/live-upgrades.ts b/test/live-upgrades.ts index a2bcce69..00cef838 100644 --- a/test/live-upgrades.ts +++ b/test/live-upgrades.ts @@ -64,8 +64,9 @@ describe('Live Upgrades', function () { it('Should updateTradeData respecting timelock', async function () { // first manager must setup the timelock - let updateTradeDataSelector = eDPI.interface.getSighash('updateTradeData') + const updateTradeDataSelector = eDPI.interface.getSighash('updateTradeData') await eDPI.connect(manager).updateTimelock(updateTradeDataSelector, 5 * 60) + await eDPI.connect(accounts[1]).finalizeTimelock() // now updateTradeData respecting the timelock diff --git a/test/meta-strategy-adapter.ts b/test/meta-strategy-adapter.ts index 70da4cbb..a1704940 100644 --- a/test/meta-strategy-adapter.ts +++ b/test/meta-strategy-adapter.ts @@ -31,6 +31,7 @@ import { } from '../lib/deploy' import { DEFAULT_DEPOSIT_SLIPPAGE } from '../lib/constants' //import { displayBalances } from '../lib/logging' +import { increaseTime } from '../lib/utils' const NUM_TOKENS = 15 const STRATEGY_STATE: InitialState = { @@ -57,7 +58,7 @@ describe('MetaStrategyAdapter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, uniswapAdapter: Contract, metaStrategyAdapter: Contract, basicStrategy: Contract, @@ -80,8 +81,8 @@ describe('MetaStrategyAdapter', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library - loopRouter = await deployLoopRouter(accounts[0], controller, library) + controllerLibrary = platform.controllerLibrary + loopRouter = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(loopRouter.address) multicallRouter = await deployMulticallRouter(accounts[0], controller) await whitelist.connect(accounts[0]).approve(multicallRouter.address) @@ -116,10 +117,15 @@ describe('MetaStrategyAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - basicWrapper = await LibraryWrapper.connect(accounts[0]).deploy(oracle.address, strategyAddress) + basicWrapper = await LibraryWrapper.connect(accounts[0]).deploy( + oracle.address, + strategyAddress, + controller.address + ) await basicWrapper.deployed() expect(await basicWrapper.isBalanced()).to.equal(true) @@ -156,10 +162,15 @@ describe('MetaStrategyAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - metaWrapper = await LibraryWrapper.connect(accounts[0]).deploy(oracle.address, strategyAddress) + metaWrapper = await LibraryWrapper.connect(accounts[0]).deploy( + oracle.address, + strategyAddress, + controller.address + ) await metaWrapper.deployed() //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) @@ -218,10 +229,15 @@ describe('MetaStrategyAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - metaMetaWrapper = await LibraryWrapper.connect(accounts[0]).deploy(oracle.address, strategyAddress) + metaMetaWrapper = await LibraryWrapper.connect(accounts[0]).deploy( + oracle.address, + strategyAddress, + controller.address + ) await metaMetaWrapper.deployed() }) @@ -241,6 +257,7 @@ describe('MetaStrategyAdapter', function () { it('Should rebalance strategy: selling basic strategy tokens', async function () { //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) const balanceBefore = await basicStrategy.balanceOf(metaStrategy.address) + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(metaStrategy.address, loopRouter.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -254,6 +271,7 @@ describe('MetaStrategyAdapter', function () { //await displayBalances(basicWrapper, basicStrategyItems.map((item) => item.item), weth) //await displayBalances(metaWrapper, metaStrategyItems.map((item) => item.item), weth) const balanceBefore = await metaStrategy.balanceOf(metaMetaStrategy.address) + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(metaMetaStrategy.address, loopRouter.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -278,6 +296,7 @@ describe('MetaStrategyAdapter', function () { it('Should rebalance strategy: buying basic strategy tokens', async function () { const balanceBefore = await basicStrategy.balanceOf(metaStrategy.address) + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(metaStrategy.address, loopRouter.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -423,6 +442,7 @@ describe('MetaStrategyAdapter', function () { ) //Encode multicalls and rebalance const rebalanceData = await multicallRouter.encodeCalls([...maliciousCalls, ...rebalanceCalls]) + await increaseTime(5 * 60 + 1) await expect( controller.connect(accounts[1]).rebalance(basicStrategy.address, multicallRouter.address, rebalanceData) ).to.be.revertedWith('') diff --git a/test/multicall-router.ts b/test/multicall-router.ts index 7c2ec0aa..29f4bfac 100644 --- a/test/multicall-router.ts +++ b/test/multicall-router.ts @@ -30,6 +30,7 @@ import { } from '../lib/encode' import { isRevertedWith } from '../lib/errors' import { DEFAULT_DEPOSIT_SLIPPAGE } from '../lib/constants' +import { increaseTime } from '../lib/utils' const { constants, getContractFactory, getSigners } = ethers const { AddressZero, WeiPerEther } = constants @@ -52,7 +53,7 @@ describe('MulticallRouter', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -68,7 +69,7 @@ describe('MulticallRouter', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(adapter.address) multicallRouter = await deployMulticallRouter(accounts[0], controller) @@ -129,10 +130,11 @@ describe('MulticallRouter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems, weth) @@ -192,6 +194,7 @@ describe('MulticallRouter', function () { // it should fail before that check is made const calls = [transferCall, reentrancyCall] const data = await multicallRouter.encodeCalls(calls) + await increaseTime(5 * 60 + 1) await expect( controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data) ).to.be.revertedWith('') @@ -210,6 +213,7 @@ describe('MulticallRouter', function () { strategy.address ) const data = await multicallRouter.encodeCalls([call]) + await increaseTime(5 * 60 + 1) expect( await isRevertedWith( controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data), @@ -232,6 +236,7 @@ describe('MulticallRouter', function () { strategy.address ) const data = await multicallRouter.encodeCalls([call]) + await increaseTime(5 * 60 + 1) await expect( controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data) ).to.be.revertedWith('') //Revert in calldata @@ -326,6 +331,7 @@ describe('MulticallRouter', function () { } const data = await multicallRouter.encodeCalls(calls) + await increaseTime(5 * 60 + 1) expect( await isRevertedWith( controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data), @@ -339,6 +345,7 @@ describe('MulticallRouter', function () { // Multicall gets initial tokens from uniswap const calls = await prepareRebalanceMulticall(strategy, multicallRouter, adapter, oracle, weth) const data = await multicallRouter.encodeCalls(calls) + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, multicallRouter.address, data) const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/reentrancy.ts b/test/reentrancy.ts index 6657e889..769021fe 100644 --- a/test/reentrancy.ts +++ b/test/reentrancy.ts @@ -35,7 +35,7 @@ describe('Reentrancy ', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -50,7 +50,7 @@ describe('Reentrancy ', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(adapter.address) multicallRouter = await deployMulticallRouter(accounts[0], controller) @@ -98,10 +98,11 @@ describe('Reentrancy ', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address) + wrapper = await LibraryWrapper.deploy(oracle.address, strategy.address, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems, weth) diff --git a/test/social-strategy.ts b/test/social-strategy.ts index 07855889..1717082e 100644 --- a/test/social-strategy.ts +++ b/test/social-strategy.ts @@ -43,7 +43,7 @@ describe('StrategyController - Social', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -59,10 +59,10 @@ describe('StrategyController - Social', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[0], uniswapFactory, weth) await whitelist.connect(accounts[0]).approve(adapter.address) - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) }) @@ -89,10 +89,11 @@ describe('StrategyController - Social', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -129,6 +130,7 @@ describe('StrategyController - Social', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -206,6 +208,7 @@ describe('StrategyController - Social', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/strategy-admin.ts b/test/strategy-admin.ts index 28f7880b..ba3511f6 100644 --- a/test/strategy-admin.ts +++ b/test/strategy-admin.ts @@ -47,13 +47,12 @@ describe('StrategyProxyAdmin', function () { controller = platform.controller strategyFactory = platform.strategyFactory whitelist = platform.administration.whitelist - const library = platform.library const strategyAdminAddress = await strategyFactory.admin() const StrategyAdmin = await getContractFactory('StrategyProxyAdmin') strategyAdmin = await StrategyAdmin.attach(strategyAdminAddress) adapter = await deployUniswapV2Adapter(accounts[10], uniswapFactory, weth) await whitelist.connect(accounts[10]).approve(adapter.address) - router = await deployLoopRouter(accounts[10], controller, library) + router = await deployLoopRouter(accounts[10], controller, platform.strategyLibrary) await whitelist.connect(accounts[10]).approve(router.address) }) diff --git a/test/strategy-controller.ts b/test/strategy-controller.ts index 580154dd..6da93aa5 100644 --- a/test/strategy-controller.ts +++ b/test/strategy-controller.ts @@ -42,7 +42,7 @@ describe('StrategyController', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, failAdapter: Contract, strategy: Contract, @@ -61,11 +61,11 @@ describe('StrategyController', function () { controller = platform.controller oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(owner, uniswapFactory, weth) await whitelist.connect(owner).approve(adapter.address) - router = await deployLoopRouter(owner, controller, library) + router = await deployLoopRouter(owner, controller, platform.strategyLibrary) await whitelist.connect(owner).approve(router.address) }) @@ -269,10 +269,11 @@ describe('StrategyController', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -545,7 +546,18 @@ describe('StrategyController', function () { expect(BigNumber.from((await controller.strategyState(strategy.address)).timelock).eq(timelock)).to.equal(true) }) + it('Should fail to rebalance, rebalance timelock not ready.', async function () { + expect( + await isRevertedWith( + controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x'), + 'rebalance timelock not ready.', + 'StrategyController.sol' + ) + ).to.be.true + }) + it('Should fail to rebalance, already balanced', async function () { + await increaseTime(5 * 60 + 1) expect( await isRevertedWith( controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x'), @@ -555,9 +567,9 @@ describe('StrategyController', function () { ).to.be.true }) - it('Should purchase a token, requiring a rebalance', async function () { + it('Should purchase a token, not quite enough for a rebalance', async function () { // Approve the user to use the adapter - const value = WeiPerEther.mul(50) + const value = WeiPerEther.mul(1) await weth.connect(accounts[2]).deposit({ value: value.mul(2) }) await weth.connect(accounts[2]).approve(adapter.address, value.mul(2)) await adapter @@ -568,7 +580,10 @@ describe('StrategyController', function () { .connect(accounts[2]) .swap(value.div(4), 0, weth.address, tokens[3].address, accounts[2].address, accounts[2].address) //await displayBalances(wrapper, strategyItems, weth) - expect(await wrapper.isBalanced()).to.equal(false) + + // note the differences in inner and outer rebalance thresholds + expect(await wrapper.isBalanced()).to.equal(true) + expect(await wrapper.isBalancedInner()).to.equal(false) // inner and outer wrt rebalance threshold }) it('Should fail to rebalance, router not approved', async function () { @@ -581,7 +596,35 @@ describe('StrategyController', function () { ).to.be.true }) + it('Should fail to rebalance, balanced', async function () { + expect( + await isRevertedWith( + controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x'), + 'Balanced', + 'StrategyController.sol' + ) + ).to.be.true + }) + + it('Should purchase a token, requiring a rebalance', async function () { + // Approve the user to use the adapter + const value = WeiPerEther.mul(10) + await weth.connect(accounts[2]).deposit({ value: value.mul(2) }) + await weth.connect(accounts[2]).approve(adapter.address, value.mul(2)) + await adapter + .connect(accounts[2]) + .swap(value, 0, weth.address, tokens[1].address, accounts[2].address, accounts[2].address) + //The following trade should increase the value of the token such that it doesn't need to be rebalanced + await adapter + .connect(accounts[2]) + .swap(value.div(4), 0, weth.address, tokens[3].address, accounts[2].address, accounts[2].address) + //await displayBalances(wrapper, strategyItems, weth) + expect(await wrapper.isBalanced()).to.equal(false) + expect(await wrapper.isBalancedInner()).to.equal(false) // inner and outer wrt rebalance threshold + }) + it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -628,7 +671,7 @@ describe('StrategyController', function () { await isRevertedWith( controller .connect(accounts[1]) - .deposit(strategy.address, router.address, 0, 1000, '0x', { value: BigNumber.from('1000') }), + .deposit(strategy.address, router.address, 0, 1000, '0x', { value: BigNumber.from('10000') }), 'Too much slippage', 'StrategyController.sol' ) @@ -777,7 +820,7 @@ describe('StrategyController', function () { .connect(accounts[2]) .swap(value, 0, tokens[1].address, weth.address, accounts[2].address, accounts[2].address) } else { - const value = WeiPerEther.mul(100) + const value = WeiPerEther.mul(1000) await weth.connect(accounts[2]).deposit({ value: value }) await weth.connect(accounts[2]).approve(adapter.address, value) await adapter @@ -794,6 +837,7 @@ describe('StrategyController', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -856,6 +900,7 @@ describe('StrategyController', function () { }) it('Transfer reserve token to require rebalance', async function () { + await increaseTime(5 * 60 + 1) expect( await isRevertedWith( controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x'), @@ -873,6 +918,7 @@ describe('StrategyController', function () { }) it('Transfer reserve weth to require rebalance', async function () { + await increaseTime(5 * 60 + 1) expect( await isRevertedWith( controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x'), @@ -881,7 +927,7 @@ describe('StrategyController', function () { ) ).to.be.true - const value = WeiPerEther.div(100) + const value = WeiPerEther.div(10) await weth.connect(accounts[4]).deposit({ value: value }) await weth.connect(accounts[4]).transfer(strategy.address, value) expect(await wrapper.isBalanced()).to.equal(false) @@ -911,7 +957,9 @@ describe('StrategyController', function () { await expect( controller .connect(accounts[1]) - .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { value: WeiPerEther }) + .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { + value: WeiPerEther, + }) ).to.be.revertedWith('') tx = await strategyFactory @@ -922,7 +970,9 @@ describe('StrategyController', function () { await expect( controller .connect(accounts[1]) - .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { value: WeiPerEther }) + .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { + value: WeiPerEther, + }) ).to.be.revertedWith('') const EmergencyEstimator = await getContractFactory('EmergencyEstimator') @@ -936,7 +986,9 @@ describe('StrategyController', function () { await expect( controller .connect(accounts[1]) - .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { value: WeiPerEther }) + .deposit(strategy.address, router.address, 0, DEFAULT_DEPOSIT_SLIPPAGE, '0x', { + value: WeiPerEther, + }) ).to.emit(controller, 'Deposit') }) diff --git a/test/strategy-factory.ts b/test/strategy-factory.ts index 31eb9697..e3866a89 100644 --- a/test/strategy-factory.ts +++ b/test/strategy-factory.ts @@ -15,6 +15,7 @@ import { isRevertedWith } from '../lib/errors' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' const { constants, getSigners } = ethers const { AddressZero, MaxUint256, WeiPerEther } = constants +import { increaseTime } from '../lib/utils' const chai = require('chai') import { solidity } from 'ethereum-waffle' @@ -36,7 +37,6 @@ describe('StrategyProxyFactory', function () { newOracle: Contract, newWhitelist: Contract, whitelist: Contract, - library: Contract, adapter: Contract, newRouter: Contract, strategy: Contract, @@ -53,10 +53,9 @@ describe('StrategyProxyFactory', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library adapter = await deployUniswapV2Adapter(accounts[10], uniswapFactory, weth) await whitelist.connect(accounts[10]).approve(adapter.address) - router = await deployLoopRouter(accounts[10], controller, library) + router = await deployLoopRouter(accounts[10], controller, platform.strategyLibrary) await whitelist.connect(accounts[10]).approve(router.address) }) @@ -70,7 +69,7 @@ describe('StrategyProxyFactory', function () { newFactory = platform.strategyFactory newOracle = platform.oracles.ensoOracle newWhitelist = platform.administration.whitelist - newRouter = await deployLoopRouter(accounts[10], controller, library) + newRouter = await deployLoopRouter(accounts[10], controller, platform.strategyLibrary) await newWhitelist.connect(accounts[10]).approve(adapter.address) await newWhitelist.connect(accounts[10]).approve(newRouter.address) newImplementationAddress = await newFactory.implementation() @@ -124,6 +123,34 @@ describe('StrategyProxyFactory', function () { expect(await controller.oracle()).to.equal(newOracle.address) }) + it('Should update rebalanceParameters', async function () { + expect( + await isRevertedWith( + // sanity check + controller.connect(accounts[5]).finalizeRebalanceParameters(), + 'updateRebalanceParameters timelock not ready.', + 'StrategyController.sol' + ) + ).to.be.true + let rebalanceTimelockPeriod = 5 * 60 + let rebalanceThresholdScalar = 1000 + await strategyFactory + .connect(accounts[10]) + .updateRebalanceParameters(rebalanceTimelockPeriod, rebalanceThresholdScalar) + await increaseTime(5 * 60 + 1) + await controller.connect(accounts[5]).finalizeRebalanceParameters() + expect(await controller.callStatic.rebalanceThresholdScalar()).to.eq(rebalanceThresholdScalar) + + // settle on this value + rebalanceThresholdScalar = 2000 + await strategyFactory + .connect(accounts[10]) + .updateRebalanceParameters(rebalanceTimelockPeriod, rebalanceThresholdScalar) + await increaseTime(5 * 60 + 1) + await controller.connect(accounts[5]).finalizeRebalanceParameters() + expect(await controller.callStatic.rebalanceThresholdScalar()).to.eq(rebalanceThresholdScalar) + }) + it('Should fail to update whitelist: not owner', async function () { await expect(strategyFactory.connect(accounts[1]).updateWhitelist(newWhitelist.address)).to.be.revertedWith( 'Not owner' diff --git a/test/strategy-token.ts b/test/strategy-token.ts index 87095d81..d5c3b3f9 100644 --- a/test/strategy-token.ts +++ b/test/strategy-token.ts @@ -33,7 +33,6 @@ describe('StrategyToken', function () { whitelist: Contract, router: Contract, oracle: Contract, - library: Contract, adapter: Contract, strategy: Contract, strategyItems: StrategyItem[], @@ -50,10 +49,9 @@ describe('StrategyToken', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library adapter = await deployUniswapV2Adapter(accounts[10], uniswapFactory, weth) await whitelist.connect(accounts[10]).approve(adapter.address) - router = await deployLoopRouter(accounts[10], controller, library) + router = await deployLoopRouter(accounts[10], controller, platform.strategyLibrary) await whitelist.connect(accounts[10]).approve(router.address) const Strategy = await platform.getStrategyContractFactory() const strategyImplementation = await Strategy.connect(accounts[10]).deploy( diff --git a/test/synthetix-adapter.ts b/test/synthetix-adapter.ts index 30b5861a..114fc0eb 100644 --- a/test/synthetix-adapter.ts +++ b/test/synthetix-adapter.ts @@ -41,7 +41,7 @@ describe('SynthetixAdapter', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, uniswapAdapter: Contract, compoundAdapter: Contract, curveAdapter: Contract, @@ -80,7 +80,7 @@ describe('SynthetixAdapter', function () { strategyFactory = platform.strategyFactory controller = platform.controller oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary const synthetixResolver = new Contract( '0x823bE81bbF96BEc0e25CA13170F5AaCb5B79ba83', @@ -90,7 +90,12 @@ describe('SynthetixAdapter', function () { const curveAddressProvider = new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], accounts[0]) const whitelist = platform.administration.whitelist - router = await deployFullRouter(accounts[10], new Contract(AddressZero, [], accounts[0]), controller, library) + router = await deployFullRouter( + accounts[10], + new Contract(AddressZero, [], accounts[0]), + controller, + platform.strategyLibrary + ) await whitelist.connect(accounts[10]).approve(router.address) uniswapAdapter = await deployUniswapV2Adapter(accounts[10], uniswapFactory, weth) await whitelist.connect(accounts[10]).approve(uniswapAdapter.address) @@ -210,10 +215,11 @@ describe('SynthetixAdapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) diff --git a/test/token-registry.ts b/test/token-registry.ts index e0ac2c05..eb6497bc 100644 --- a/test/token-registry.ts +++ b/test/token-registry.ts @@ -36,7 +36,7 @@ describe('TokenRegistry', function () { this.controller = platform.controller this.oracle = platform.oracles.ensoOracle this.whitelist = platform.administration.whitelist - this.library = platform.library + this.controllerLibrary = platform.controllerLibrary this.tokenRegistry = platform.oracles.registries.tokenRegistry const { curveDepositZapRegistry, chainlinkRegistry } = platform.oracles.registries @@ -48,7 +48,7 @@ describe('TokenRegistry', function () { curveDepositZapRegistry ) - this.router = await deployLoopRouter(this.accounts[0], this.controller, this.library) + this.router = await deployLoopRouter(this.accounts[0], this.controller, platform.strategyLibrary) await this.whitelist.connect(this.accounts[0]).approve(this.router.address) this.uniswapAdapter = await deployUniswapV2Adapter(this.accounts[0], this.uniswapFactory, this.weth) await this.whitelist.connect(this.accounts[0]).approve(this.uniswapAdapter.address) @@ -93,10 +93,11 @@ describe('TokenRegistry', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: this.library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: this.controllerLibrary.address, }, }) - this.wrapper = await LibraryWrapper.deploy(this.oracle.address, strategyAddress) + this.wrapper = await LibraryWrapper.deploy(this.oracle.address, strategyAddress, this.controller.address) await this.wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) diff --git a/test/uniswap-v3-adapter.ts b/test/uniswap-v3-adapter.ts index f6c9ad23..ce1d7d19 100644 --- a/test/uniswap-v3-adapter.ts +++ b/test/uniswap-v3-adapter.ts @@ -28,11 +28,12 @@ let tokens: Contract[], strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, uniswapOracle: Contract, adapter: Contract, router: Contract, strategy: Contract, + strategyLibrary: Contract, strategyClaim: Contract, wrapper: Contract, uniswapRegistry: Contract, @@ -118,21 +119,17 @@ describe('UniswapV3Adapter', function () { const whitelist = await Whitelist.connect(owner).deploy() await whitelist.deployed() - const StrategyLibraryFactory = await getContractFactory('StrategyLibrary') - library = await StrategyLibraryFactory.connect(owner).deploy() - await library.deployed() - const PlatformProxyAdmin = await getContractFactory('PlatformProxyAdmin') const platformProxyAdmin = await PlatformProxyAdmin.connect(owner).deploy() await platformProxyAdmin.deployed() const controllerAddress = await platformProxyAdmin.controller() const factoryAddress = await platformProxyAdmin.factory() - const strategyLibrary = await waffle.deployContract(accounts[0], StrategyLibrary, []) + strategyLibrary = await waffle.deployContract(accounts[0], StrategyLibrary, []) await strategyLibrary.deployed() const strategyLibraryLink = createLink(StrategyLibrary, strategyLibrary.address) - const controllerLibrary = await waffle.deployContract( + controllerLibrary = await waffle.deployContract( accounts[0], linkBytecode(ControllerLibrary, [strategyLibraryLink]), [] @@ -183,7 +180,7 @@ describe('UniswapV3Adapter', function () { adapter = await deployUniswapV3Adapter(owner, uniswapRegistry, uniswapRouter, weth) await whitelist.connect(owner).approve(adapter.address) - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, strategyLibrary) await whitelist.connect(owner).approve(router.address) uniswapQuoter = await deployContract(trader, Quoter, [uniswapV3Factory.address, weth.address]) @@ -231,10 +228,11 @@ describe('UniswapV3Adapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -257,6 +255,7 @@ describe('UniswapV3Adapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -271,6 +270,7 @@ describe('UniswapV3Adapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) diff --git a/test/weird-erc20s.ts b/test/weird-erc20s.ts index 6ed93cda..a1282db6 100644 --- a/test/weird-erc20s.ts +++ b/test/weird-erc20s.ts @@ -17,6 +17,7 @@ import { } from '../lib/deploy' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { BigNumber, Event, Contract } from 'ethers' +import { increaseTime } from '../lib/utils' const NUM_TOKENS = 10 const STRATEGY_STATE: InitialState = { @@ -66,7 +67,7 @@ describe('Weird ERC20s', function () { controller: Contract, oracle: Contract, whitelist: Contract, - library: Contract, + controllerLibrary: Contract, router: Contract, adapter: Contract, strategy: Contract, @@ -124,10 +125,10 @@ describe('Weird ERC20s', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(accounts[10], uniswapFactory, weth) await whitelist.connect(accounts[10]).approve(adapter.address) - router = await deployLoopRouter(accounts[10], controller, library) + router = await deployLoopRouter(accounts[10], controller, platform.strategyLibrary) await whitelist.connect(accounts[10]).approve(router.address) // remove weth from weird token list @@ -157,10 +158,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -177,6 +179,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -202,10 +205,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -221,6 +225,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -246,10 +251,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -265,6 +271,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -290,10 +297,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -309,6 +317,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -334,10 +343,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -353,6 +363,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -378,10 +389,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -397,6 +409,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) @@ -422,10 +435,11 @@ describe('Weird ERC20s', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() expect(await wrapper.isBalanced()).to.equal(true) @@ -441,6 +455,7 @@ describe('Weird ERC20s', function () { expect(await wrapper.isBalanced()).to.equal(false) // Rebalance + await increaseTime(5 * 60 + 1) await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') }) diff --git a/test/x-fees.ts b/test/x-fees.ts index 953c8a53..de72be5d 100644 --- a/test/x-fees.ts +++ b/test/x-fees.ts @@ -38,7 +38,7 @@ describe('StrategyToken Fees', function () { whitelist: Contract, router: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, adapter: Contract, strategy: Contract, wrapper: Contract, @@ -66,10 +66,10 @@ describe('StrategyToken Fees', function () { strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle whitelist = platform.administration.whitelist - library = platform.library + controllerLibrary = platform.controllerLibrary adapter = await deployUniswapV2Adapter(owner, uniswapFactory, weth) await whitelist.connect(owner).approve(adapter.address) - router = await deployLoopRouter(owner, controller, library) + router = await deployLoopRouter(owner, controller, platform.strategyLibrary) await whitelist.connect(owner).approve(router.address) }) @@ -114,10 +114,11 @@ describe('StrategyToken Fees', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() }) diff --git a/test/yearn-adapter.ts b/test/yearn-adapter.ts index 116db042..6512bead 100644 --- a/test/yearn-adapter.ts +++ b/test/yearn-adapter.ts @@ -24,6 +24,7 @@ import ERC20 from '@uniswap/v2-periphery/build/ERC20.json' import WETH9 from '@uniswap/v2-periphery/build/WETH9.json' import UniswapV2Factory from '@uniswap/v2-core/build/UniswapV2Factory.json' import UniswapV3Factory from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json' +import { increaseTime } from '../lib/utils' chai.use(solidity) @@ -37,7 +38,7 @@ describe('YEarnV2Adapter', function () { strategyFactory: Contract, controller: Contract, oracle: Contract, - library: Contract, + controllerLibrary: Contract, uniswapV2Adapter: Contract, uniswapV3Adapter: Contract, curveAdapter: Contract, @@ -60,7 +61,7 @@ describe('YEarnV2Adapter', function () { controller = platform.controller strategyFactory = platform.strategyFactory oracle = platform.oracles.ensoOracle - library = platform.library + controllerLibrary = platform.controllerLibrary const { tokenRegistry, curveDepositZapRegistry, chainlinkRegistry, uniswapV3Registry } = platform.oracles.registries @@ -74,7 +75,7 @@ describe('YEarnV2Adapter', function () { const addressProvider = new Contract(MAINNET_ADDRESSES.CURVE_ADDRESS_PROVIDER, [], accounts[0]) const whitelist = platform.administration.whitelist - router = await deployLoopRouter(accounts[0], controller, library) + router = await deployLoopRouter(accounts[0], controller, platform.strategyLibrary) await whitelist.connect(accounts[0]).approve(router.address) uniswapV2Adapter = await deployUniswapV2Adapter(accounts[0], uniswapV2Factory, weth) await whitelist.connect(accounts[0]).approve(uniswapV2Adapter.address) @@ -140,10 +141,11 @@ describe('YEarnV2Adapter', function () { const LibraryWrapper = await getContractFactory('LibraryWrapper', { libraries: { - StrategyLibrary: library.address, + StrategyLibrary: platform.strategyLibrary.address, + ControllerLibrary: controllerLibrary.address, }, }) - wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress) + wrapper = await LibraryWrapper.deploy(oracle.address, strategyAddress, controller.address) await wrapper.deployed() //await displayBalances(wrapper, strategyItems.map((item) => item.item), weth) @@ -164,6 +166,7 @@ describe('YEarnV2Adapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString()) @@ -181,6 +184,7 @@ describe('YEarnV2Adapter', function () { }) it('Should rebalance strategy', async function () { + await increaseTime(5 * 60 + 1) const tx = await controller.connect(accounts[1]).rebalance(strategy.address, router.address, '0x') const receipt = await tx.wait() console.log('Gas Used: ', receipt.gasUsed.toString())