From 00f001bd6c0668410015800ef753857af812ad99 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 7 Nov 2023 12:21:02 -0700 Subject: [PATCH 1/4] build: util contracts --- src/HealthCheck/BaseHealthCheck.sol | 2 +- src/utils/Clonable.sol | 38 +++++++++++++++++++ src/utils/Governance2Step.sol | 58 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/utils/Clonable.sol create mode 100644 src/utils/Governance2Step.sol diff --git a/src/HealthCheck/BaseHealthCheck.sol b/src/HealthCheck/BaseHealthCheck.sol index 23b2090..0fe93d3 100644 --- a/src/HealthCheck/BaseHealthCheck.sol +++ b/src/HealthCheck/BaseHealthCheck.sol @@ -110,7 +110,7 @@ abstract contract BaseHealthCheck is BaseStrategy { } /** - * @notice OVerrides the default {harvestAndReport} to include a healthcheck. + * @notice Overrides the default {harvestAndReport} to include a healthcheck. * @return _totalAssets New totalAssets post report. */ function harvestAndReport() diff --git a/src/utils/Clonable.sol b/src/utils/Clonable.sol new file mode 100644 index 0000000..ec23cc3 --- /dev/null +++ b/src/utils/Clonable.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +contract Clonable { + // Set to the address to auto clone from. + address public original; + + /** + * @notice Clone the contracts default `original` contract. + * @return Address of the new Minimal Proxy clone. + */ + function _clone() internal returns (address) { + return _clone(original); + } + + /** + * @notice Clone any `_original` contract. + * @return _newContract Address of the new Minimal Proxy clone. + */ + function _clone(address _original) internal returns (address _newContract) { + // Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol + bytes20 addressBytes = bytes20(_original); + assembly { + // EIP-1167 bytecode + let clone_code := mload(0x40) + mstore( + clone_code, + 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 + ) + mstore(add(clone_code, 0x14), addressBytes) + mstore( + add(clone_code, 0x28), + 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 + ) + _newContract := create(0, clone_code, 0x37) + } + } +} diff --git a/src/utils/Governance2Step.sol b/src/utils/Governance2Step.sol new file mode 100644 index 0000000..ba7edf5 --- /dev/null +++ b/src/utils/Governance2Step.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +contract Governance { + event GovernanceTransferred( + address indexed previousGovernance, + address indexed newGovernance + ); + + event UpdatePendingGovernance(address indexed newPendingGovernance); + + modifier onlyGovernance() { + _checkGovernance(); + _; + } + + function _checkGovernance() internal view virtual { + require(governance == msg.sender, "!governance"); + } + + // Address that can set the default base fee and provider + address public governance; + + // Address that is set to take over governance. + address public pendingGovernance; + + constructor(address _governance) { + governance = _governance; + + emit GovernanceTransferred(address(0), _governance); + } + + /** + * @notice Sets a new address as the `pendingGovernance` of the contract. + * @dev Throws if the caller is not current governance. + * @param _newGovernance The new governance address. + */ + function transferGovernance( + address _newGovernance + ) external onlyGovernance { + require(_newGovernance != address(0), "ZERO ADDRESS"); + pendingGovernance = _newGovernance; + + emit UpdatePendingGovernance(_newGovernance); + } + + /** + * @notice Allows the `pendingGovernance` to accept the role. + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "!pending governance"); + + emit GovernanceTransferred(governance, msg.sender); + + governance = msg.sender; + pendingGovernance = address(0); + } +} From 200e71556d41f12f5c24b219d86bd51df8fef8cb Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 7 Nov 2023 12:22:16 -0700 Subject: [PATCH 2/4] fix: name --- src/HealthCheck/BaseHealthCheck.sol | 2 +- src/utils/Governance2Step.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HealthCheck/BaseHealthCheck.sol b/src/HealthCheck/BaseHealthCheck.sol index 0fe93d3..23b2090 100644 --- a/src/HealthCheck/BaseHealthCheck.sol +++ b/src/HealthCheck/BaseHealthCheck.sol @@ -110,7 +110,7 @@ abstract contract BaseHealthCheck is BaseStrategy { } /** - * @notice Overrides the default {harvestAndReport} to include a healthcheck. + * @notice OVerrides the default {harvestAndReport} to include a healthcheck. * @return _totalAssets New totalAssets post report. */ function harvestAndReport() diff --git a/src/utils/Governance2Step.sol b/src/utils/Governance2Step.sol index ba7edf5..729d9d6 100644 --- a/src/utils/Governance2Step.sol +++ b/src/utils/Governance2Step.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity 0.8.18; -contract Governance { +contract Governance2Step { event GovernanceTransferred( address indexed previousGovernance, address indexed newGovernance From ea5159047aaf0fecb17536080006cb151df4e90b Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 10 Nov 2023 16:50:53 -0700 Subject: [PATCH 3/4] feat: virtualize --- src/AprOracle/AprOracle.sol | 14 +++++++----- src/ReportTrigger/CommonReportTrigger.sol | 26 +++++++++++------------ src/swappers/SolidlySwapper.sol | 10 ++++----- src/swappers/UniswapV2Swapper.sol | 8 +++---- src/swappers/UniswapV3Swapper.sol | 8 +++---- src/utils/Clonable.sol | 6 ++++-- src/utils/Governance.sol | 2 +- src/utils/Governance2Step.sol | 23 ++++---------------- 8 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/AprOracle/AprOracle.sol b/src/AprOracle/AprOracle.sol index 7a262df..49d6983 100644 --- a/src/AprOracle/AprOracle.sol +++ b/src/AprOracle/AprOracle.sol @@ -54,7 +54,7 @@ contract AprOracle { function getStrategyApr( address _strategy, int256 _debtChange - ) public view returns (uint256) { + ) public view virtual returns (uint256) { // Get the oracle set for this specific strategy. address oracle = oracles[_strategy]; @@ -70,7 +70,9 @@ contract AprOracle { * @param _strategy Address of the strategy. * @return . The current weighted APR of the strategy. */ - function weightedApr(address _strategy) external view returns (uint256) { + function weightedApr( + address _strategy + ) external view virtual returns (uint256) { return IStrategy(_strategy).totalAssets() * getStrategyApr(_strategy, 0); } @@ -84,7 +86,7 @@ contract AprOracle { * @param _strategy Address of the strategy. * @param _oracle Address of the APR Oracle. */ - function setOracle(address _strategy, address _oracle) external { + function setOracle(address _strategy, address _oracle) external virtual { require(msg.sender == IStrategy(_strategy).management(), "!authorized"); oracles[_strategy] = _oracle; @@ -100,7 +102,9 @@ contract AprOracle { * @param _vault The address of the vault or strategy. * @return apr The current apr expressed as 1e18. */ - function getCurrentApr(address _vault) external view returns (uint256 apr) { + function getCurrentApr( + address _vault + ) external view virtual returns (uint256 apr) { return getExpectedApr(_vault, 0); } @@ -122,7 +126,7 @@ contract AprOracle { function getExpectedApr( address _vault, int256 _delta - ) public view returns (uint256 apr) { + ) public view virtual returns (uint256 apr) { IVault vault = IVault(_vault); // Check if the full profit has already been unlocked. diff --git a/src/ReportTrigger/CommonReportTrigger.sol b/src/ReportTrigger/CommonReportTrigger.sol index 6cb5dd8..4c23e75 100644 --- a/src/ReportTrigger/CommonReportTrigger.sol +++ b/src/ReportTrigger/CommonReportTrigger.sol @@ -121,7 +121,7 @@ contract CommonReportTrigger is Governance { function setCustomStrategyTrigger( address _strategy, address _trigger - ) external { + ) external virtual { require(msg.sender == IStrategy(_strategy).management(), "!authorized"); customStrategyTrigger[_strategy] = _trigger; @@ -144,7 +144,7 @@ contract CommonReportTrigger is Governance { function setCustomStrategyBaseFee( address _strategy, uint256 _baseFee - ) external { + ) external virtual { require(msg.sender == IStrategy(_strategy).management(), "!authorized"); customStrategyBaseFee[_strategy] = _baseFee; @@ -171,7 +171,7 @@ contract CommonReportTrigger is Governance { address _vault, address _strategy, address _trigger - ) external { + ) external virtual { // Check that the address has the ADD_STRATEGY_MANAGER role on // the vault. Just check their role has a 1 at the first position. uint256 mask = 1; @@ -204,7 +204,7 @@ contract CommonReportTrigger is Governance { address _vault, address _strategy, uint256 _baseFee - ) external { + ) external virtual { // Check that the address has the ADD_STRATEGY_MANAGER role on // the vault. Just check their role has a 1 at the first position. uint256 mask = 1; @@ -232,7 +232,7 @@ contract CommonReportTrigger is Governance { */ function strategyReportTrigger( address _strategy - ) external view returns (bool, bytes memory) { + ) external view virtual returns (bool, bytes memory) { address _trigger = customStrategyTrigger[_strategy]; // If a custom trigger contract is set use that one. @@ -266,7 +266,7 @@ contract CommonReportTrigger is Governance { */ function defaultStrategyReportTrigger( address _strategy - ) public view returns (bool, bytes memory) { + ) public view virtual returns (bool, bytes memory) { // Cache the strategy instance. IStrategy strategy = IStrategy(_strategy); @@ -314,7 +314,7 @@ contract CommonReportTrigger is Governance { function vaultReportTrigger( address _vault, address _strategy - ) external view returns (bool, bytes memory) { + ) external view virtual returns (bool, bytes memory) { address _trigger = customVaultTrigger[_vault][_strategy]; // If a custom trigger contract is set use that. @@ -351,7 +351,7 @@ contract CommonReportTrigger is Governance { function defaultVaultReportTrigger( address _vault, address _strategy - ) public view returns (bool, bytes memory) { + ) public view virtual returns (bool, bytes memory) { // Cache the vault instance. IVault vault = IVault(_vault); @@ -404,7 +404,7 @@ contract CommonReportTrigger is Governance { */ function strategyTendTrigger( address _strategy - ) external view returns (bool, bytes memory) { + ) external view virtual returns (bool, bytes memory) { // Return the status of the tend trigger. return IStrategy(_strategy).tendTrigger(); } @@ -414,7 +414,7 @@ contract CommonReportTrigger is Governance { * @dev Will return 0 if a base fee provider is not set. * @return . The current base fee for the chain. */ - function getCurrentBaseFee() public view returns (uint256) { + function getCurrentBaseFee() public view virtual returns (uint256) { address _baseFeeProvider = baseFeeProvider; if (_baseFeeProvider == address(0)) return 0; @@ -431,7 +431,7 @@ contract CommonReportTrigger is Governance { * * @return . IF the current base fee is acceptable. */ - function isCurrentBaseFeeAcceptable() external view returns (bool) { + function isCurrentBaseFeeAcceptable() external view virtual returns (bool) { address _baseFeeProvider = baseFeeProvider; // If no provider is set return true. if (_baseFeeProvider == address(0)) return true; @@ -450,7 +450,7 @@ contract CommonReportTrigger is Governance { */ function setBaseFeeProvider( address _baseFeeProvider - ) external onlyGovernance { + ) external virtual onlyGovernance { baseFeeProvider = _baseFeeProvider; emit NewBaseFeeProvider(_baseFeeProvider); @@ -463,7 +463,7 @@ contract CommonReportTrigger is Governance { */ function setAcceptableBaseFee( uint256 _newAcceptableBaseFee - ) external onlyGovernance { + ) external virtual onlyGovernance { acceptableBaseFee = _newAcceptableBaseFee; emit UpdatedAcceptableBaseFee(_newAcceptableBaseFee); diff --git a/src/swappers/SolidlySwapper.sol b/src/swappers/SolidlySwapper.sol index 7a076b2..754574d 100644 --- a/src/swappers/SolidlySwapper.sol +++ b/src/swappers/SolidlySwapper.sol @@ -43,7 +43,7 @@ contract SolidlySwapper { address _token0, address _token1, bool _stable - ) internal { + ) internal virtual { stable[_token0][_token1] = _stable; stable[_token1][_token0] = _stable; } @@ -66,7 +66,7 @@ contract SolidlySwapper { address _to, uint256 _amountIn, uint256 _minAmountOut - ) internal { + ) internal virtual { if (_amountIn > minAmountToSell) { _checkAllowance(router, _from, _amountIn); @@ -95,7 +95,7 @@ contract SolidlySwapper { address _from, address _to, uint256 _amountIn - ) internal view returns (uint256) { + ) internal view virtual returns (uint256) { uint256[] memory amounts = ISolidly(router).getAmountsOut( _amountIn, _getTokenOutPath(_from, _to) @@ -115,7 +115,7 @@ contract SolidlySwapper { function _getTokenOutPath( address _tokenIn, address _tokenOut - ) internal view returns (ISolidly.route[] memory _path) { + ) internal view virtual returns (ISolidly.route[] memory _path) { bool isBase = _tokenIn == base || _tokenOut == base; _path = new ISolidly.route[](isBase ? 1 : 2); @@ -143,7 +143,7 @@ contract SolidlySwapper { address _contract, address _token, uint256 _amount - ) internal { + ) internal virtual { if (ERC20(_token).allowance(address(this), _contract) < _amount) { ERC20(_token).approve(_contract, 0); ERC20(_token).approve(_contract, _amount); diff --git a/src/swappers/UniswapV2Swapper.sol b/src/swappers/UniswapV2Swapper.sol index 4ef41c6..fe4cfc6 100644 --- a/src/swappers/UniswapV2Swapper.sol +++ b/src/swappers/UniswapV2Swapper.sol @@ -43,7 +43,7 @@ contract UniswapV2Swapper { address _to, uint256 _amountIn, uint256 _minAmountOut - ) internal { + ) internal virtual { if (_amountIn > minAmountToSell) { _checkAllowance(router, _from, _amountIn); @@ -72,7 +72,7 @@ contract UniswapV2Swapper { address _from, address _to, uint256 _amountIn - ) internal view returns (uint256) { + ) internal view virtual returns (uint256) { uint256[] memory amounts = IUniswapV2Router02(router).getAmountsOut( _amountIn, _getTokenOutPath(_from, _to) @@ -92,7 +92,7 @@ contract UniswapV2Swapper { function _getTokenOutPath( address _tokenIn, address _tokenOut - ) internal view returns (address[] memory _path) { + ) internal view virtual returns (address[] memory _path) { bool isBase = _tokenIn == base || _tokenOut == base; _path = new address[](isBase ? 2 : 3); _path[0] = _tokenIn; @@ -117,7 +117,7 @@ contract UniswapV2Swapper { address _contract, address _token, uint256 _amount - ) internal { + ) internal virtual { if (ERC20(_token).allowance(address(this), _contract) < _amount) { ERC20(_token).approve(_contract, 0); ERC20(_token).approve(_contract, _amount); diff --git a/src/swappers/UniswapV3Swapper.sol b/src/swappers/UniswapV3Swapper.sol index fc55239..a31cd1c 100644 --- a/src/swappers/UniswapV3Swapper.sol +++ b/src/swappers/UniswapV3Swapper.sol @@ -44,7 +44,7 @@ contract UniswapV3Swapper { address _token0, address _token1, uint24 _fee - ) internal { + ) internal virtual { uniFees[_token0][_token1] = _fee; uniFees[_token1][_token0] = _fee; } @@ -71,7 +71,7 @@ contract UniswapV3Swapper { address _to, uint256 _amountIn, uint256 _minAmountOut - ) internal returns (uint256 _amountOut) { + ) internal virtual returns (uint256 _amountOut) { if (_amountIn > minAmountToSell) { _checkAllowance(router, _from, _amountIn); if (_from == base || _to == base) { @@ -134,7 +134,7 @@ contract UniswapV3Swapper { address _to, uint256 _amountTo, uint256 _maxAmountFrom - ) internal returns (uint256 _amountIn) { + ) internal virtual returns (uint256 _amountIn) { if (_maxAmountFrom > minAmountToSell) { _checkAllowance(router, _from, _maxAmountFrom); if (_from == base || _to == base) { @@ -185,7 +185,7 @@ contract UniswapV3Swapper { address _contract, address _token, uint256 _amount - ) internal { + ) internal virtual { if (ERC20(_token).allowance(address(this), _contract) < _amount) { ERC20(_token).approve(_contract, 0); ERC20(_token).approve(_contract, _amount); diff --git a/src/utils/Clonable.sol b/src/utils/Clonable.sol index ec23cc3..5aeb436 100644 --- a/src/utils/Clonable.sol +++ b/src/utils/Clonable.sol @@ -9,7 +9,7 @@ contract Clonable { * @notice Clone the contracts default `original` contract. * @return Address of the new Minimal Proxy clone. */ - function _clone() internal returns (address) { + function _clone() internal virtual returns (address) { return _clone(original); } @@ -17,7 +17,9 @@ contract Clonable { * @notice Clone any `_original` contract. * @return _newContract Address of the new Minimal Proxy clone. */ - function _clone(address _original) internal returns (address _newContract) { + function _clone( + address _original + ) internal virtual returns (address _newContract) { // Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol bytes20 addressBytes = bytes20(_original); assembly { diff --git a/src/utils/Governance.sol b/src/utils/Governance.sol index aebe04d..53eaec6 100644 --- a/src/utils/Governance.sol +++ b/src/utils/Governance.sol @@ -32,7 +32,7 @@ contract Governance { */ function transferGovernance( address _newGovernance - ) external onlyGovernance { + ) external virtual onlyGovernance { require(_newGovernance != address(0), "ZERO ADDRESS"); address oldGovernance = governance; governance = _newGovernance; diff --git a/src/utils/Governance2Step.sol b/src/utils/Governance2Step.sol index 729d9d6..017d813 100644 --- a/src/utils/Governance2Step.sol +++ b/src/utils/Governance2Step.sol @@ -1,26 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity 0.8.18; -contract Governance2Step { - event GovernanceTransferred( - address indexed previousGovernance, - address indexed newGovernance - ); +import {Governance} from "./Governance.sol"; +contract Governance2Step is Governance { event UpdatePendingGovernance(address indexed newPendingGovernance); - modifier onlyGovernance() { - _checkGovernance(); - _; - } - - function _checkGovernance() internal view virtual { - require(governance == msg.sender, "!governance"); - } - - // Address that can set the default base fee and provider - address public governance; - // Address that is set to take over governance. address public pendingGovernance; @@ -37,7 +22,7 @@ contract Governance2Step { */ function transferGovernance( address _newGovernance - ) external onlyGovernance { + ) external virtual override onlyGovernance { require(_newGovernance != address(0), "ZERO ADDRESS"); pendingGovernance = _newGovernance; @@ -47,7 +32,7 @@ contract Governance2Step { /** * @notice Allows the `pendingGovernance` to accept the role. */ - function acceptGovernance() external { + function acceptGovernance() external virtual { require(msg.sender == pendingGovernance, "!pending governance"); emit GovernanceTransferred(governance, msg.sender); From 288d6c8adec6dc9856c34c2dae2a8c93537b1794 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 13 Nov 2023 18:18:06 -0700 Subject: [PATCH 4/4] fix: two step gov --- src/utils/Clonable.sol | 2 +- src/utils/Governance.sol | 4 +++- src/utils/Governance2Step.sol | 9 +++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/utils/Clonable.sol b/src/utils/Clonable.sol index 5aeb436..43bd317 100644 --- a/src/utils/Clonable.sol +++ b/src/utils/Clonable.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; contract Clonable { - // Set to the address to auto clone from. + /// @notice Set to the address to auto clone from. address public original; /** diff --git a/src/utils/Governance.sol b/src/utils/Governance.sol index 53eaec6..43078f8 100644 --- a/src/utils/Governance.sol +++ b/src/utils/Governance.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.18; contract Governance { + /// @notice Emitted when the governance address is updated. event GovernanceTransferred( address indexed previousGovernance, address indexed newGovernance @@ -12,11 +13,12 @@ contract Governance { _; } + /// @notice Checks if the msg sender is the governance. function _checkGovernance() internal view virtual { require(governance == msg.sender, "!governance"); } - // Address that can set the default base fee and provider + /// @notice Address that can set the default base fee and provider address public governance; constructor(address _governance) { diff --git a/src/utils/Governance2Step.sol b/src/utils/Governance2Step.sol index 017d813..d29f318 100644 --- a/src/utils/Governance2Step.sol +++ b/src/utils/Governance2Step.sol @@ -4,16 +4,13 @@ pragma solidity 0.8.18; import {Governance} from "./Governance.sol"; contract Governance2Step is Governance { + /// @notice Emitted when the pending governance address is set. event UpdatePendingGovernance(address indexed newPendingGovernance); - // Address that is set to take over governance. + /// @notice Address that is set to take over governance. address public pendingGovernance; - constructor(address _governance) { - governance = _governance; - - emit GovernanceTransferred(address(0), _governance); - } + constructor(address _governance) Governance(_governance) {} /** * @notice Sets a new address as the `pendingGovernance` of the contract.