diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol index e6b1668ca9..3beab28cfd 100644 --- a/contracts/contracts/interfaces/IVault.sol +++ b/contracts/contracts/interfaces/IVault.sol @@ -25,6 +25,11 @@ interface IVault { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); + event AMOStrategyUpdated(address _addr, bool _isAMO); + event MintForStrategyThresholdChanged( + address _strategy, + uint256 _threshold + ); event SwapperChanged(address _address); event SwapAllowedUndervalueChanged(uint256 _basis); event SwapSlippageChanged(address _asset, uint256 _basis); @@ -79,8 +84,6 @@ interface IVault { function trusteeFeeBps() external view returns (uint256); - function ousdMetaStrategy() external view returns (address); - function setSwapper(address _swapperAddr) external; function setSwapAllowedUndervalue(uint16 _percentageBps) external; @@ -189,13 +192,15 @@ interface IVault { function getAllStrategies() external view returns (address[] memory); - function isSupportedAsset(address _asset) external view returns (bool); - - function netOusdMintForStrategyThreshold() external view returns (uint256); + function getStrategyConfig(address _addr) + external + view + returns (VaultStorage.Strategy memory); - function setOusdMetaStrategy(address _ousdMetaStrategy) external; + function isSupportedAsset(address _asset) external view returns (bool); - function setNetOusdMintForStrategyThreshold(uint256 _threshold) external; + function setAMOStrategy(address _address, bool _isAMO) external; - function netOusdMintedForStrategy() external view returns (int256); + function setMintForStrategyThreshold(address _strategy, uint256 _threshold) + external; } diff --git a/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol b/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol index 647c503f5e..0b2a92b835 100644 --- a/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol +++ b/contracts/contracts/mocks/curve/MockCurveAbstractMetapool.sol @@ -10,24 +10,36 @@ import "../../utils/Helpers.sol"; abstract contract MockCurveAbstractMetapool is MintableERC20 { using StableMath for uint256; + address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address[] public coins; uint256[2] public balances; + function get_balances() external view returns (uint256[2] memory) { + return balances; + } + // Returns the same amount of LP tokens in 1e18 decimals function add_liquidity(uint256[2] calldata _amounts, uint256 _minAmount) external + payable returns (uint256 lpAmount) { for (uint256 i = 0; i < _amounts.length; i++) { if (_amounts[i] > 0) { - IERC20(coins[i]).transferFrom( - msg.sender, - address(this), - _amounts[i] - ); - uint256 assetDecimals = Helpers.getDecimals(coins[i]); - // Convert to 1e18 and add to sum - lpAmount += _amounts[i].scaleBy(18, assetDecimals); + if (coins[i] == ETH) { + require(msg.value == _amounts[i], "no ETH sent"); + lpAmount += _amounts[i]; + } else { + IERC20(coins[i]).transferFrom( + msg.sender, + address(this), + _amounts[i] + ); + uint256 assetDecimals = Helpers.getDecimals(coins[i]); + // Convert to 1e18 and add to sum + lpAmount += _amounts[i].scaleBy(18, assetDecimals); + } balances[i] = balances[i] + _amounts[i]; } } @@ -39,13 +51,12 @@ abstract contract MockCurveAbstractMetapool is MintableERC20 { } // Dumb implementation that returns the same amount - function calc_withdraw_one_coin(uint256 _amount, int128 _index) + function calc_withdraw_one_coin(uint256 _burn_amount, int128) public - view - returns (uint256 lpAmount) + pure + returns (uint256 coinAmount) { - uint256 assetDecimals = Helpers.getDecimals(coins[uint128(_index)]); - lpAmount = _amount.scaleBy(assetDecimals, 18); + coinAmount = _burn_amount; } function remove_liquidity_one_coin( @@ -54,12 +65,32 @@ abstract contract MockCurveAbstractMetapool is MintableERC20 { // solhint-disable-next-line no-unused-vars uint256 _minAmount ) external returns (uint256 amount) { + amount = remove_liquidity_one_coin( + _lpAmount, + _index, + _minAmount, + msg.sender + ); + } + + function remove_liquidity_one_coin( + uint256 _lpAmount, + int128 _index, + // solhint-disable-next-line no-unused-vars + uint256 _minAmount, + address _receiver + ) public returns (uint256 amount) { _burn(msg.sender, _lpAmount); uint256[] memory amounts = new uint256[](coins.length); amounts[uint128(_index)] = _lpAmount; amount = calc_withdraw_one_coin(_lpAmount, _index); balances[uint128(_index)] -= amount; - IERC20(coins[uint128(_index)]).transfer(msg.sender, amount); + if (coins[uint128(_index)] == ETH) { + require(address(this).balance >= amount, "not enough ETH"); + payable(_receiver).transfer(amount); + } else { + IERC20(coins[uint128(_index)]).transfer(_receiver, amount); + } } function get_virtual_price() external pure returns (uint256) { @@ -71,15 +102,21 @@ abstract contract MockCurveAbstractMetapool is MintableERC20 { public returns (uint256[2] memory amounts) { - _burn(msg.sender, _amount); uint256 totalSupply = totalSupply(); + if (totalSupply == 0) return amounts; + + _burn(msg.sender, _amount); for (uint256 i = 0; i < 2; i++) { - amounts[i] = totalSupply > 0 - ? (_amount * IERC20(coins[i]).balanceOf(address(this))) / - totalSupply - : IERC20(coins[i]).balanceOf(address(this)); balances[i] -= amounts[i]; - IERC20(coins[i]).transfer(msg.sender, amounts[i]); + if (coins[i] == ETH) { + amounts[i] = (_amount * address(this).balance) / totalSupply; + payable(msg.sender).transfer(amounts[i]); + } else { + amounts[i] = + (_amount * IERC20(coins[i]).balanceOf(address(this))) / + totalSupply; + IERC20(coins[i]).transfer(msg.sender, amounts[i]); + } } } @@ -118,7 +155,11 @@ abstract contract MockCurveAbstractMetapool is MintableERC20 { for (uint256 i = 0; i < _amounts.length; i++) { balances[i] -= _amounts[i]; if (_amounts[i] > 0) { - IERC20(coins[i]).transfer(_reveiver, _amounts[i]); + if (coins[i] == ETH) { + payable(msg.sender).transfer(_amounts[i]); + } else { + IERC20(coins[i]).transfer(_reveiver, _amounts[i]); + } } } } diff --git a/contracts/contracts/mocks/curve/MockCurveFrxEthOethPool.sol b/contracts/contracts/mocks/curve/MockCurveFrxEthOethPool.sol new file mode 100644 index 0000000000..f27f591932 --- /dev/null +++ b/contracts/contracts/mocks/curve/MockCurveFrxEthOethPool.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { MockCurveAbstractMetapool } from "./MockCurveAbstractMetapool.sol"; +import "../MintableERC20.sol"; + +contract MockCurveFrxEthOethPool is MockCurveAbstractMetapool { + constructor(address[2] memory _coins) + ERC20("Curve.fi Factory Plain Pool: frxETH/OETH", "frxETHOETH-f") + { + coins = _coins; + } +} diff --git a/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol b/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol new file mode 100644 index 0000000000..a0b428be98 --- /dev/null +++ b/contracts/contracts/mocks/curve/MockCurveOethEthPool.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { MockCurveAbstractMetapool } from "./MockCurveAbstractMetapool.sol"; +import "../MintableERC20.sol"; + +contract MockCurveOethEthPool is MockCurveAbstractMetapool { + constructor(address[2] memory _coins) + ERC20("Curve.fi Factory Pool: OETH", "OETHCRV-f") + { + coins = _coins; + } + + // TODO need to handle native ETH transfers +} diff --git a/contracts/contracts/mocks/curve/MockCurvePool.sol b/contracts/contracts/mocks/curve/MockCurvePool.sol index f3b6d288e4..093582dc95 100644 --- a/contracts/contracts/mocks/curve/MockCurvePool.sol +++ b/contracts/contracts/mocks/curve/MockCurvePool.sol @@ -30,7 +30,7 @@ contract MockCurvePool { function add_liquidity(uint256[3] calldata _amounts, uint256 _minAmount) external { - uint256 sum = 0; + uint256 lpAmount = 0; for (uint256 i = 0; i < _amounts.length; i++) { if (_amounts[i] > 0) { IERC20(coins[i]).transferFrom( @@ -40,16 +40,16 @@ contract MockCurvePool { ); uint256 assetDecimals = Helpers.getDecimals(coins[i]); // Convert to 1e18 and add to sum - sum += _amounts[i].scaleBy(18, assetDecimals); + lpAmount += _amounts[i].scaleBy(18, assetDecimals); balances[i] = balances[i] + _amounts[i]; } } // Hacky way of simulating slippage to check _minAmount - if (sum == 29000e18) sum = 14500e18; - require(sum >= _minAmount, "Slippage ruined your day"); + if (lpAmount == 29000e18) lpAmount = 14500e18; + require(lpAmount >= _minAmount, "Slippage ruined your day"); // Send LP token to sender, e.g. 3CRV - IMintableERC20(lpToken).mint(sum); - IERC20(lpToken).transfer(msg.sender, sum); + IMintableERC20(lpToken).mint(lpAmount); + IERC20(lpToken).transfer(msg.sender, lpAmount); } // Dumb implementation that returns the same amount @@ -85,9 +85,9 @@ contract MockCurvePool { function remove_liquidity(uint256 _lpAmount, uint256[3] memory _min_amounts) public { + uint256 totalSupply = IERC20(lpToken).totalSupply(); // Burn the Curve LP tokens IBurnableERC20(lpToken).burnFrom(msg.sender, _lpAmount); - uint256 totalSupply = IERC20(lpToken).totalSupply(); for (uint256 i = 0; i < 3; i++) { uint256 coinAmount = totalSupply > 0 ? (_lpAmount * IERC20(coins[i]).balanceOf(address(this))) / diff --git a/contracts/contracts/proxies/Proxies.sol b/contracts/contracts/proxies/Proxies.sol index 327f60d8fa..dc0f09b229 100644 --- a/contracts/contracts/proxies/Proxies.sol +++ b/contracts/contracts/proxies/Proxies.sol @@ -204,3 +204,10 @@ contract ConvexFrxEthWethStrategyProxy is { } + +/** + * @notice ConvexFrxETHAMOStrategyProxy delegates calls to a ConvexFrxETHAMOStrategy implementation + */ +contract ConvexFrxETHAMOStrategyProxy is InitializeGovernedUpgradeabilityProxy { + +} diff --git a/contracts/contracts/strategies/CompoundStrategy.sol b/contracts/contracts/strategies/CompoundStrategy.sol index b195c3b9cc..9516c0244d 100644 --- a/contracts/contracts/strategies/CompoundStrategy.sol +++ b/contracts/contracts/strategies/CompoundStrategy.sol @@ -217,7 +217,7 @@ contract CompoundStrategy is BaseCompoundStrategy { * @notice Approve the spending of all assets by their corresponding cToken, * if for some reason is it necessary. */ - function safeApproveAllTokens() external override { + function safeApproveAllTokens() external override onlyGovernor { uint256 assetCount = assetsMapped.length; for (uint256 i = 0; i < assetCount; ++i) { IERC20 asset = IERC20(assetsMapped[i]); diff --git a/contracts/contracts/strategies/ConvexEthMetaStrategy.sol b/contracts/contracts/strategies/ConvexEthMetaStrategy.sol deleted file mode 100644 index b62a7a4a98..0000000000 --- a/contracts/contracts/strategies/ConvexEthMetaStrategy.sol +++ /dev/null @@ -1,617 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @title Convex Automated Market Maker (AMO) Strategy - * @notice AMO strategy for the Curve OETH/ETH pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { ICurveETHPoolV1 } from "./curve/ICurveETHPoolV1.sol"; -import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { IWETH9 } from "../interfaces/IWETH9.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { IRewardStaking } from "./IRewardStaking.sol"; - -contract ConvexEthMetaStrategy is InitializableAbstractStrategy { - using StableMath for uint256; - using SafeERC20 for IERC20; - - uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI - address public constant ETH_ADDRESS = - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // The following slots have been deprecated with immutable variables - // slither-disable-next-line constable-states - address private _deprecated_cvxDepositorAddress; - // slither-disable-next-line constable-states - address private _deprecated_cvxRewardStaker; - // slither-disable-next-line constable-states - uint256 private _deprecated_cvxDepositorPTokenId; - // slither-disable-next-line constable-states - address private _deprecated_curvePool; - // slither-disable-next-line constable-states - address private _deprecated_lpToken; - // slither-disable-next-line constable-states - address private _deprecated_oeth; - // slither-disable-next-line constable-states - address private _deprecated_weth; - - // Ordered list of pool assets - // slither-disable-next-line constable-states - uint128 private _deprecated_oethCoinIndex; - // slither-disable-next-line constable-states - uint128 private _deprecated_ethCoinIndex; - - // New immutable variables that must be set in the constructor - address public immutable cvxDepositorAddress; - IRewardStaking public immutable cvxRewardStaker; - uint256 public immutable cvxDepositorPTokenId; - ICurveETHPoolV1 public immutable curvePool; - IERC20 public immutable lpToken; - IERC20 public immutable oeth; - IWETH9 public immutable weth; - - // Ordered list of pool assets - uint128 public constant oethCoinIndex = 1; - uint128 public constant ethCoinIndex = 0; - - /** - * @dev Verifies that the caller is the Strategist. - */ - modifier onlyStrategist() { - require( - msg.sender == IVault(vaultAddress).strategistAddr(), - "Caller is not the Strategist" - ); - _; - } - - /** - * @dev Checks the Curve pool's balances have improved and the balances - * have not tipped to the other side. - * This modifier only works on functions that do a single sided add or remove. - * The standard deposit function adds to both sides of the pool in a way that - * the pool's balance is not worsened. - * Withdrawals are proportional so doesn't change the pools asset balance. - */ - modifier improvePoolBalance() { - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balancesBefore = curvePool.get_balances(); - // diff = ETH balance - OETH balance - int256 diffBefore = int256(balancesBefore[ethCoinIndex]) - - int256(balancesBefore[oethCoinIndex]); - - _; - - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balancesAfter = curvePool.get_balances(); - // diff = ETH balance - OETH balance - int256 diffAfter = int256(balancesAfter[ethCoinIndex]) - - int256(balancesAfter[oethCoinIndex]); - - if (diffBefore <= 0) { - // If the pool was originally imbalanced in favor of OETH, then - // we want to check that the pool is now more balanced - require(diffAfter <= 0, "OTokens overshot peg"); - require(diffBefore < diffAfter, "OTokens balance worse"); - } - if (diffBefore >= 0) { - // If the pool was originally imbalanced in favor of ETH, then - // we want to check that the pool is now more balanced - require(diffAfter >= 0, "Assets overshot peg"); - require(diffAfter < diffBefore, "Assets balance worse"); - } - } - - // Used to circumvent the stack too deep issue - struct ConvexEthMetaConfig { - address cvxDepositorAddress; //Address of the Convex depositor(AKA booster) for this pool - address cvxRewardStakerAddress; //Address of the CVX rewards staker - uint256 cvxDepositorPTokenId; //Pid of the pool referred to by Depositor and staker - address oethAddress; //Address of OETH token - address wethAddress; //Address of WETH - } - - constructor( - BaseStrategyConfig memory _baseConfig, - ConvexEthMetaConfig memory _convexConfig - ) InitializableAbstractStrategy(_baseConfig) { - lpToken = IERC20(_baseConfig.platformAddress); - curvePool = ICurveETHPoolV1(_baseConfig.platformAddress); - - cvxDepositorAddress = _convexConfig.cvxDepositorAddress; - cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress); - cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId; - oeth = IERC20(_convexConfig.oethAddress); - weth = IWETH9(_convexConfig.wethAddress); - } - - /** - * Initializer for setting up strategy internal state. This overrides the - * InitializableAbstractStrategy initializer as Curve strategies don't fit - * well within that abstraction. - * @param _rewardTokenAddresses Address of CRV & CVX - * @param _assets Addresses of supported assets. eg WETH - */ - function initialize( - address[] calldata _rewardTokenAddresses, // CRV + CVX - address[] calldata _assets // WETH - ) external onlyGovernor initializer { - require(_assets.length == 1, "Must have exactly one asset"); - require(_assets[0] == address(weth), "Asset not WETH"); - - address[] memory pTokens = new address[](1); - pTokens[0] = address(curvePool); - - InitializableAbstractStrategy._initialize( - _rewardTokenAddresses, - _assets, - pTokens - ); - - _approveBase(); - } - - /*************************************** - Deposit - ****************************************/ - - /** - * @notice Deposit WETH into the Curve pool - * @param _weth Address of Wrapped ETH (WETH) contract. - * @param _amount Amount of WETH to deposit. - */ - function deposit(address _weth, uint256 _amount) - external - override - onlyVault - nonReentrant - { - _deposit(_weth, _amount); - } - - function _deposit(address _weth, uint256 _wethAmount) internal { - require(_wethAmount > 0, "Must deposit something"); - require(_weth == address(weth), "Can only deposit WETH"); - weth.withdraw(_wethAmount); - - emit Deposit(_weth, address(lpToken), _wethAmount); - - // Get the asset and OToken balances in the Curve pool - uint256[2] memory balances = curvePool.get_balances(); - // safe to cast since min value is at least 0 - uint256 oethToAdd = uint256( - _max( - 0, - int256(balances[ethCoinIndex]) + - int256(_wethAmount) - - int256(balances[oethCoinIndex]) - ) - ); - - /* Add so much OETH so that the pool ends up being balanced. And at minimum - * add as much OETH as WETH and at maximum twice as much OETH. - */ - oethToAdd = Math.max(oethToAdd, _wethAmount); - oethToAdd = Math.min(oethToAdd, _wethAmount * 2); - - /* Mint OETH with a strategy that attempts to contribute to stability of OETH/WETH pool. Try - * to mint so much OETH that after deployment of liquidity pool ends up being balanced. - * - * To manage unpredictability minimal OETH minted will always be at least equal or greater - * to WETH amount deployed. And never larger than twice the WETH amount deployed even if - * it would have a further beneficial effect on pool stability. - */ - IVault(vaultAddress).mintForStrategy(oethToAdd); - - emit Deposit(address(oeth), address(lpToken), oethToAdd); - - uint256[2] memory _amounts; - _amounts[ethCoinIndex] = _wethAmount; - _amounts[oethCoinIndex] = oethToAdd; - - uint256 valueInLpTokens = (_wethAmount + oethToAdd).divPrecisely( - curvePool.get_virtual_price() - ); - uint256 minMintAmount = valueInLpTokens.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Do the deposit to the Curve pool - // slither-disable-next-line arbitrary-send - uint256 lpDeposited = curvePool.add_liquidity{ value: _wethAmount }( - _amounts, - minMintAmount - ); - - // Deposit the Curve pool's LP tokens into the Convex rewards pool - require( - IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - lpDeposited, - true // Deposit with staking - ), - "Depositing LP to Convex not successful" - ); - } - - /** - * @notice Deposit the strategy's entire balance of WETH into the Curve pool - */ - function depositAll() external override onlyVault nonReentrant { - uint256 balance = weth.balanceOf(address(this)); - if (balance > 0) { - _deposit(address(weth), balance); - } - } - - /*************************************** - Withdraw - ****************************************/ - - /** - * @notice Withdraw ETH and OETH from the Curve pool, burn the OETH, - * convert the ETH to WETH and transfer to the recipient. - * @param _recipient Address to receive withdrawn asset which is normally the Vault. - * @param _weth Address of the Wrapped ETH (WETH) contract. - * @param _amount Amount of WETH to withdraw. - */ - function withdraw( - address _recipient, - address _weth, - uint256 _amount - ) external override onlyVault nonReentrant { - require(_amount > 0, "Invalid amount"); - require(_weth == address(weth), "Can only withdraw WETH"); - - emit Withdrawal(_weth, address(lpToken), _amount); - - uint256 requiredLpTokens = calcTokenToBurn(_amount); - - _lpWithdraw(requiredLpTokens); - - /* math in requiredLpTokens should correctly calculate the amount of LP to remove - * in that the strategy receives enough WETH on balanced removal - */ - uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)]; - _minWithdrawalAmounts[ethCoinIndex] = _amount; - // slither-disable-next-line unused-return - curvePool.remove_liquidity(requiredLpTokens, _minWithdrawalAmounts); - - // Burn all the removed OETH and any that was left in the strategy - uint256 oethToBurn = oeth.balanceOf(address(this)); - IVault(vaultAddress).burnForStrategy(oethToBurn); - - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - - // Transfer WETH to the recipient - weth.deposit{ value: _amount }(); - require( - weth.transfer(_recipient, _amount), - "Transfer of WETH not successful" - ); - } - - function calcTokenToBurn(uint256 _wethAmount) - internal - view - returns (uint256 lpToBurn) - { - /* The rate between coins in the pool determines the rate at which pool returns - * tokens when doing balanced removal (remove_liquidity call). And by knowing how much WETH - * we want we can determine how much of OETH we receive by removing liquidity. - * - * Because we are doing balanced removal we should be making profit when removing liquidity in a - * pool tilted to either side. - * - * Important: A downside is that the Strategist / Governor needs to be - * cognisant of not removing too much liquidity. And while the proposal to remove liquidity - * is being voted on the pool tilt might change so much that the proposal that has been valid while - * created is no longer valid. - */ - - uint256 poolWETHBalance = curvePool.balances(ethCoinIndex); - /* K is multiplied by 1e36 which is used for higher precision calculation of required - * pool LP tokens. Without it the end value can have rounding errors up to precision of - * 10 digits. This way we move the decimal point by 36 places when doing the calculation - * and again by 36 places when we are done with it. - */ - uint256 k = (1e36 * lpToken.totalSupply()) / poolWETHBalance; - // prettier-ignore - // slither-disable-next-line divide-before-multiply - uint256 diff = (_wethAmount + 1) * k; - lpToBurn = diff / 1e36; - } - - /** - * @notice Remove all ETH and OETH from the Curve pool, burn the OETH, - * convert the ETH to WETH and transfer to the Vault contract. - */ - function withdrawAll() external override onlyVaultOrGovernor nonReentrant { - uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this)); - _lpWithdraw(gaugeTokens); - - // Withdraws are proportional to assets held by 3Pool - uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)]; - - // Remove liquidity - // slither-disable-next-line unused-return - curvePool.remove_liquidity( - lpToken.balanceOf(address(this)), - minWithdrawAmounts - ); - - // Burn all OETH - uint256 oethToBurn = oeth.balanceOf(address(this)); - IVault(vaultAddress).burnForStrategy(oethToBurn); - - // Get the strategy contract's ether balance. - // This includes all that was removed from the Curve pool and - // any ether that was sitting in the strategy contract before the removal. - uint256 ethBalance = address(this).balance; - // Convert all the strategy contract's ether to WETH and transfer to the vault. - weth.deposit{ value: ethBalance }(); - require( - weth.transfer(vaultAddress, ethBalance), - "Transfer of WETH not successful" - ); - - emit Withdrawal(address(weth), address(lpToken), ethBalance); - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - } - - /*************************************** - Curve pool Rebalancing - ****************************************/ - - /** - * @notice Mint OTokens and one-sided add to the Curve pool. - * This is used when the Curve pool does not have enough OTokens and too many ETH. - * The OToken/Asset, eg OETH/ETH, price with increase. - * The amount of assets in the vault is unchanged. - * The total supply of OTokens is increased. - * The asset value of the strategy and vault is increased. - * @param _oTokens The amount of OTokens to be minted and added to the pool. - */ - function mintAndAddOTokens(uint256 _oTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - IVault(vaultAddress).mintForStrategy(_oTokens); - - uint256[2] memory amounts = [uint256(0), uint256(0)]; - amounts[oethCoinIndex] = _oTokens; - - // Convert OETH to Curve pool LP tokens - uint256 valueInLpTokens = (_oTokens).divPrecisely( - curvePool.get_virtual_price() - ); - // Apply slippage to LP tokens - uint256 minMintAmount = valueInLpTokens.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Add the minted OTokens to the Curve pool - uint256 lpDeposited = curvePool.add_liquidity(amounts, minMintAmount); - - // Deposit the Curve pool LP tokens to the Convex rewards pool - require( - IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - lpDeposited, - true // Deposit with staking - ), - "Failed to Deposit LP to Convex" - ); - - emit Deposit(address(oeth), address(lpToken), _oTokens); - } - - /** - * @notice One-sided remove of OTokens from the Curve pool which are then burned. - * This is used when the Curve pool has too many OTokens and not enough ETH. - * The amount of assets in the vault is unchanged. - * The total supply of OTokens is reduced. - * The asset value of the strategy and vault is reduced. - * @param _lpTokens The amount of Curve pool LP tokens to be burned for OTokens. - */ - function removeAndBurnOTokens(uint256 _lpTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - // Withdraw Curve pool LP tokens from Convex and remove OTokens from the Curve pool - uint256 oethToBurn = _withdrawAndRemoveFromPool( - _lpTokens, - oethCoinIndex - ); - - // The vault burns the OTokens from this strategy - IVault(vaultAddress).burnForStrategy(oethToBurn); - - emit Withdrawal(address(oeth), address(lpToken), oethToBurn); - } - - /** - * @notice One-sided remove of ETH from the Curve pool, convert to WETH - * and transfer to the vault. - * This is used when the Curve pool does not have enough OTokens and too many ETH. - * The OToken/Asset, eg OETH/ETH, price with decrease. - * The amount of assets in the vault increases. - * The total supply of OTokens does not change. - * The asset value of the strategy reduces. - * The asset value of the vault should be close to the same. - * @param _lpTokens The amount of Curve pool LP tokens to be burned for ETH. - * @dev Curve pool LP tokens is used rather than WETH assets as Curve does not - * have a way to accurately calculate the amount of LP tokens for a required - * amount of ETH. Curve's `calc_token_amount` functioun does not include fees. - * A 3rd party libary can be used that takes into account the fees, but this - * is a gas intensive process. It's easier for the trusted strategist to - * caclulate the amount of Curve pool LP tokens required off-chain. - */ - function removeOnlyAssets(uint256 _lpTokens) - external - onlyStrategist - nonReentrant - improvePoolBalance - { - // Withdraw Curve pool LP tokens from Convex and remove ETH from the Curve pool - uint256 ethAmount = _withdrawAndRemoveFromPool(_lpTokens, ethCoinIndex); - - // Convert ETH to WETH and transfer to the vault - weth.deposit{ value: ethAmount }(); - require( - weth.transfer(vaultAddress, ethAmount), - "Transfer of WETH not successful" - ); - - emit Withdrawal(address(weth), address(lpToken), ethAmount); - } - - /** - * @dev Remove Curve pool LP tokens from the Convex pool and - * do a one-sided remove of ETH or OETH from the Curve pool. - * @param _lpTokens The amount of Curve pool LP tokens to be removed from the Convex pool. - * @param coinIndex The index of the coin to be removed from the Curve pool. 0 = ETH, 1 = OETH. - * @return coinsRemoved The amount of ETH or OETH removed from the Curve pool. - */ - function _withdrawAndRemoveFromPool(uint256 _lpTokens, uint128 coinIndex) - internal - returns (uint256 coinsRemoved) - { - // Withdraw Curve pool LP tokens from Convex pool - _lpWithdraw(_lpTokens); - - // Convert Curve pool LP tokens to ETH value - uint256 valueInEth = _lpTokens.mulTruncate( - curvePool.get_virtual_price() - ); - // Apply slippage to ETH value - uint256 minAmount = valueInEth.mulTruncate( - uint256(1e18) - MAX_SLIPPAGE - ); - - // Remove just the ETH from the Curve pool - coinsRemoved = curvePool.remove_liquidity_one_coin( - _lpTokens, - int128(coinIndex), - minAmount, - address(this) - ); - } - - /*************************************** - Assets and Rewards - ****************************************/ - - /** - * @notice Collect accumulated CRV and CVX rewards and send to the Harvester. - */ - function collectRewardTokens() - external - override - onlyHarvester - nonReentrant - { - // Collect CRV and CVX - cvxRewardStaker.getReward(); - _collectRewardTokens(); - } - - function _lpWithdraw(uint256 _wethAmount) internal { - // withdraw and unwrap with claim takes back the lpTokens - // and also collects the rewards for deposit - cvxRewardStaker.withdrawAndUnwrap(_wethAmount, true); - } - - /** - * @notice Get the total asset value held in the platform - * @param _asset Address of the asset - * @return balance Total value of the asset in the platform - */ - function checkBalance(address _asset) - public - view - override - returns (uint256 balance) - { - require(_asset == address(weth), "Unsupported asset"); - - // Eth balance needed here for the balance check that happens from vault during depositing. - balance = address(this).balance; - uint256 lpTokens = cvxRewardStaker.balanceOf(address(this)); - if (lpTokens > 0) { - balance += (lpTokens * curvePool.get_virtual_price()) / 1e18; - } - } - - /** - * @notice Returns bool indicating whether asset is supported by strategy - * @param _asset Address of the asset - */ - function supportsAsset(address _asset) public view override returns (bool) { - return _asset == address(weth); - } - - /*************************************** - Approvals - ****************************************/ - - /** - * @notice Approve the spending of all assets by their corresponding pool tokens, - * if for some reason is it necessary. - */ - function safeApproveAllTokens() - external - override - onlyGovernor - nonReentrant - { - _approveBase(); - } - - /** - * @notice Accept unwrapped WETH - */ - receive() external payable {} - - /** - * @dev Since we are unwrapping WETH before depositing it to Curve - * there is no need to to set an approval for WETH on the Curve - * pool - * @param _asset Address of the asset - * @param _pToken Address of the Curve LP token - */ - // solhint-disable-next-line no-unused-vars - function _abstractSetPToken(address _asset, address _pToken) - internal - override - {} - - function _approveBase() internal { - // Approve Curve pool for OETH (required for adding liquidity) - // No approval is needed for ETH - // slither-disable-next-line unused-return - oeth.approve(platformAddress, type(uint256).max); - - // Approve Convex deposit contract to transfer Curve pool LP tokens - // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool - // slither-disable-next-line unused-return - lpToken.approve(cvxDepositorAddress, type(uint256).max); - } - - /** - * @dev Returns the largest of two numbers int256 version - */ - function _max(int256 a, int256 b) internal pure returns (int256) { - return a >= b ? a : b; - } -} diff --git a/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol b/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol deleted file mode 100644 index 3ab141ab0f..0000000000 --- a/contracts/contracts/strategies/ConvexOUSDMetaStrategy.sol +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/** - * @title Curve Convex Strategy - * @notice Investment strategy for investing stablecoins via Curve 3Pool - * @author Origin Protocol Inc - */ -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; - -import { IRewardStaking } from "./IRewardStaking.sol"; -import { IConvexDeposits } from "./IConvexDeposits.sol"; -import { ICurvePool } from "./curve/ICurvePool.sol"; -import { CurveThreeCoinFunctions } from "./curve/CurveThreeCoinFunctions.sol"; -import { CurveFunctions, IERC20, InitializableAbstractStrategy } from "./BaseCurveStrategy.sol"; -import { BaseConvexMetaStrategy, BaseCurveStrategy } from "./BaseConvexMetaStrategy.sol"; -import { StableMath } from "../utils/StableMath.sol"; -import { IVault } from "../interfaces/IVault.sol"; - -contract ConvexOUSDMetaStrategy is - CurveThreeCoinFunctions, - BaseConvexMetaStrategy -{ - using StableMath for uint256; - using SafeERC20 for IERC20; - - constructor( - BaseStrategyConfig memory _stratConfig, - CurveConfig memory _curveConfig - ) - InitializableAbstractStrategy(_stratConfig) - BaseCurveStrategy(_curveConfig) - CurveThreeCoinFunctions(_curveConfig.curvePool) - {} - - function getCurveFunctions() - internal - pure - override(BaseCurveStrategy, CurveThreeCoinFunctions) - returns (CurveFunctions memory) - { - return CurveThreeCoinFunctions.getCurveFunctions(); - } - - /* Take 3pool LP and mint the corresponding amount of ousd. Deposit and stake that to - * ousd Curve Metapool. Take the LP from metapool and deposit them to Convex. - */ - function _lpDepositAll() internal override { - uint256 threePoolLpBalance = IERC20(CURVE_LP_TOKEN).balanceOf( - address(this) - ); - uint256 curve3PoolVirtualPrice = ICurvePool(CURVE_POOL) - .get_virtual_price(); - uint256 threePoolLpDollarValue = threePoolLpBalance.mulTruncate( - curve3PoolVirtualPrice - ); - - // safe to cast since min value is at least 0 - uint256 ousdToAdd = uint256( - _max( - 0, - int256( - metapool.balances(crvCoinIndex).mulTruncate( - curve3PoolVirtualPrice - ) - ) - - int256(metapool.balances(mainCoinIndex)) + - int256(threePoolLpDollarValue) - ) - ); - - /* Add so much OUSD so that the pool ends up being balanced. And at minimum - * add twice as much OUSD as 3poolLP and at maximum at twice as - * much OUSD. - */ - ousdToAdd = Math.max(ousdToAdd, threePoolLpDollarValue); - ousdToAdd = Math.min(ousdToAdd, threePoolLpDollarValue * 2); - - /* Mint OUSD with a strategy that attempts to contribute to stability of OUSD metapool. Try - * to mint so much OUSD that after deployment of liquidity pool ends up being balanced. - * - * To manage unpredictability minimal OUSD minted will always be at least equal or greater - * to stablecoin(DAI, USDC, USDT) amount of 3CRVLP deployed. And never larger than twice the - * stablecoin amount of 3CRVLP deployed even if it would have a further beneficial effect - * on pool stability. - */ - if (ousdToAdd > 0) { - IVault(vaultAddress).mintForStrategy(ousdToAdd); - } - - uint256[2] memory _amounts = [ousdToAdd, threePoolLpBalance]; - - uint256 metapoolVirtualPrice = metapool.get_virtual_price(); - /** - * First convert all the deposited tokens to dollar values, - * then divide by virtual price to convert to metapool LP tokens - * and apply the max slippage - */ - uint256 minReceived = (ousdToAdd + threePoolLpDollarValue) - .divPrecisely(metapoolVirtualPrice) - .mulTruncate(uint256(1e18) - MAX_SLIPPAGE); - - uint256 metapoolLp = metapool.add_liquidity(_amounts, minReceived); - - bool success = IConvexDeposits(cvxDepositorAddress).deposit( - cvxDepositorPTokenId, - metapoolLp, - true // Deposit with staking - ); - - require(success, "Failed to deposit to Convex"); - } - - /** - * Withdraw the specified amount of tokens from the gauge. And use all the resulting tokens - * to remove liquidity from metapool - * @param num3CrvTokens Number of 3CRV tokens to withdraw from metapool - */ - function _lpWithdraw(uint256 num3CrvTokens) internal override { - ICurvePool curvePool = ICurvePool(platformAddress); - /* The rate between coins in the metapool determines the rate at which metapool returns - * tokens when doing balanced removal (remove_liquidity call). And by knowing how much 3crvLp - * we want we can determine how much of OUSD we receive by removing liquidity. - * - * Because we are doing balanced removal we should be making profit when removing liquidity in a - * pool tilted to either side. - * - * Important: A downside is that the Strategist / Governor needs to be - * cognisant of not removing too much liquidity. And while the proposal to remove liquidity - * is being voted on the pool tilt might change so much that the proposal that has been valid while - * created is no longer valid. - */ - - uint256 crvPoolBalance = metapool.balances(crvCoinIndex); - /* K is multiplied by 1e36 which is used for higher precision calculation of required - * metapool LP tokens. Without it the end value can have rounding errors up to precision of - * 10 digits. This way we move the decimal point by 36 places when doing the calculation - * and again by 36 places when we are done with it. - */ - uint256 k = (1e36 * metapoolLPToken.totalSupply()) / crvPoolBalance; - // simplifying below to: `uint256 diff = (num3CrvTokens - 1) * k` causes loss of precision - // prettier-ignore - // slither-disable-next-line divide-before-multiply - uint256 diff = crvPoolBalance * k - - (crvPoolBalance - num3CrvTokens - 1) * k; - uint256 lpToBurn = diff / 1e36; - - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - - require( - lpToBurn <= gaugeTokens, - string( - bytes.concat( - bytes("Attempting to withdraw "), - bytes(Strings.toString(lpToBurn)), - bytes(", metapoolLP but only "), - bytes(Strings.toString(gaugeTokens)), - bytes(" available.") - ) - ) - ); - - // withdraw and unwrap with claim takes back the lpTokens and also collects the rewards for deposit - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - lpToBurn, - true - ); - - // calculate the min amount of OUSD expected for the specified amount of LP tokens - uint256 minOUSDAmount = lpToBurn.mulTruncate( - metapool.get_virtual_price() - ) - - num3CrvTokens.mulTruncate(curvePool.get_virtual_price()) - - 1; - - // withdraw the liquidity from metapool - uint256[2] memory _removedAmounts = metapool.remove_liquidity( - lpToBurn, - [minOUSDAmount, num3CrvTokens] - ); - - IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]); - } - - function _lpWithdrawAll() internal override { - IERC20 metapoolErc20 = IERC20(address(metapool)); - uint256 gaugeTokens = IRewardStaking(cvxRewardStakerAddress).balanceOf( - address(this) - ); - IRewardStaking(cvxRewardStakerAddress).withdrawAndUnwrap( - gaugeTokens, - true - ); - - uint256[2] memory _minAmounts = [uint256(0), uint256(0)]; - uint256[2] memory _removedAmounts = metapool.remove_liquidity( - metapoolErc20.balanceOf(address(this)), - _minAmounts - ); - - IVault(vaultAddress).burnForStrategy(_removedAmounts[mainCoinIndex]); - } -} diff --git a/contracts/contracts/strategies/README.md b/contracts/contracts/strategies/README.md index e96fbd0992..9bc0f17714 100644 --- a/contracts/contracts/strategies/README.md +++ b/contracts/contracts/strategies/README.md @@ -88,9 +88,9 @@ ![Frax ETH Strategy Squashed](../../docs/FraxETHStrategySquashed.svg) - +![Frax ETH Strategy Storage](../../docs/FraxETHStrategyStorage.svg) ## Generalized ERC-4626 Strategy diff --git a/contracts/contracts/strategies/amo/BalancerEthAMOStrategy.sol b/contracts/contracts/strategies/amo/BalancerEthAMOStrategy.sol new file mode 100644 index 0000000000..0353ac3c0f --- /dev/null +++ b/contracts/contracts/strategies/amo/BalancerEthAMOStrategy.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Convex Automated Market Maker (AMO) Strategy + * @notice AMO strategy for the Balancer OETH/WETH pool + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { BaseBalancerAMOStrategy } from "./BaseBalancerAMOStrategy.sol"; +import { IRateProvider } from "../../interfaces/balancer/IRateProvider.sol"; +import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; +import { StableMath } from "../../utils/StableMath.sol"; + +contract BalancerEthAMOStrategy is BaseBalancerAMOStrategy { + using StableMath for uint256; + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + BalancerConfig memory _balancerConfig + ) BaseBalancerAMOStrategy(_baseConfig, _amoConfig, _balancerConfig) {} + + /*************************************** + Vault to Pool Asset Conversions + ****************************************/ + + /// @dev WETH is the Vault asset and the Balancer pool asset so + /// nothing to except return the vault asset amount + function _toPoolAsset(address, uint256 assets) + internal + pure + override + returns (uint256 poolAssets) + { + poolAssets = assets; + } + + function _calcPoolAsset(address, uint256 vaultAssetAmount) + internal + pure + override + returns (uint256 poolAssetAmount) + { + poolAssetAmount = vaultAssetAmount; + } + + /// @dev WETH is the Vault asset and the pool asset so return the WETH amount + /// @param poolAssetAmount Amount of WETH to convert to OETH + /// @param oTokenAmount Amount of OETH converted from WETH + function _toOTokens(uint256 poolAssetAmount) + internal + pure + override + returns (uint256 oTokenAmount) + { + oTokenAmount = poolAssetAmount; + } + + /*************************************** + Curve Pool Withdrawals + ****************************************/ + + /// @dev transfers the specified WETH amount to the recipient + function _withdrawAsset( + address, + uint256 vaultAssetAmount, + address _recipient + ) internal override { + // Transfer the WETH to the Vault + require( + vaultAsset.transfer(_recipient, vaultAssetAmount), + "WETH transfer failed" + ); + + emit Withdrawal( + address(vaultAsset), + address(lpToken), + vaultAssetAmount + ); + } + + /// @dev transfers the WETH balance of this strategy contract to the recipient + function _withdrawAllAsset(address _recipient) internal override { + uint256 vaultAssets = vaultAsset.balanceOf(address(this)); + + _withdrawAsset(address(vaultAsset), vaultAssets, _recipient); + } + + /*************************************** + Asset Balance + ****************************************/ + + /** + * @notice Get strategy's share of an assets in the Balancer pool. + * This is not denominated in OUSD/ETH value of the assets in the Balancer pool. + * @param _asset Address of the Vault asset. eg WETH + * @return balance the amount of vault assets + * + * IMPORTANT if this function is overridden it needs to have a whenNotInBalancerVaultContext + * modifier on it or it is susceptible to read-only re-entrancy attack + * + * @dev it is important that this function is not affected by reporting inflated + * values of assets in case of any pool manipulation. Such a manipulation could easily + * exploit the protocol by: + * - minting OETH + * - tilting Balancer pool to report higher balances of assets + * - rebasing() -> all that extra token balances get distributed to OETH holders + * - tilting pool back + * - redeeming OETH + */ + function checkBalance(address _asset) + public + view + override + returns (uint256 balance) + { + require(_asset == address(vaultAsset), "Unsupported asset"); + + uint256 bptBalance = IERC4626(auraRewardPool).maxRedeem(address(this)); + + if (bptBalance > 0) { + balance = (bptBalance.mulTruncate( + IRateProvider(address(lpToken)).getRate() + ) / 2); + } + } + + /*************************************** + Approvals + ****************************************/ + + /// @dev Is not used + function _abstractSetPToken(address _asset, address _pToken) + internal + override + {} +} diff --git a/contracts/contracts/strategies/amo/BaseAMOStrategy.sol b/contracts/contracts/strategies/amo/BaseAMOStrategy.sol new file mode 100644 index 0000000000..348837e537 --- /dev/null +++ b/contracts/contracts/strategies/amo/BaseAMOStrategy.sol @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Abstract Convex Automated Market Maker (AMO) Strategy + * @notice Investment strategy for investing assets in Curve and Convex pools + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; +import { StableMath } from "../../utils/StableMath.sol"; +import { IVault } from "../../interfaces/IVault.sol"; + +abstract contract BaseAMOStrategy is InitializableAbstractStrategy { + using StableMath for uint256; + + uint256 public constant MAX_SLIPPAGE = 1e16; // 1%, same as the Curve UI + + /// @notice The AMO pool LP token that the strategy invests in + IERC20 public immutable lpToken; + /// @notice The OToken that is used in the AMO pool. eg OETH or OUSD + IERC20 public immutable oToken; + /// @notice The vault asset of this strategy. eg WETH, frxETH or 3CRV + IERC20 public immutable vaultAsset; + /// @notice The token that is used in the AMO pool. eg ETH, frxETH or 3CRV + IERC20 public immutable poolAsset; + + // Index position of oToken in AMO pool. For example + // for OETH/ETH, OETH = 1 + // for OUSD/3CRV, OUSD = 0 + // for frxETH/OUSD, OETH = 1 + uint128 public immutable oTokenCoinIndex; + // Index position of asset in AMO pool. For example + // for OETH/ETH, ETH = 0 + // for OUSD/3CRV, 3CRV = 1 + // for frxETH/OUSD, frxETH = 0 + uint128 public immutable assetCoinIndex; + + /// @notice Validates the vault asset is supported by this strategy. + modifier onlyAsset(address _vaultAsset) { + require(_isVaultAsset(_vaultAsset), "Unsupported asset"); + _; + } + + /// @notice Validates all the vault assets are supported by this strategy. + modifier onlyAssets(address[] memory _vaultAssets) { + require(_isVaultAssets(_vaultAssets), "Unsupported assets"); + _; + } + + /// @dev Verifies that the caller is the Strategist + modifier onlyStrategist() { + require( + msg.sender == IVault(vaultAddress).strategistAddr(), + "Caller is not the Strategist" + ); + _; + } + + /** + * @dev Checks the AMO pool's balances have improved and the balances + * have not tipped to the other side. + */ + modifier improvePoolBalance() { + // Get the asset and OToken balances in the AMO pool + uint256[2] memory balancesBefore = _getBalances(); + // diff = asset balance - OToken balance + int256 diffBefore = int256(balancesBefore[assetCoinIndex]) - + int256(balancesBefore[oTokenCoinIndex]); + + _; + + // Get the asset and OToken balances in the AMO pool + uint256[2] memory balancesAfter = _getBalances(); + // diff = asset balance - OToken balance + int256 diffAfter = int256(balancesAfter[assetCoinIndex]) - + int256(balancesAfter[oTokenCoinIndex]); + + // started with balanced or more OTokens + if (diffBefore <= 0) { + // If the pool was originally imbalanced in favor of the OToken, then + // we want to check that the pool is now more balanced + require(diffAfter <= 0, "OTokens overshot peg"); + require(diffBefore < diffAfter, "OTokens balance worse"); + } + // Started with balanceed or more assets + if (diffBefore >= 0) { + // If the pool was originally imbalanced in favor of the asset, then + // we want to check that the pool is now more balanced + require(diffAfter >= 0, "Assets overshot peg"); + require(diffAfter < diffBefore, "Assets balance worse"); + } + } + + // Used to circumvent the stack too deep issue + struct AMOConfig { + address oTokenAddress; // Address of the OToken. eg OETH or OUSD + address vaultAssetAddress; // Address of the asset token. eg WETH, frxETH or DAU/USDC/USDT + address poolAssetAddress; // Address of the asset token. eg ETH, frxETH or 3CRV + uint128 oTokenCoinIndex; + uint128 assetCoinIndex; + } + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig + ) InitializableAbstractStrategy(_baseConfig) { + // This assumes the AMO pool and LP token have the same address + lpToken = IERC20(_baseConfig.platformAddress); + oToken = IERC20(_amoConfig.oTokenAddress); + vaultAsset = IERC20(_amoConfig.vaultAssetAddress); + poolAsset = IERC20(_amoConfig.poolAssetAddress); + oTokenCoinIndex = _amoConfig.oTokenCoinIndex; + assetCoinIndex = _amoConfig.assetCoinIndex; + } + + /*************************************** + Vault Asset Validation + ****************************************/ + + /// @dev Validates the vault asset is supported by this strategy. + /// The default implementation is the vault asset matches the pool asset. + /// This needs to be overriden for OUSD AMO as the vault assets are DAI, USDC and USDT + /// while the pool asset is 3CRV. + /// @param _vaultAsset Address of the vault asset + function _isVaultAsset(address _vaultAsset) + internal + view + virtual + returns (bool supported) + { + supported = _vaultAsset == address(vaultAsset); + } + + /// @dev Returns bool indicating whether all the assets are supported by this strategy. + /// The default implementation is the vault asset matches the pool asset. + /// This needs to be overriden for OUSD AMO as the vault assets are DAI, USDC and USDT + /// while the pool asset is 3CRV. + /// @param _vaultAssets Addresses of the vault assets + function _isVaultAssets(address[] memory _vaultAssets) + internal + view + virtual + returns (bool) + { + require(_vaultAssets.length == 1, "Only one asset supported"); + return _isVaultAsset(_vaultAssets[0]); + } + + /*************************************** + Vault to Pool Asset Conversions + ****************************************/ + + /// @dev Converts Vault assets to a pool assets. + /// @param vaultAsset The address of the Vault asset to convert. eg WETH, frxETH, DAI + /// @param vaultAssetAmount The amount of vault assets to convert. + /// @return poolAssets The amount of pool assets. eg ETH, frxETH or 3CRV + function _toPoolAsset(address vaultAsset, uint256 vaultAssetAmount) + internal + virtual + returns (uint256 poolAssets); + + /// @dev Calculates the required amount of pool assets to be removed from + /// the pool in order to get the specified amount of vault assets. + /// For example, the amount of 3CRV to remove from the pool to withdraw USDT. + function _calcPoolAsset(address vaultAsset, uint256 vaultAssetAmount) + internal + virtual + returns (uint256 poolAssets); + + /// @dev Convert pool asset amount to an oToken amount. + /// @param poolAssetAmount The amount of pool assets to convert. eg ETH, 3CRV or frxETH + function _toOTokens(uint256 poolAssetAmount) + internal + virtual + returns (uint256 oTokenAmount); + + /// @dev Returns the index position of the coin in the AMO pool. + function _getCoinIndex(address _asset) internal view returns (uint128) { + if (_asset == address(oToken)) { + return oTokenCoinIndex; + } else if (_asset == address(poolAsset)) { + return assetCoinIndex; + } else { + revert("Unsupported asset"); + } + } + + /*************************************** + AMO Pool Deposits + ****************************************/ + + /// @dev Adds pool assets and/or OTokens to the AMO pool + /// @param poolAmounts The amount of AMO pool assets and OTokens to add to the pool + /// @param minLpAmount The minimum amount of AMO pool LP tokens that is acceptable to receive + function _addLiquidityToPool( + uint256[2] memory poolAmounts, + uint256 minLpAmount + ) internal virtual returns (uint256 lpDeposited); + + /// @dev Removes pool assets and/or OTokens from the AMO pool. + /// Curve will burn an exact amount of AMO LP tokens in exchange for a calculated amount of pool assets. + /// Balancer will withdraw and exact amount of pool assets in exchange for a calculated amount of AMO LP tokens. + /// @param lpTokens The maximum amount of AMO pool LP tokens to be burnt + /// @param poolAssetAmounts The minimum amount of AMO pool assets that are acceptable to receive + function _removeLiquidityFromPool( + uint256 lpTokens, + uint256[2] memory poolAssetAmounts + ) internal virtual; + + /// @dev Removes either pool assets or OTokens from the AMO pool. + /// Curve will burn an exact amount of AMO LP tokens in exchange for a calculated amount of pool assets. + /// Balancer will withdraw and exact amount of pool assets in exchange for a calculated amount of AMO LP tokens. + /// @param poolAsset The address of the AMO pool asset to be removed. eg OETH, OUSD, ETH, 3CRV, frxETH + /// @param lpTokens The maximum amount of AMO pool LP tokens to be burnt + /// @param poolAssetAmount The minimum amount of AMO pool assets that are acceptable to receive. eg OETH or ETH + function _removeOneSidedLiquidityFromPool( + address poolAsset, + uint256 lpTokens, + uint256 poolAssetAmount + ) internal virtual returns (uint256 coinsRemoved); + + /// @dev Returns the current balances of the AMO pool + function _getBalances() + internal + view + virtual + returns (uint256[2] memory balances); + + /// @dev Returns the current balances of the AMO pool + function _getBalance(address poolAsset) + internal + view + virtual + returns (uint256 balance); + + /// @dev Returns the price of one AMO pool LP token in base asset terms. + function _getVirtualPrice() + internal + view + virtual + returns (uint256 virtualPrice); + + /*************************************** + AMO Pool Withdrawals + ****************************************/ + + /// @dev Converts all the pool assets in this strategy to a single Vault asset + /// and transfers them to the Vault. + /// For OUSD, this converts 3CRV to either DAI, USDC or USDT. + function _withdrawAsset( + address vaultAsset, + uint256 vaultAssetAmount, + address recipient + ) internal virtual; + + /// @dev Converts all the pool assets in this strategy to the Vault assets + /// and transfers them to the Vault. + /// For OUSD, this converts 3CRV to DAI, USDC and USDT. + function _withdrawAllAsset(address recipient) internal virtual; + + /*************************************** + Convex Reward Pool + ****************************************/ + + /// @dev Deposit the AMO pool LP tokens to the rewards pool. + /// eg Curve LP tokens into Convex or Balancer LP tokens into Aura + /// @param lpAmount the amount of AMO pool LP tokens to deposit + function _stakeCurveLp(uint256 lpAmount) internal virtual; + + /// @dev Withdraw a specific amount of AMO pool LP tokens from the rewards pool + /// eg Curve LP tokens from Convex or Balancer LP tokens from Aura + /// @param lpAmount the amount of AMO pool LP tokens to withdraw + function _unStakeLpTokens(uint256 lpAmount) internal virtual; + + /// @dev Withdraw all AMO pool LP tokens from the rewards pool + function _unStakeAllLpTokens() internal virtual; + + /*************************************** + Deposit + ****************************************/ + + /** + * @notice Deposit a vault asset into the AMO strategy. + * @param _vaultAsset Address of the vault asset token. eg WETH, frxETH, DAI, USDC or USDT + * @param _vaultAssetAmount Amount of vault asset tokens to deposit. + */ + function deposit(address _vaultAsset, uint256 _vaultAssetAmount) + external + override + onlyVault + onlyAsset(_vaultAsset) + nonReentrant + { + emit Deposit(_vaultAsset, address(lpToken), _vaultAssetAmount); + uint256 poolAssetAmount = _toPoolAsset(_vaultAsset, _vaultAssetAmount); + _deposit(poolAssetAmount); + } + + /** + * @notice Deposit multiple vault assets into the AMO strategy. + * @param _vaultAssets Addresses of the vault asset tokens. eg WETH, frxETH, DAI, USDC or USDT + * @param _vaultAssetAmounts Amounts of vault asset tokens to deposit. + * @dev Only the OUSD Curve AMO supports depositing multiple assets in DAI, USDC and USDT. + * The default implementation only supports a single asset and must be overriden for the OUSD AMO. + */ + function deposit( + address[] memory _vaultAssets, + uint256[] memory _vaultAssetAmounts + ) external virtual onlyVault onlyAssets(_vaultAssets) nonReentrant { + // validate the number of assets matches the number of amounts + // The onlyAssets modified ensures the correct number of assets are supported. + // Most AMOs will be just one asset but for OUSD's 3CRV AMO it will be 3 assets. + require( + _vaultAssets.length == _vaultAssetAmounts.length, + "Assets and amounts mismatch" + ); + uint256 poolAssetAmount = _toPoolAsset( + _vaultAssets[0], + _vaultAssetAmounts[0] + ); + _deposit(poolAssetAmount); + } + + function _deposit(uint256 _poolAssetAmount) internal { + require(_poolAssetAmount > 0, "Must deposit something"); + + // Get the asset and OToken balances in the AMO pool + uint256[2] memory balances = _getBalances(); + + // Add the old balance with the new deposit amount + // and then convert to the pool asset value. + // For example, convert 3CRV to USD value + uint256 newAssetsValueInOTokens = _toOTokens( + balances[assetCoinIndex] + _poolAssetAmount + ); + + // Calculate the amount of OTokens to add to the pool + // safe to cast since min value is at least 0 + uint256 oTokensToAdd = uint256( + _max( + 0, + int256(newAssetsValueInOTokens) - + int256(balances[oTokenCoinIndex]) + ) + ); + + /* Add so much OTokens so that the pool ends up being balanced. And at minimum + * add as much OTokens as asset and at maximum twice as much OTokens. + */ + oTokensToAdd = Math.max(oTokensToAdd, _poolAssetAmount); + oTokensToAdd = Math.min(oTokensToAdd, _poolAssetAmount * 2); + + /* Mint OTokens with a strategy that attempts to contribute to stability of pool. Try + * to mint so much OTokens that after deployment of liquidity pool ends up being balanced. + * + * To manage unpredictability minimal OTokens minted will always be at least equal or greater + * to the asset amount deployed. And never larger than twice the asset amount deployed even if + * it would have a further beneficial effect on pool stability. + */ + IVault(vaultAddress).mintForStrategy(oTokensToAdd); + + emit Deposit(address(oToken), address(lpToken), oTokensToAdd); + + uint256[2] memory _amounts; + _amounts[assetCoinIndex] = _poolAssetAmount; + _amounts[oTokenCoinIndex] = oTokensToAdd; + + uint256 valueInLpTokens = (_poolAssetAmount + oTokensToAdd) + .divPrecisely(_getVirtualPrice()); + uint256 minMintAmount = valueInLpTokens.mulTruncate( + uint256(1e18) - MAX_SLIPPAGE + ); + + // Deposit to the AMO pool and receive LP tokens. eg OUSD/3CRV-f or OETH/ETH-f + uint256 lpDeposited = _addLiquidityToPool(_amounts, minMintAmount); + + // Stake the AMO pool LP tokens for rewards + _stakeCurveLp(lpDeposited); + } + + /** + * @notice Deposit the strategy's entire balance of assets into the AMO pool + */ + function depositAll() external virtual override onlyVault nonReentrant { + uint256 vaultAssetBalance = vaultAsset.balanceOf(address(this)); + + emit Deposit(address(vaultAsset), address(lpToken), vaultAssetBalance); + + uint256 poolAssetAmount = _toPoolAsset( + address(vaultAsset), + vaultAssetBalance + ); + if (vaultAssetBalance > 0) { + _deposit(poolAssetAmount); + } + } + + /*************************************** + Withdraw + ****************************************/ + + /** + * @notice Withdraw pool asset and OToken from the AMO pool, burn the OTokens, + * convert pool asset to vault asset and transfer the vault asset to the recipient. + * @param _recipient Address to receive withdrawn asset which is normally the vault or redeemer. + * @param _vaultAsset Address of the vault asset token. eg WETH, frxETH, DAI, USDC or USDT + * @param _vaultAssetAmount Amount of vault asset tokens to withdraw. + */ + function withdraw( + address _recipient, + address _vaultAsset, + uint256 _vaultAssetAmount + ) external override onlyVault onlyAsset(_vaultAsset) nonReentrant { + _withdraw(_recipient, _vaultAsset, _vaultAssetAmount); + } + + /** + * @notice Withdraw pool asset and OToken from the AMO pool, burn the OTokens, + * convert pool asset to vault assets and transfer the vault assets to the recipient. + * @dev Only the OUSD Curve AMO supports withdrawing multiple assets in DAI, USDC and USDT. + * The default implementation only supports a single asset and must be overriden for the OUSD AMO. + * @param _recipient Address to receive withdrawn asset which is normally the vault or redeemer. + * @param _vaultAssets Addresses of the vault asset tokens. eg WETH, frxETH, DAI, USDC or USDT + * @param _vaultAssetAmounts Amounts of vault asset tokens to withdraw. + */ + function withdraw( + address _recipient, + address[] memory _vaultAssets, + uint256[] memory _vaultAssetAmounts + ) external virtual onlyVault onlyAssets(_vaultAssets) nonReentrant { + // validate the number of assets matches the number of amounts + // The onlyAssets modified ensures the correct number of assets are supported. + // Most AMOs will be just one asset but for OUSD's 3CRV AMO it will be 3 assets. + require( + _vaultAssets.length == _vaultAssetAmounts.length, + "Assets and amounts mismatch" + ); + _withdraw(_recipient, _vaultAssets[0], _vaultAssetAmounts[0]); + } + + function _withdraw( + address _recipient, + address _vaultAsset, + uint256 _vaultAssetAmount + ) internal { + require(_vaultAssetAmount > 0, "Must withdraw something"); + + // Calc required number of pool assets for specified number of vault assets + uint256 poolAssetAmount = _calcPoolAsset( + _vaultAsset, + _vaultAssetAmount + ); + + uint256 requiredLpTokens = calcLpTokensToBurn(poolAssetAmount); + + // Withdraw AMO pool LP tokens from the rewards pool + _unStakeLpTokens(requiredLpTokens); + + /* math in calcLpTokensToBurn should correctly calculate the amount of Curve LP tokens + * to burn so the strategy receives enough asset tokens on balanced removal + */ + uint256[2] memory _minWithdrawalAmounts = [uint256(0), uint256(0)]; + _minWithdrawalAmounts[assetCoinIndex] = poolAssetAmount; + _removeLiquidityFromPool(requiredLpTokens, _minWithdrawalAmounts); + + // Burn all the removed OTokens and any that was left in the strategy + uint256 oTokenToBurn = oToken.balanceOf(address(this)); + if (oTokenToBurn > 0) { + IVault(vaultAddress).burnForStrategy(oTokenToBurn); + + emit Withdrawal(address(oToken), address(lpToken), oTokenToBurn); + } + + // Convert the pool assets in this strategy to the Vault asset + // and transfer them to the vault. + // Also emits the Withdraw event. + _withdrawAsset(_vaultAsset, _vaultAssetAmount, _recipient); + } + + function calcLpTokensToBurn(uint256 _vaultAssetAmount) + internal + view + returns (uint256 lpToBurn) + { + /* The rate between coins in the pool determines the rate at which pool returns + * tokens when doing balanced removal (remove_liquidity call). And by knowing how much assets + * we want we can determine how much of OToken we receive by removing liquidity. + * + * Because we are doing balanced removal we should be making profit when removing liquidity in a + * pool tilted to either side. + * + * Important: A downside is that the Strategist / Governor needs to be + * cognisant of not removing too much liquidity. And while the proposal to remove liquidity + * is being voted on the pool tilt might change so much that the proposal that has been valid while + * created is no longer valid. + */ + + uint256 poolAssetBalance = _getBalance(address(poolAsset)); + /* K is multiplied by 1e36 which is used for higher precision calculation of required + * pool LP tokens. Without it the end value can have rounding errors up to precision of + * 10 digits. This way we move the decimal point by 36 places when doing the calculation + * and again by 36 places when we are done with it. + */ + uint256 k = (1e36 * lpToken.totalSupply()) / poolAssetBalance; + // prettier-ignore + // slither-disable-next-line divide-before-multiply + uint256 diff = (_vaultAssetAmount + 1) * k; + lpToBurn = diff / 1e36; + } + + /** + * @notice Remove all assets and OTokens from the pool, burn the OTokens, + * transfer the assets to the Vault contract. + */ + function withdrawAll() external override onlyVaultOrGovernor nonReentrant { + _unStakeAllLpTokens(); + + // Withdraws are proportional to assets held by the Curve pool + uint256[2] memory minWithdrawAmounts = [uint256(0), uint256(0)]; + + // Only withdraw from pool if the strategy has assets in the pool + uint256 poolLpTokens = lpToken.balanceOf(address(this)); + if (poolLpTokens > 0) { + // Remove liquidity from the pool + _removeLiquidityFromPool(poolLpTokens, minWithdrawAmounts); + + // Burn all the OTokens. eg OETH or OUSD + uint256 oTokenToBurn = oToken.balanceOf(address(this)); + IVault(vaultAddress).burnForStrategy(oTokenToBurn); + + emit Withdrawal(address(oToken), address(lpToken), oTokenToBurn); + } + + // Convert all the pool assets in this strategy to Vault assets + // and transfer them to the vault. + // Also emits the Withdraw events for each vault asset. + _withdrawAllAsset(vaultAddress); + } + + /*************************************** + AMO Pool Rebalancing + ****************************************/ + + /** + * @notice Mint OTokens and one-sided add to the AMO pool. + * This is used when the AMO pool does not have enough OTokens and too many ETH. + * The OToken/Asset, eg OETH/ETH, price with increase. + * The amount of assets in the vault is unchanged. + * The total supply of OTokens is increased. + * The asset value of the strategy and vault is increased. + * @param _oTokens The amount of OTokens to be minted and added to the pool. + */ + function mintAndAddOTokens(uint256 _oTokens) + external + onlyStrategist + nonReentrant + improvePoolBalance + { + IVault(vaultAddress).mintForStrategy(_oTokens); + + uint256[2] memory _amounts; + _amounts[assetCoinIndex] = 0; + _amounts[oTokenCoinIndex] = _oTokens; + + // Convert OTokens, eg OETH, to AMO pool LP tokens + uint256 valueInLpTokens = (_oTokens).divPrecisely(_getVirtualPrice()); + // Apply slippage to LP tokens + uint256 minMintAmount = valueInLpTokens.mulTruncate( + uint256(1e18) - MAX_SLIPPAGE + ); + + // Add the minted OTokens to the AMO pool + uint256 lpDeposited = _addLiquidityToPool(_amounts, minMintAmount); + + // Deposit the AMO pool LP tokens to the rewards pool. eg Convex or Aura + _stakeCurveLp(lpDeposited); + + emit Deposit(address(oToken), address(lpToken), _oTokens); + } + + /** + * @notice One-sided remove of OTokens from the AMO pool which are then burned. + * This is used when the AMO pool has too many OTokens and not enough ETH. + * The amount of assets in the vault is unchanged. + * The total supply of OTokens is reduced. + * The asset value of the strategy and vault is reduced. + * @param _lpTokens The amount of AMO pool LP tokens to be burned for OTokens. + */ + function removeAndBurnOTokens(uint256 _lpTokens) + external + onlyStrategist + nonReentrant + improvePoolBalance + { + // Withdraw AMO pool LP tokens from the rewards pool and remove OTokens from the AMO pool + uint256 oTokenToBurn = _withdrawAndRemoveFromPool( + _lpTokens, + address(oToken) + ); + + // The vault burns the OTokens from this strategy + IVault(vaultAddress).burnForStrategy(oTokenToBurn); + + emit Withdrawal(address(oToken), address(lpToken), oTokenToBurn); + } + + /** + * @notice One-sided remove of assets, eg ETH, from the AMO pool, + * convert to the asset and transfer to the vault. + * This is used when the AMO pool does not have enough OTokens and too many assets. + * The OToken/Asset, eg OETH/ETH, price with decrease. + * The amount of assets in the vault increases. + * The total supply of OTokens does not change. + * The asset value of the strategy reduces. + * The asset value of the vault should be close to the same. + * @param _lpTokens The amount of AMO pool LP tokens to be burned for pool assets. + * @dev Curve pool LP tokens is used rather than assets as Curve does not + * have a way to accurately calculate the amount of LP tokens for a required + * amount of assets. Curve's `calc_token_amount` functioun does not include fees. + * A 3rd party libary can be used that takes into account the fees, but this + * is a gas intensive process. It's easier for the trusted strategist to + * caclulate the amount of AMO pool LP tokens required off-chain. + */ + function removeOnlyAssets(uint256 _lpTokens) + external + onlyStrategist + nonReentrant + improvePoolBalance + { + // Withdraw AMO pool LP tokens from rewards pool and remove asset from the AMO pool + _withdrawAndRemoveFromPool(_lpTokens, address(poolAsset)); + + // Convert all the pool assets in this strategy to Vault assets + // and transfer them to the vault + _withdrawAllAsset(vaultAddress); + } + + /** + * @dev Remove AMO pool LP tokens from the rewards pool and + * do a one-sided remove of pool assets or OTokens from the AMO pool. + * @param _lpTokens The amount of AMO pool LP tokens to be removed from the Convex pool. + * @param removeAsset The address of the AMO pool asset or OTokens to be removed. + * @return coinsRemoved The amount of assets or OTokens removed from the AMO pool. + */ + function _withdrawAndRemoveFromPool(uint256 _lpTokens, address removeAsset) + internal + returns (uint256 coinsRemoved) + { + // Withdraw AMO pool LP tokens from rewards pool + _unStakeLpTokens(_lpTokens); + + // Convert AMO pool LP tokens to asset value + uint256 valueInEth = _lpTokens.mulTruncate(_getVirtualPrice()); + // Apply slippage to asset value + uint256 minAmount = valueInEth.mulTruncate( + uint256(1e18) - MAX_SLIPPAGE + ); + + coinsRemoved = _removeOneSidedLiquidityFromPool( + removeAsset, + _lpTokens, + minAmount + ); + } + + /*************************************** + Assets + ****************************************/ + + /** + * @notice Returns bool indicating whether asset is supported by this strategy + * @param _vaultAsset Address of the vault asset + */ + function supportsAsset(address _vaultAsset) + public + view + override + returns (bool) + { + return _isVaultAsset(_vaultAsset); + } + + /** + * @notice Approve the spending of all assets by their corresponding pool tokens, + * if for some reason is it necessary. + */ + function safeApproveAllTokens() + external + virtual + override + onlyGovernor + nonReentrant + { + _approveBase(); + } + + function _approveBase() internal virtual {} + + /*************************************** + Utils + ****************************************/ + + /** + * @dev Returns the largest of two numbers int256 version + */ + function _max(int256 a, int256 b) internal pure returns (int256) { + return a >= b ? a : b; + } +} diff --git a/contracts/contracts/strategies/amo/BaseBalancerAMOStrategy.sol b/contracts/contracts/strategies/amo/BaseBalancerAMOStrategy.sol new file mode 100644 index 0000000000..2fe1cddd75 --- /dev/null +++ b/contracts/contracts/strategies/amo/BaseBalancerAMOStrategy.sol @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Abstract Aura Automated Market Maker (AMO) Strategy + * @notice Investment strategy for investing assets in Balancer and Aura pools + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { BaseAMOStrategy, InitializableAbstractStrategy } from "./BaseAMOStrategy.sol"; +import { IConvexDeposits } from "../IConvexDeposits.sol"; +import { IRewardStaking } from "../IRewardStaking.sol"; +import { IBalancerVault } from "../../interfaces/balancer/IBalancerVault.sol"; +import { IRateProvider } from "../../interfaces/balancer/IRateProvider.sol"; +import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol"; + +abstract contract BaseBalancerAMOStrategy is BaseAMOStrategy { + /// @notice Address of the Balancer vault + IBalancerVault public immutable balancerVault; + /// @notice Balancer pool identifier + bytes32 public immutable balancerPoolId; + /// @notice Address of the Aura rewards pool + address public immutable auraRewardPool; + + struct BalancerConfig { + address balancerVault; // Address of the Balancer vault + bytes32 balancerPoolId; // Balancer pool identifier + address auraRewardPool; // Address of the Aura rewards pool + } + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + BalancerConfig memory _balancerConfig + ) BaseAMOStrategy(_baseConfig, _amoConfig) { + balancerVault = IBalancerVault(_balancerConfig.balancerVault); + balancerPoolId = _balancerConfig.balancerPoolId; + auraRewardPool = _balancerConfig.auraRewardPool; + } + + /** + * Initializer for setting up strategy internal state. This overrides the + * InitializableAbstractStrategy initializer as Curve strategies don't fit + * well within that abstraction. + * @param _rewardTokenAddresses Addresses of BAL & AURA tokens + */ + function initialize(address[] calldata _rewardTokenAddresses) + external + onlyGovernor + initializer + { + address[] memory assets = new address[](1); + assets[0] = address(vaultAsset); + // pTokens are not used by this strategy + // it is only included for backward compatibility with the + // parent InitializableAbstractStrategy contract + address[] memory pTokens = new address[](1); + pTokens[0] = address(balancerVault); + + InitializableAbstractStrategy._initialize( + _rewardTokenAddresses, + assets, + pTokens + ); + + _approveBase(); + } + + /*************************************** + Balancer Pool + ****************************************/ + + /// @dev Adds ETH and/or OETH to the Curve pool + /// @param poolAmounts The amount of Balancer pool assets and OTokens to add to the pool + function _addLiquidityToPool( + uint256[2] memory poolAmounts, + uint256 minMintAmount + ) internal override returns (uint256 lpDeposited) { + // TODO do we need to check if the tokens in the pool have changed? + // slither-disable-next-line unused-return + (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( + balancerPoolId + ); + require(tokens[oTokenCoinIndex] == oToken, "Invalid Balancer oToken"); + require(tokens[assetCoinIndex] == poolAsset, "Invalid Balancer asset"); + + uint256[] memory amountsIn = new uint256[](tokens.length); + amountsIn[oTokenCoinIndex] = poolAmounts[oTokenCoinIndex]; + amountsIn[assetCoinIndex] = poolAmounts[assetCoinIndex]; + + address[] memory poolAssets = new address[](tokens.length); + poolAssets[oTokenCoinIndex] = address(oToken); + poolAssets[assetCoinIndex] = address(poolAsset); + + /* EXACT_TOKENS_IN_FOR_BPT_OUT: + * User sends precise quantities of tokens, and receives an + * estimated but unknown (computed at run time) quantity of BPT. + * + * ['uint256', 'uint256[]', 'uint256'] + * [EXACT_TOKENS_IN_FOR_BPT_OUT, amountsIn, minimumBPT] + */ + bytes memory userData = abi.encode( + IBalancerVault.WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, + amountsIn, + minMintAmount + ); + + IBalancerVault.JoinPoolRequest memory request = IBalancerVault + .JoinPoolRequest(poolAssets, amountsIn, userData, false); + + // Add the pool assets in this strategy to the balancer pool + balancerVault.joinPool( + balancerPoolId, + address(this), + address(this), + request + ); + + lpDeposited = lpToken.balanceOf(address(this)); + } + + /// @dev Removes pool assets and/or OTokens from the Balancer pool. + /// Balancer will withdraw and exact amount of pool assets in exchange for a calculated amount of AMO LP tokens. + /// @param lpTokens The maximum amount of AMO pool LP tokens to be burnt. + /// @param poolAssetAmounts The exact amount of AMO pool assets to be removed. + function _removeLiquidityFromPool( + uint256 lpTokens, + uint256[2] memory poolAssetAmounts + ) internal override { + uint256[] memory poolAssetsAmountsOut = new uint256[](2); + poolAssetsAmountsOut[oTokenCoinIndex] = poolAssetAmounts[ + oTokenCoinIndex + ]; + poolAssetsAmountsOut[assetCoinIndex] = poolAssetAmounts[assetCoinIndex]; + + _removeBalancerLiquidity(lpTokens, poolAssetsAmountsOut); + } + + /// @dev Removes either pool assets or OTokens from the Balancer pool. + /// Balancer will withdraw and exact amount of pool assets in exchange for a calculated amount of AMO LP tokens. + /// @param poolAsset The address of the AMO pool asset to be removed. eg OETH or WETH + /// @param lpTokens The maximum amount of AMO pool LP tokens to be burnt. + /// @param poolAssetAmount The exact amount of AMO pool assets to be removed. + function _removeOneSidedLiquidityFromPool( + address poolAsset, + uint256 lpTokens, + uint256 poolAssetAmount + ) internal override returns (uint256 coinsRemoved) { + uint128 coinIndex = _getCoinIndex(poolAsset); + uint256[] memory poolAssetsAmountsOut = new uint256[](2); + poolAssetsAmountsOut[coinIndex] = poolAssetAmount; + + _removeBalancerLiquidity(lpTokens, poolAssetsAmountsOut); + + // As we are withdrawing and exact amount of pool assets, the amount received + // is the same as the amount requested. + coinsRemoved = poolAssetAmount; + } + + function _removeBalancerLiquidity( + uint256 lpTokens, + uint256[] memory poolAssetsAmountsOut + ) internal { + // TODO do we need to check if the tokens in the pool have changed? + // slither-disable-next-line unused-return + (IERC20[] memory tokens, , ) = balancerVault.getPoolTokens( + balancerPoolId + ); + require(tokens[oTokenCoinIndex] == oToken, "Invalid Balancer oToken"); + require(tokens[assetCoinIndex] == poolAsset, "Invalid Balancer asset"); + + address[] memory poolAssets = new address[](tokens.length); + poolAssets[oTokenCoinIndex] = address(oToken); + poolAssets[assetCoinIndex] = address(poolAsset); + + /* Custom asset exit: BPT_IN_FOR_EXACT_TOKENS_OUT: + * User sends an estimated but unknown (computed at run time) quantity of BPT, + * and receives precise quantities of specified tokens. + * + * ['uint256', 'uint256[]', 'uint256'] + * [BPT_IN_FOR_EXACT_TOKENS_OUT, amountsOut, maxBPTAmountIn] + */ + bytes memory userData = abi.encode( + IBalancerVault.WeightedPoolExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT, + poolAssetsAmountsOut, + lpTokens + ); + IBalancerVault.ExitPoolRequest memory request = IBalancerVault + .ExitPoolRequest( + poolAssets, + /* We specify the exact amount of a tokens we are expecting in the encoded + * userData, for that reason we don't need to specify the amountsOut here. + * + * Also Balancer has a rounding issue that can make a transaction fail: + * https://github.com/balancer/balancer-v2-monorepo/issues/2541 + * which is an extra reason why this field is empty. + */ + new uint256[](tokens.length), + userData, + false + ); + balancerVault.exitPool( + balancerPoolId, + address(this), + /* Payable keyword is required because of the IBalancerVault interface even though + * this strategy shall never be receiving native ETH + */ + payable(address(this)), + request + ); + } + + /// @dev Returns the current balances of the AMO pool + function _getBalances() + internal + view + override + returns (uint256[2] memory balances) + { + // Get all the supported Balancer pool assets and balances + // slither-disable-next-line unused-return + (IERC20[] memory tokens, uint256[] memory allBalances, ) = balancerVault + .getPoolTokens(balancerPoolId); + require(tokens[oTokenCoinIndex] == oToken, "Invalid Balancer oToken"); + require(tokens[assetCoinIndex] == poolAsset, "Invalid Balancer asset"); + + balances[oTokenCoinIndex] = allBalances[oTokenCoinIndex]; + balances[assetCoinIndex] = allBalances[assetCoinIndex]; + } + + /// @dev Returns the current balances of the AMO pool + function _getBalance(address poolAsset) + internal + view + override + returns (uint256 balance) + { + uint128 coinIndex = _getCoinIndex(poolAsset); + + // Get all the supported Balancer pool assets and balances + // slither-disable-next-line unused-return + (IERC20[] memory tokens, uint256[] memory allBalances, ) = balancerVault + .getPoolTokens(balancerPoolId); + require( + address(tokens[coinIndex]) == address(poolAsset), + "Invalid Balancer index" + ); + + balance = allBalances[coinIndex]; + } + + /// @dev Returns the price of one AMO pool LP token in base asset terms. + function _getVirtualPrice() + internal + view + override + returns (uint256 virtualPrice) + { + virtualPrice = IRateProvider(address(lpToken)).getRate(); + } + + /*************************************** + Aura Reward Pool + ****************************************/ + + /// @dev Deposit the AMO pool LP tokens to the rewards pool. + /// eg Curve LP tokens into Convex or Balancer LP tokens into Aura + function _stakeCurveLp(uint256 lpAmount) internal override { + uint256 lpDeposited = IERC4626(auraRewardPool).deposit( + lpAmount, + address(this) + ); + require(lpAmount == lpDeposited, "Aura LP != BPT"); + } + + /** + * @dev Withdraw `_lpAmount` Balancer Pool Tokens (BPT) from + * the Aura rewards pool to this strategy contract. + * @param _lpAmount Number of Balancer Pool Tokens (BPT) to withdraw + */ + function _unStakeLpTokens(uint256 _lpAmount) internal override { + IRewardStaking(auraRewardPool).withdrawAndUnwrap( + _lpAmount, + true // also claim reward tokens + ); + } + + /** + * @dev Withdraw all Balancer Pool Tokens (BPT) from + * the Aura rewards pool to this strategy contract. + */ + function _unStakeAllLpTokens() internal override { + // Get all the strategy's BPTs in Aura + // maxRedeem is implemented as balanceOf(address) in Aura + uint256 bptBalance = IERC4626(auraRewardPool).maxRedeem(address(this)); + + IRewardStaking(auraRewardPool).withdrawAndUnwrap( + bptBalance, + true // also claim reward tokens + ); + } + + /** + * @notice Collect accumulated BAL and AURA rewards and send to the Harvester. + */ + function collectRewardTokens() + external + override + onlyHarvester + nonReentrant + { + /* Similar to Convex, calling this function collects both of the + * accrued BAL and AURA tokens. + */ + IRewardStaking(auraRewardPool).getReward(); + _collectRewardTokens(); + } + + /*************************************** + Approvals + ****************************************/ + + function _approveBase() internal override { + // Approve Balancer vault for WETH and OETH (required for adding liquidity) + // slither-disable-next-line unused-return + oToken.approve(address(balancerVault), type(uint256).max); + // slither-disable-next-line unused-return + poolAsset.approve(address(balancerVault), type(uint256).max); + + // Approve Aura rewards pool to transfer Balancer pool tokens (BPT) + // slither-disable-next-line unused-return + lpToken.approve(auraRewardPool, type(uint256).max); + } +} diff --git a/contracts/contracts/strategies/amo/BaseConvexAMOStrategy.sol b/contracts/contracts/strategies/amo/BaseConvexAMOStrategy.sol new file mode 100644 index 0000000000..5a4ebbbfb3 --- /dev/null +++ b/contracts/contracts/strategies/amo/BaseConvexAMOStrategy.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Abstract Convex Automated Market Maker (AMO) Strategy + * @notice Investment strategy for investing assets in Curve and Convex pools + * @author Origin Protocol Inc + */ +import { BaseAMOStrategy, InitializableAbstractStrategy } from "./BaseAMOStrategy.sol"; +import { ICurveETHPoolV1 } from "../curve/ICurveETHPoolV1.sol"; +import { IConvexDeposits } from "../IConvexDeposits.sol"; +import { IRewardStaking } from "../IRewardStaking.sol"; + +abstract contract BaseConvexAMOStrategy is BaseAMOStrategy { + // New immutable variables that must be set in the constructor + address public immutable cvxDepositorAddress; + IRewardStaking public immutable cvxRewardStaker; + uint256 public immutable cvxDepositorPTokenId; + /// @notice The Curve pool that the strategy invests in + ICurveETHPoolV1 public immutable curvePool; + + // Used to circumvent the stack too deep issue + struct ConvexConfig { + address cvxDepositorAddress; // Address of the Convex depositor(AKA booster) for this pool + address cvxRewardStakerAddress; // Address of the CVX rewards staker + uint256 cvxDepositorPTokenId; // Pid of the pool referred to by Depositor and staker + } + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + ConvexConfig memory _convexConfig + ) BaseAMOStrategy(_baseConfig, _amoConfig) { + // Is the AMO pool contract. eg OETH/ETH, OUSD/3CRV or OETH/frxETH + curvePool = ICurveETHPoolV1(_baseConfig.platformAddress); + + cvxDepositorAddress = _convexConfig.cvxDepositorAddress; + cvxRewardStaker = IRewardStaking(_convexConfig.cvxRewardStakerAddress); + cvxDepositorPTokenId = _convexConfig.cvxDepositorPTokenId; + } + + /** + * Initializer for setting up strategy internal state. This overrides the + * InitializableAbstractStrategy initializer as Curve strategies don't fit + * well within that abstraction. + * @param _rewardTokenAddresses Address of reward tokens CRV & CVX + */ + function initialize( + address[] calldata _rewardTokenAddresses, + address[] calldata _assets, + address[] calldata _pTokens + ) external onlyGovernor initializer { + InitializableAbstractStrategy._initialize( + _rewardTokenAddresses, + _assets, + _pTokens + ); + + _approveBase(); + } + + /*************************************** + Curve Pool + ****************************************/ + + /// @dev Adds pool assets (3CRV, frxETH or ETH) and/or OTokens (OUSD or OETH) to the Curve pool + /// @param poolAmounts The amount of Curve pool assets to add to the pool + /// @param minLpAmount The minimum amount of Curve pool LP tokens that is acceptable to receive + function _addLiquidityToPool( + uint256[2] memory poolAmounts, + uint256 minLpAmount + ) internal virtual override returns (uint256 lpDeposited) { + lpDeposited = curvePool.add_liquidity(poolAmounts, minLpAmount); + } + + /// @dev Removes pool assets and/or OTokens from the Curve pool + /// @param lpTokens The amount of Curve pool LP tokens to be burnt + /// @param minPoolAssetAmounts The minimum amount of AMO pool assets that are acceptable to receive + function _removeLiquidityFromPool( + uint256 lpTokens, + uint256[2] memory minPoolAssetAmounts + ) internal override { + // slither-disable-next-line unused-return + curvePool.remove_liquidity(lpTokens, minPoolAssetAmounts); + } + + /// @dev Removes either pool assets or OTokens from the Curve pool. + /// @param poolAsset The address of the Curve pool asset to be removed. eg OETH, OUSD, ETH, 3CRV, frxETH + /// @param lpTokens The amount of Curve pool LP tokens to be burnt + /// @param minPoolAssetAmount The minimum amount of Curve pool assets that are acceptable to receive. eg OETH or ETH + function _removeOneSidedLiquidityFromPool( + address poolAsset, + uint256 lpTokens, + uint256 minPoolAssetAmount + ) internal override returns (uint256 coinsRemoved) { + uint128 coinIndex = _getCoinIndex(poolAsset); + + // Remove only one asset from the Curve pool + coinsRemoved = curvePool.remove_liquidity_one_coin( + lpTokens, + int128(coinIndex), + minPoolAssetAmount, + address(this) + ); + } + + /// @dev Returns the current balances of the Curve pool + function _getBalances() + internal + view + override + returns (uint256[2] memory balances) + { + balances = curvePool.get_balances(); + } + + /// @dev Returns the current balances of the Curve pool + function _getBalance(address poolAsset) + internal + view + override + returns (uint256 balance) + { + uint128 coinIndex = _getCoinIndex(poolAsset); + balance = curvePool.balances(uint128(coinIndex)); + } + + /// @dev Returns the price of one Curve pool LP token in base asset terms. + function _getVirtualPrice() + internal + view + override + returns (uint256 virtualPrice) + { + virtualPrice = curvePool.get_virtual_price(); + } + + /*************************************** + Convex Reward Pool + ****************************************/ + + /// @dev Deposit the AMO pool LP tokens to the rewards pool. + /// eg Curve LP tokens into Convex or Balancer LP tokens into Aura + function _stakeCurveLp(uint256 lpDeposited) internal override { + require( + IConvexDeposits(cvxDepositorAddress).deposit( + cvxDepositorPTokenId, + lpDeposited, + true // Deposit with staking + ), + "Failed to Deposit LP to Convex" + ); + } + + /// @dev Withdraw a specific amount of AMO pool LP tokens from the rewards pool + /// eg Curve LP tokens from Convex or Balancer LP tokens from Aura + function _unStakeLpTokens(uint256 _lpAmount) internal override { + // withdraw and unwrap with claim takes back the lpTokens. + // Do not collect any reward tokens as that will be done via the harvester + cvxRewardStaker.withdrawAndUnwrap(_lpAmount, false); + } + + /// @dev Withdraw all AMO pool LP tokens from the rewards pool + function _unStakeAllLpTokens() internal override { + uint256 gaugeTokens = cvxRewardStaker.balanceOf(address(this)); + // withdraw and unwrap with claim takes back the lpTokens. + // Do not collect any reward tokens as that will be done via the harvester + cvxRewardStaker.withdrawAndUnwrap(gaugeTokens, false); + } + + /** + * @notice Collect accumulated CRV and CVX rewards and send to the Harvester. + */ + function collectRewardTokens() + external + override + onlyHarvester + nonReentrant + { + // Collect CRV and CVX + cvxRewardStaker.getReward(); + _collectRewardTokens(); + } +} diff --git a/contracts/contracts/strategies/amo/ConvexEthMetaStrategy.sol b/contracts/contracts/strategies/amo/ConvexEthMetaStrategy.sol new file mode 100644 index 0000000000..7cbb7824df --- /dev/null +++ b/contracts/contracts/strategies/amo/ConvexEthMetaStrategy.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Convex Automated Market Maker (AMO) Strategy + * @notice AMO strategy for the Curve OETH/ETH pool + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { BaseConvexAMOStrategy } from "./BaseConvexAMOStrategy.sol"; +import { IWETH9 } from "../../interfaces/IWETH9.sol"; + +contract ConvexEthMetaStrategy is BaseConvexAMOStrategy { + // The following slots have been deprecated with immutable variables + // slither-disable-next-line constable-states + address private _deprecated_cvxDepositorAddress; + // slither-disable-next-line constable-states + address private _deprecated_cvxRewardStaker; + // slither-disable-next-line constable-states + uint256 private _deprecated_cvxDepositorPTokenId; + // slither-disable-next-line constable-states + address private _deprecated_curvePool; + // slither-disable-next-line constable-states + address private _deprecated_lpToken; + // slither-disable-next-line constable-states + address private _deprecated_oeth; + // slither-disable-next-line constable-states + address private _deprecated_weth; + + // Ordered list of pool assets + // slither-disable-next-line constable-states + uint128 private _deprecated_oethCoinIndex = 1; + // slither-disable-next-line constable-states + uint128 private _deprecated_ethCoinIndex = 0; + + // Added for backward compatibility + address public immutable oeth; + address public immutable weth; + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + ConvexConfig memory _convexConfig + ) BaseConvexAMOStrategy(_baseConfig, _amoConfig, _convexConfig) { + oeth = _amoConfig.oTokenAddress; + weth = _amoConfig.vaultAssetAddress; + } + + /*************************************** + Vault to Pool Asset Conversions + ****************************************/ + + /// @dev Unwraps the ETH from WETH using WETH withdraw + function _toPoolAsset(address, uint256 wethAmount) + internal + override + returns (uint256 ethAmount) + { + IWETH9(address(vaultAsset)).withdraw(wethAmount); + ethAmount = wethAmount; + } + + function _calcPoolAsset(address, uint256 wethAmount) + internal + pure + override + returns (uint256 ethAmount) + { + ethAmount = wethAmount; + } + + /// @dev The OETH OToken is 1:1 to ETH so return the ETH amount + function _toOTokens(uint256 ethAmount) + internal + pure + override + returns (uint256 oethAmount) + { + oethAmount = ethAmount; + } + + /*************************************** + Curve Pool Deposits + ****************************************/ + + /// @dev Adds ETH and/or OETH to the Curve pool + /// @param poolAmounts The amount of Curve pool assets (ETH/OETH) to add to the pool + /// @param minLpAmount The minimum amount of Curve pool LP tokens that is acceptable to receive + function _addLiquidityToPool( + uint256[2] memory poolAmounts, + uint256 minLpAmount + ) internal override returns (uint256 lpDeposited) { + // ETH is passed into the Pool as value in the add_liquidity call + lpDeposited = curvePool.add_liquidity{ + value: poolAmounts[assetCoinIndex] + }(poolAmounts, minLpAmount); + } + + /*************************************** + Curve Pool Withdrawals + ****************************************/ + + function _withdrawAsset( + address, + uint256 vaultAssetAmount, + address recipient + ) internal override { + require(address(this).balance >= vaultAssetAmount, "Insufficient ETH"); + + // Convert ETH to WETH + IWETH9(address(vaultAsset)).deposit{ value: vaultAssetAmount }(); + + // Transfer the WETH to the Vault + require( + vaultAsset.transfer(recipient, vaultAssetAmount), + "WETH transfer failed" + ); + + emit Withdrawal( + address(vaultAsset), + address(lpToken), + vaultAssetAmount + ); + } + + /// @dev Gets the ETH balance of this strategy contract + /// and then converts all the ETH to WETH + function _withdrawAllAsset(address recipient) internal override { + // Get ETH balance of this strategy contract + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + _withdrawAsset(address(vaultAsset), ethBalance, recipient); + } + } + + /*************************************** + Asset Balance + ****************************************/ + + /** + * @notice Get the total asset value held in the platform + * @param _asset Address of the asset + * @return balance ETH value of both OETH and ETH in the Curve pool + */ + function checkBalance(address _asset) + public + view + override + onlyAsset(_asset) + returns (uint256 balance) + { + // Eth balance needed here for the balance check that happens from vault during depositing. + balance = address(this).balance; + uint256 lpTokens = cvxRewardStaker.balanceOf(address(this)); + if (lpTokens > 0) { + balance += (lpTokens * curvePool.get_virtual_price()) / 1e18; + } + } + + /*************************************** + Approvals + ****************************************/ + + /** + * @notice Accept unwrapped WETH + */ + receive() external payable {} + + /** + * @dev Since we are unwrapping WETH before depositing it to Curve + * there is no need to to set an approval for WETH on the Curve + * pool + * @param _asset Address of the asset + * @param _pToken Address of the Curve LP token + */ + // solhint-disable-next-line no-unused-vars + function _abstractSetPToken(address _asset, address _pToken) + internal + override + {} + + function _approveBase() internal override { + // Approve Curve pool for OETH (required for adding liquidity) + // No approval is needed for ETH + // slither-disable-next-line unused-return + oToken.approve(platformAddress, type(uint256).max); + + // Approve Convex deposit contract to transfer Curve pool LP tokens + // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool + // slither-disable-next-line unused-return + lpToken.approve(cvxDepositorAddress, type(uint256).max); + } +} diff --git a/contracts/contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol b/contracts/contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol new file mode 100644 index 0000000000..cc4386572c --- /dev/null +++ b/contracts/contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Convex Automated Market Maker (AMO) Strategy + * @notice AMO strategy for the Curve OETH/frxETH pool + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { BaseConvexAMOStrategy } from "./BaseConvexAMOStrategy.sol"; + +contract ConvexFrxETHAMOStrategy is BaseConvexAMOStrategy { + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + ConvexConfig memory _convexConfig + ) BaseConvexAMOStrategy(_baseConfig, _amoConfig, _convexConfig) {} + + /*************************************** + Vault to Pool Asset Conversions + ****************************************/ + + /// @dev frxETH is the Vault asset and the Curve pool asset so + /// nothing to except return the vault asset amount + function _toPoolAsset(address, uint256 assets) + internal + pure + override + returns (uint256 poolAssets) + { + poolAssets = assets; + } + + function _calcPoolAsset(address, uint256 vaultAssetAmount) + internal + pure + override + returns (uint256 poolAssetAmount) + { + poolAssetAmount = vaultAssetAmount; + } + + /// @dev frxETH is the Vault asset and the pool asset so return the frxETH amount + /// @param poolAssetAmount Amount of frxETH to convert to OETH + function _toOTokens(uint256 poolAssetAmount) + internal + pure + override + returns (uint256 oethAmount) + { + // TODO - does this need to be converted to OETH amount using the Oracle? + oethAmount = poolAssetAmount; + } + + /*************************************** + Curve Pool Withdrawals + ****************************************/ + + /// @dev transfers the specified frxETH amount to the recipient + function _withdrawAsset( + address, + uint256 vaultAssetAmount, + address _recipient + ) internal override { + // Transfer the frxETH to the Vault + require( + vaultAsset.transfer(_recipient, vaultAssetAmount), + "frxETH transfer failed" + ); + + emit Withdrawal( + address(vaultAsset), + address(lpToken), + vaultAssetAmount + ); + } + + /// @dev transfers the frxETH balance of this strategy contract to the recipient + function _withdrawAllAsset(address _recipient) internal override { + uint256 vaultAssets = vaultAsset.balanceOf(address(this)); + + if (vaultAssets > 0) { + _withdrawAsset(address(vaultAsset), vaultAssets, _recipient); + } + } + + /*************************************** + Asset Balance + ****************************************/ + + /** + * @notice Get the total asset value held in the platform + * @param _asset Address of the asset + * @return balance Total value of the asset in the platform + */ + function checkBalance(address _asset) + public + view + override + onlyAsset(_asset) + returns (uint256 balance) + { + // Get the Curve LP tokens staked in the Convex pool + uint256 curveLpTokens = cvxRewardStaker.balanceOf(address(this)); + + /* We intentionally omit any Curve LP tokens held by this strategy that + * may have been left by a withdraw as this should be dust amounts. + * Under normal operation, there should not be any frxETH assets in this strategy so it is also ignore. + * We also ignore any assets accedentally sent to this contract by a 3rd party. eg ETH. + */ + + if (curveLpTokens > 0) { + balance = (curveLpTokens * curvePool.get_virtual_price()) / 1e18; + + // Unlike the OUSD Vault assets, the OETH Vault assets do not need to be scaled down from 18 decimals + // as they are all 18 decimals + } + } + + /*************************************** + Approvals + ****************************************/ + + /** + * @dev Since we are unwrapping WETH before depositing it to Curve + * there is no need to to set an approval for WETH on the Curve + * pool + * @param _asset Address of the asset + * @param _pToken Address of the Curve LP token + */ + // solhint-disable-next-line no-unused-vars + function _abstractSetPToken(address _asset, address _pToken) + internal + override + {} + + function _approveBase() internal override { + // Approve Curve pool for frxETH and OETH (required for adding liquidity) + // slither-disable-next-line unused-return + oToken.approve(platformAddress, type(uint256).max); + // slither-disable-next-line unused-return + poolAsset.approve(platformAddress, type(uint256).max); + + // Approve Convex deposit contract to transfer Curve pool LP tokens + // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool + // slither-disable-next-line unused-return + lpToken.approve(cvxDepositorAddress, type(uint256).max); + } +} diff --git a/contracts/contracts/strategies/amo/ConvexOUSDMetaStrategy.sol b/contracts/contracts/strategies/amo/ConvexOUSDMetaStrategy.sol new file mode 100644 index 0000000000..b8a321caa6 --- /dev/null +++ b/contracts/contracts/strategies/amo/ConvexOUSDMetaStrategy.sol @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Convex Automated Market Maker (AMO) Strategy + * @notice AMO strategy for the Curve OUSD/3CRV pool + * @author Origin Protocol Inc + */ +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import { BaseConvexAMOStrategy } from "./BaseConvexAMOStrategy.sol"; +import { ICurvePool } from "../curve/ICurvePool.sol"; +import { StableMath } from "../../utils/StableMath.sol"; + +contract ConvexOUSDMetaStrategy is BaseConvexAMOStrategy { + using StableMath for uint256; + using SafeERC20 for IERC20; + + uint256 internal constant THREEPOOL_ASSET_COUNT = 3; + + address public immutable DAI; + address public immutable USDC; + address public immutable USDT; + ICurvePool public immutable curve3Pool; + + // The following slots have been deprecated with immutable variables + // slither-disable-next-line constable-states + address private _deprecated_pTokenAddresses; + // slither-disable-next-line constable-states + int256[49] private __reserved; + // slither-disable-next-line constable-states + address private _deprecated_cvxDepositorAddress; + // slither-disable-next-line constable-states + address private _deprecated_cvxRewardStakerAddress; + // slither-disable-next-line constable-states + uint256 private _deprecated_cvxDepositorPTokenId; + // slither-disable-next-line constable-states + address private _deprecated_metapool; + // slither-disable-next-line constable-states + address private _deprecated_metapoolMainToken; + // slither-disable-next-line constable-states + address private _deprecated_metapoolLPToken; + // slither-disable-next-line constable-states + address[] private _deprecated_metapoolAssets; + // slither-disable-next-line constable-states + uint256 private _deprecated_maxWithdrawalSlippage; + // slither-disable-next-line constable-states + uint128 private _deprecated_crvCoinIndex; + // slither-disable-next-line constable-states + uint128 private _deprecated_mainCoinIndex; + + constructor( + BaseStrategyConfig memory _baseConfig, + AMOConfig memory _amoConfig, + ConvexConfig memory _convexConfig, + address _curve3Pool, + address[3] memory _curve3PoolAssets + ) BaseConvexAMOStrategy(_baseConfig, _amoConfig, _convexConfig) { + DAI = _curve3PoolAssets[0]; + USDC = _curve3PoolAssets[1]; + USDT = _curve3PoolAssets[2]; + curve3Pool = ICurvePool(_curve3Pool); + } + + /*************************************** + Vault Asset Validation + ****************************************/ + + function _isVaultAsset(address _vaultAsset) + internal + view + override + returns (bool supported) + { + supported = + _vaultAsset == DAI || + _vaultAsset == USDC || + _vaultAsset == USDT; + } + + /// @dev Returns bool indicating whether all the assets are supported by this strategy. + /// For the OUSD AMO, this is DAI, USDC or USDT. + /// @param _vaultAssets Addresses of the vault assets + function _isVaultAssets(address[] memory _vaultAssets) + internal + view + override + returns (bool) + { + if (_vaultAssets.length == 1) { + return _isVaultAsset(_vaultAssets[0]); + } else if (_vaultAssets.length == 2) { + return + _isVaultAsset(_vaultAssets[0]) && + _isVaultAsset(_vaultAssets[1]); + } else if (_vaultAssets.length == 2) { + return + _isVaultAsset(_vaultAssets[0]) && + _isVaultAsset(_vaultAssets[1]) && + _isVaultAsset(_vaultAssets[2]); + } + require(_vaultAssets.length > 0, "No asset"); + revert("Only three assets supported"); + } + + /*************************************** + Curve Pool + ****************************************/ + + /** + * @dev Get the asset token's index position in the Curve 3Pool + * and the token's decimals. + */ + function _coinIndexDecimals(address _asset) + internal + view + returns (uint256 index, uint256 decimals) + { + if (_asset == DAI) { + return (0, 18); + } else if (_asset == USDC) { + return (1, 6); + } else if (_asset == USDT) { + return (2, 6); + } + revert("Unsupported asset"); + } + + /*************************************** + Vault to Pool Asset Conversions + ****************************************/ + + /// @dev DAI, USDC or USDC is the Vault asset and the Curve 3Pool lp token 3CRV is + /// is Curve's OUSD/3CRV Metapool asset + function _toPoolAsset(address _asset, uint256 _amount) + internal + override + returns (uint256 poolAssets) + { + (uint256 poolCoinIndex, uint256 decimals) = _coinIndexDecimals(_asset); + + // 3Pool requires passing deposit amounts for all 3 assets, set to 0 for all + uint256[3] memory _amounts; + // Set the amount on the asset we want to deposit + _amounts[poolCoinIndex] = _amount; + uint256 minMintAmount = _amount + .scaleBy(18, decimals) + .divPrecisely(curve3Pool.get_virtual_price()) + .mulTruncate(uint256(1e18) - MAX_SLIPPAGE); + + // Do the deposit to 3pool + curve3Pool.add_liquidity(_amounts, minMintAmount); + + poolAssets = poolAsset.balanceOf(address(this)); + } + + /** + * @dev Calculate amount of LP required when withdrawing specific amount of one + * of the underlying assets accounting for fees and slippage. + * + * Curve pools unfortunately do not contain a calculation function for + * amount of LP required when withdrawing a specific amount of one of the + * underlying tokens and also accounting for fees (Curve's calc_token_amount + * does account for slippage but not fees). + * + * Steps taken to calculate the metric: + * - get amount of LP required if fees wouldn't apply + * - increase the LP amount as if fees would apply to the entirety of the underlying + * asset withdrawal. (when withdrawing only one coin fees apply only to amounts + * of other assets pool would return in case of balanced removal - since those need + * to be swapped for the single underlying asset being withdrawn) + * - get amount of underlying asset withdrawn (this Curve function does consider slippage + * and fees) when using the increased LP amount. As LP amount is slightly over-increased + * so is amount of underlying assets returned. + * - since we know exactly how much asset we require take the rate of LP required for asset + * withdrawn to get the exact amount of LP. + */ + function _calcPoolAsset(address _vaultAsset, uint256 _vaultAssetAmount) + internal + override + returns (uint256 required3Crv) + { + (uint256 coinIndex, ) = _coinIndexDecimals(_vaultAsset); + uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)]; + _amounts[coinIndex] = _vaultAssetAmount; + + // 3Pool LP required when removing reuiqred vault assets ignoring fees + uint256 lpRequiredNoFees = curve3Pool.calc_token_amount( + _amounts, + false + ); + /* 3Pool LP required if fees would apply to entirety of removed amount + * + * fee is 1e10 denominated number: https://curve.readthedocs.io/exchange-pools.html#StableSwap.fee + */ + uint256 lpRequiredFullFees = lpRequiredNoFees.mulTruncateScale( + 1e10 + curve3Pool.fee(), + 1e10 + ); + + /* asset received when withdrawing full fee applicable LP accounting for + * slippage and fees + */ + uint256 assetReceivedForFullLPFees = curve3Pool.calc_withdraw_one_coin( + lpRequiredFullFees, + int128(uint128(coinIndex)) + ); + + // exact amount of LP required + required3Crv = + (lpRequiredFullFees * _vaultAssetAmount) / + assetReceivedForFullLPFees; + } + + /// @dev Converts 3CRV to OUSD by using the 3Pool virtual price + function _toOTokens(uint256 threeCrvAmount) + internal + view + override + returns (uint256 ousdAmount) + { + uint256 virtualPrice = curve3Pool.get_virtual_price(); + ousdAmount = threeCrvAmount.mulTruncate(virtualPrice); + } + + /*************************************** + Curve Pool Deposits + ****************************************/ + + /** + * @notice Deposit multiple vault assets into the AMO strategy. + * @param _vaultAssets Addresses of the vault asset tokens. eg DAI, USDC or USDT + * @param _vaultAssetAmounts Amounts of vault asset tokens to deposit. + */ + function deposit( + address[] memory _vaultAssets, + uint256[] memory _vaultAssetAmounts + ) external override onlyVault nonReentrant { + uint256[3] memory amounts = [uint256(0), uint256(0), uint256(0)]; + uint256 depositValue = 0; + uint256 curve3PoolVirtualPrice = curve3Pool.get_virtual_price(); + + for (uint256 i = 0; i < _vaultAssets.length; ++i) { + require(_isVaultAsset(_vaultAssets[i]), "Unsupported asset"); + if (_vaultAssetAmounts[i] > 0) { + depositValue += _addAmount( + _vaultAssetAmounts[i], + amounts, + _vaultAssets[i], + curve3PoolVirtualPrice + ); + } + } + uint256 minMintAmount = depositValue.mulTruncate( + uint256(1e18) - MAX_SLIPPAGE + ); + + // deposit DAI, USDC and/or USDT to the Curve 3Pool + curve3Pool.add_liquidity(amounts, minMintAmount); + + // Get the Curve OUSD/3CRV Metapool LP token balance of this strategy contract + uint256 threePoolLpBalance = poolAsset.balanceOf(address(this)); + + // AMO deposit to the Curve Metapool + _deposit(threePoolLpBalance); + } + + function depositAll() external override onlyVault nonReentrant { + uint256[3] memory amounts = [uint256(0), uint256(0), uint256(0)]; + uint256 depositValue = 0; + uint256 curve3PoolVirtualPrice = curve3Pool.get_virtual_price(); + + depositValue = _addBalanceToAmounts( + amounts, + DAI, + curve3PoolVirtualPrice + ); + depositValue += _addBalanceToAmounts( + amounts, + USDC, + curve3PoolVirtualPrice + ); + depositValue += _addBalanceToAmounts( + amounts, + USDT, + curve3PoolVirtualPrice + ); + + uint256 minMintAmount = depositValue.mulTruncate( + uint256(1e18) - MAX_SLIPPAGE + ); + + // deposit DAI, USDC and/or USDT to the Curve 3Pool + curve3Pool.add_liquidity(amounts, minMintAmount); + + // Get the Curve OUSD/3CRV Metapool LP token balance of this strategy contract + uint256 threePoolLpBalance = poolAsset.balanceOf(address(this)); + + // AMO deposit to the Curve Metapool + _deposit(threePoolLpBalance); + } + + function _addBalanceToAmounts( + uint256[3] memory amounts, + address usdAsset, + uint256 curve3PoolVirtualPrice + ) internal returns (uint256 depositValue) { + uint256 balance = IERC20(usdAsset).balanceOf(address(this)); + if (balance > 0) { + depositValue = _addAmount( + balance, + amounts, + usdAsset, + curve3PoolVirtualPrice + ); + } + } + + function _addAmount( + uint256 amount, + uint256[3] memory amounts, + address usdAsset, + uint256 curve3PoolVirtualPrice + ) internal returns (uint256 depositValue) { + (uint256 poolCoinIndex, uint256 assetDecimals) = _coinIndexDecimals( + usdAsset + ); + // Set the amount on the asset we want to deposit + amounts[poolCoinIndex] = amount; + // Get value of deposit in Curve LP token to later determine + // the minMintAmount argument for add_liquidity + depositValue = amount.scaleBy(18, assetDecimals).divPrecisely( + curve3PoolVirtualPrice + ); + emit Deposit(usdAsset, address(lpToken), amount); + } + + /*************************************** + Curve Withdrawals + ****************************************/ + + function withdraw( + address, + address[] memory _vaultAssets, + uint256[] memory + ) external override onlyVault onlyAssets(_vaultAssets) nonReentrant { + // TODO add support for withdrawing multiple 3Pool assets + revert("Not supported"); + } + + /// @dev Converts all 3CRV in this strategy to the Vault asset DAI, USDC or USDT + /// by removing liquidity from the Curve 3Pool. + /// Then transfers each vault asset to the vault. + function _withdrawAsset( + address vaultAsset, + uint256 vaultAssetAmount, + address recipient + ) internal override { + uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)]; + (uint256 coinIndex, ) = _coinIndexDecimals(vaultAsset); + _amounts[coinIndex] = vaultAssetAmount; + + // Remove just the specified vault asset from the 3Pool + // using all the previously removed 3CRV assets from the AMO pool + curve3Pool.remove_liquidity_imbalance( + _amounts, + IERC20(poolAsset).balanceOf(address(this)) + ); + + // Transfer assets to the Vault + // Note that Curve will provide all 3 of the assets in 3pool even if + // we have not set PToken addresses for all of them in this strategy + _transferAssetBalance(recipient, IERC20(vaultAsset)); + } + + /// @dev Converts all 3CRV in this strategy to the Vault assets DAI, USDC and USDT + /// by removing liquidity from the Curve OUSD/3CRV Metapool. + /// Then transfers each vault asset to the vault. + function _withdrawAllAsset(address recipient) internal override { + // Withdraws are proportional to assets held by 3Pool + uint256[3] memory minWithdrawAmounts = [ + uint256(0), + uint256(0), + uint256(0) + ]; + + // Remove all liquidity from the 3Pool + curve3Pool.remove_liquidity( + IERC20(poolAsset).balanceOf(address(this)), + minWithdrawAmounts + ); + + // Transfer assets to the Vault + // Note that Curve will provide all 3 of the assets in 3pool even if + // we have not set PToken addresses for all of them in this strategy + _transferAssetBalance(recipient, IERC20(DAI)); + _transferAssetBalance(recipient, IERC20(USDC)); + _transferAssetBalance(recipient, IERC20(USDT)); + } + + function _transferAssetBalance(address _recipient, IERC20 asset) internal { + uint256 assetBalance = asset.balanceOf(address(this)); + if (assetBalance > 0) { + asset.safeTransfer(_recipient, assetBalance); + emit Withdrawal(address(asset), address(lpToken), assetBalance); + } + } + + /*************************************** + Asset Balance + ****************************************/ + + /** + * @notice Get the total asset value held in the platform + * @param _vaultAsset Address of the vault asset + * @return balance Total value of the asset in the platform + */ + function checkBalance(address _vaultAsset) + public + view + override + onlyAsset(_vaultAsset) + returns (uint256 balance) + { + // 3Pool LP tokens (3Crv) in this strategy contract. + // This should generally be nothing as we should always stake + // the full balance in the Gauge, but include for safety + uint256 threePoolTokens = IERC20(poolAsset).balanceOf(address(this)); + if (threePoolTokens > 0) { + // Getting the virtual price is gas expensive so only do if we have Cuve LP tokens + balance = threePoolTokens.mulTruncate( + curve3Pool.get_virtual_price() + ); + } + + /* We intentionally omit the Curve Metapool LP tokens held by this strategy + * since the contract should never (except in the middle of deposit/withdrawal + * transaction) hold any amount of those tokens in normal operation. There + * could be tokens sent to it by a 3rd party and we decide to actively ignore + * those. + */ + + // Curve LP tokens in the Convex rewards pool + uint256 curveLpTokens = cvxRewardStaker.balanceOf(address(this)); + + if (curveLpTokens > 0) { + // Getting the virtual price is gas expensive so only do if we have Cuve LP tokens + balance += curveLpTokens.mulTruncate(curvePool.get_virtual_price()); + } + + // Scale down from 18 decimals used by 3Crv to asset decimals. eg 6 for USDC or USDT + (, uint256 assetDecimals) = _coinIndexDecimals(_vaultAsset); + balance = balance.scaleBy(assetDecimals, 18) / THREEPOOL_ASSET_COUNT; + } + + /*************************************** + Approvals + ****************************************/ + + // solhint-disable-next-line no-unused-vars + function _abstractSetPToken(address _asset, address _pToken) + internal + override + {} + + function _approveBase() internal override { + // Approve Curve 3Pool for DAI, USDC and USDT + // slither-disable-next-line unused-return + IERC20(DAI).approve(address(curve3Pool), type(uint256).max); + // slither-disable-next-line unused-return + IERC20(USDC).approve(address(curve3Pool), type(uint256).max); + // slither-disable-next-line unused-return + IERC20(USDT).approve(address(curve3Pool), type(uint256).max); + + // Approve Curve OUSD/3CRV Metapool for 3CRV and OUSD (required for adding liquidity) + // slither-disable-next-line unused-return + oToken.approve(address(curvePool), type(uint256).max); + // slither-disable-next-line unused-return + poolAsset.approve(address(curvePool), type(uint256).max); + + // Approve Convex deposit contract to transfer Curve pool LP tokens + // This is needed for deposits if Curve pool LP tokens into the Convex rewards pool + // slither-disable-next-line unused-return + lpToken.approve(cvxDepositorAddress, type(uint256).max); + } +} diff --git a/contracts/contracts/strategies/amo/README.md b/contracts/contracts/strategies/amo/README.md new file mode 100644 index 0000000000..5399af05ff --- /dev/null +++ b/contracts/contracts/strategies/amo/README.md @@ -0,0 +1,59 @@ +# Diagrams + +![AMO Contracts](../../../docs/AMOContractHierarchy.svg) + +## Convex AMO Strategy for OETH/ETH + +### Hierarchy + +![Convex OETH/ETH AMO Strategy Hierarchy](../../../docs/ConvexEthMetaStrategyHierarchy.svg) + +### Squashed + +![Convex OETH/ETH AMO Strategy Squashed](../../../docs/ConvexEthMetaStrategySquashed.svg) + +### Storage + +![Convex OETH/ETH AMO Strategy Storage](../../../docs/ConvexEthMetaStrategyStorage.svg) + +## Convex AMO Strategy for OETH/frxETH + +### Hierarchy + +![Convex frxETH AMO Strategy Hierarchy](../../../docs/ConvexFrxETHAMOStrategyHierarchy.svg) + +### Squashed + +![Convex frxETH AMO Strategy Squashed](../../../docs/ConvexFrxETHAMOStrategySquashed.svg) + +### Storage + +![Convex frxETH AMO Strategy Storage](../../../docs/ConvexFrxETHAMOStrategyStorage.svg) + +## Convex AMO Strategy for OUSD/3CRV + +### Hierarchy + +![Convex OUSD AMO Strategy Hierarchy](../../../docs/ConvexOUSDMetaStrategyHierarchy.svg) + +### Squashed + +![Convex OUSD AMO Strategy Squashed](../../../docs/ConvexOUSDMetaStrategySquashed.svg) + +### Storage + +![Convex OUSD AMO Strategy Storage](../../../docs/ConvexOUSDMetaStrategyStorage.svg) + +## Balancer AMO Strategy for OETH/WETH + +### Hierarchy + +![Balancer OETH AMO Strategy Hierarchy](../../../docs/BalancerEthAMOStrategyHierarchy.svg) + +### Squashed + +![Balancer OETH AMO Strategy Squashed](../../../docs/BalancerEthAMOStrategySquashed.svg) + +### Storage + +![Balancer OETH AMO Strategy Storage](../../../docs/BalancerEthAMOStrategyStorage.svg) diff --git a/contracts/contracts/vault/VaultAdmin.sol b/contracts/contracts/vault/VaultAdmin.sol index 5d4adcf2e1..6b90906d8d 100644 --- a/contracts/contracts/vault/VaultAdmin.sol +++ b/contracts/contracts/vault/VaultAdmin.sol @@ -14,9 +14,9 @@ import { ISwapper } from "../interfaces/ISwapper.sol"; import { IVault } from "../interfaces/IVault.sol"; import { StableMath } from "../utils/StableMath.sol"; -import "./VaultStorage.sol"; +import "./VaultInitializer.sol"; -contract VaultAdmin is VaultStorage { +contract VaultAdmin is VaultInitializer { using SafeERC20 for IERC20; using StableMath for uint256; @@ -104,7 +104,7 @@ contract VaultAdmin is VaultStorage { * @notice Set the default Strategy for an asset, i.e. the one which the asset will be automatically allocated to and withdrawn from * @param _asset Address of the asset - * @param _strategy Address of the Strategy + * @param _strategy Address of the strategy contract or zero address if removing */ function setAssetDefaultStrategy(address _asset, address _strategy) external @@ -128,15 +128,16 @@ contract VaultAdmin is VaultStorage { /** * @notice Set maximum amount of OTokens that can at any point be minted and deployed - * to strategy (used only by ConvexOUSDMetaStrategy for now). + * to a strategy. This is only used by the AMO strategies for now. + * @param _strategy Address of the strategy contract * @param _threshold OToken amount with 18 fixed decimals. */ - function setNetOusdMintForStrategyThreshold(uint256 _threshold) + function setMintForStrategyThreshold(address _strategy, uint256 _threshold) external onlyGovernor { /** - * Because `netOusdMintedForStrategy` check in vault core works both ways + * Because `mintedForStrategy` check in vault core works both ways * (positive and negative) the actual impact of the amount of OToken minted * could be double the threshold. E.g.: * - contract has threshold set to 100 @@ -149,9 +150,14 @@ contract VaultAdmin is VaultStorage { * amount and not have problems with current netOusdMinted being near * limits on either side. */ - netOusdMintedForStrategy = 0; - netOusdMintForStrategyThreshold = _threshold; - emit NetOusdMintForStrategyThresholdChanged(_threshold); + // read from storage + Strategy memory strategyConfig = strategies[_strategy]; + strategyConfig.mintForStrategy = int96(0); + strategyConfig.mintForStrategyThreshold = toInt96(_threshold); + // write back to storage + strategies[_strategy] = strategyConfig; + + emit MintForStrategyThresholdChanged(_strategy, _threshold); } /*************************************** @@ -362,7 +368,12 @@ contract VaultAdmin is VaultStorage { */ function approveStrategy(address _addr) external onlyGovernor { require(!strategies[_addr].isSupported, "Strategy already approved"); - strategies[_addr] = Strategy({ isSupported: true, _deprecated: 0 }); + strategies[_addr] = Strategy({ + isSupported: true, + isAMO: false, + mintForStrategy: 0, + mintForStrategyThreshold: 0 + }); allStrategies.push(_addr); emit StrategyApproved(_addr); } @@ -527,15 +538,16 @@ contract VaultAdmin is VaultStorage { } /** - * @notice Set OToken Metapool strategy - * @param _ousdMetaStrategy Address of OToken metapool strategy + * @notice Flag if a strategy is an AMO that is allowed to mint/burn OTokens. + * @param _strategyAddress Address of the strategy + * @param _isAMO true if AMO strategy */ - function setOusdMetaStrategy(address _ousdMetaStrategy) + function setAMOStrategy(address _strategyAddress, bool _isAMO) external onlyGovernor { - ousdMetaStrategy = _ousdMetaStrategy; - emit OusdMetaStrategyUpdated(_ousdMetaStrategy); + strategies[_strategyAddress].isAMO = _isAMO; + emit AMOStrategyUpdated(_strategyAddress, _isAMO); } /*************************************** diff --git a/contracts/contracts/vault/VaultCore.sol b/contracts/contracts/vault/VaultCore.sol index 3eea4968fd..3a7f4fa3d3 100644 --- a/contracts/contracts/vault/VaultCore.sol +++ b/contracts/contracts/vault/VaultCore.sol @@ -22,11 +22,6 @@ import "./VaultInitializer.sol"; contract VaultCore is VaultInitializer { using SafeERC20 for IERC20; using StableMath for uint256; - // max signed int - uint256 internal constant MAX_INT = 2**255 - 1; - // max un-signed int - uint256 internal constant MAX_UINT = - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /** * @dev Verifies that the rebasing is not paused. @@ -44,14 +39,6 @@ contract VaultCore is VaultInitializer { _; } - modifier onlyOusdMetaStrategy() { - require( - msg.sender == ousdMetaStrategy, - "Caller is not the OUSD meta strategy" - ); - _; - } - /** * @notice Deposit a supported asset and mint OTokens. * @param _asset Address of the asset being deposited @@ -97,7 +84,7 @@ contract VaultCore is VaultInitializer { } /** - * @notice Mint OTokens for a Metapool Strategy + * @notice Mint OTokens for an AMO strategy. * @param _amount Amount of the asset being deposited * * Notice: can't use `nonReentrant` modifier since the `mint` function can @@ -109,23 +96,27 @@ contract VaultCore is VaultInitializer { * that are moving funds between the Vault and end user wallets can influence strategies * utilizing this function. */ - function mintForStrategy(uint256 _amount) - external - whenNotCapitalPaused - onlyOusdMetaStrategy - { - require(_amount < MAX_INT, "Amount too high"); - - emit Mint(msg.sender, _amount); + function mintForStrategy(uint256 _amount) external whenNotCapitalPaused { + // read from storage + Strategy memory strategyConfig = strategies[msg.sender]; + // is AMO check done in here rather than a modifier to save a storage read (SLOAD) + require(strategyConfig.isAMO, "Caller is not an AMO strategy"); - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy += int256(_amount); + // toInt96 does a safe cast from uint256 to int96. + // the addition will revert if overflow of max int96 + int96 newMintForStrategy = strategyConfig.mintForStrategy + + toInt96(_amount); require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Minted ousd surpassed netOusdMintForStrategyThreshold." + abs(newMintForStrategy) < strategyConfig.mintForStrategyThreshold, + "OToken mint passes threshold" ); + // write back to storage + strategies[msg.sender].mintForStrategy = int96(newMintForStrategy); + + emit Mint(msg.sender, _amount); + // Mint matching amount of OTokens oUSD.mint(msg.sender, _amount); } @@ -216,7 +207,7 @@ contract VaultCore is VaultInitializer { } /** - * @notice Burn OTokens for Metapool Strategy + * @notice Burn OTokens for an AMO strategy * @param _amount Amount of OUSD to burn * * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could @@ -228,23 +219,27 @@ contract VaultCore is VaultInitializer { * that are moving funds between the Vault and end user wallets can influence strategies * utilizing this function. */ - function burnForStrategy(uint256 _amount) - external - whenNotCapitalPaused - onlyOusdMetaStrategy - { - require(_amount < MAX_INT, "Amount too high"); - - emit Redeem(msg.sender, _amount); + function burnForStrategy(uint256 _amount) external whenNotCapitalPaused { + // read from storage + Strategy memory strategyConfig = strategies[msg.sender]; + // is AMO check done in here rather than a modifier to save a storage read (SLOAD) + require(strategyConfig.isAMO, "Caller is not an AMO strategy"); - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy -= int256(_amount); + // toInt96 does a safe cast from uint256 to int96 + // the subtraction will revert if underflow of min int96 + int96 newMintForStrategy = strategyConfig.mintForStrategy - + toInt96(_amount); require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Attempting to burn too much OUSD." + abs(newMintForStrategy) < strategyConfig.mintForStrategyThreshold, + "OToken burn passes threshold" ); + // write back to storage + strategies[msg.sender].mintForStrategy = newMintForStrategy; + + emit Redeem(msg.sender, _amount); + // Burn OTokens oUSD.burn(msg.sender, _amount); } @@ -748,6 +743,17 @@ contract VaultCore is VaultInitializer { return allStrategies; } + /** + * @notice Gets the vault configuration of a supported strategy. + */ + function getStrategyConfig(address _strategy) + external + view + returns (VaultStorage.Strategy memory config) + { + config = strategies[_strategy]; + } + /** * @notice Returns whether the vault supports the asset * @param _asset address of the asset @@ -796,8 +802,7 @@ contract VaultCore is VaultInitializer { } } - function abs(int256 x) private pure returns (uint256) { - require(x < int256(MAX_INT), "Amount too high"); - return x >= 0 ? uint256(x) : uint256(-x); + function abs(int96 x) private pure returns (int96) { + return x >= 0 ? x : -x; } } diff --git a/contracts/contracts/vault/VaultStorage.sol b/contracts/contracts/vault/VaultStorage.sol index 066754e38f..54064b5fd6 100644 --- a/contracts/contracts/vault/VaultStorage.sol +++ b/contracts/contracts/vault/VaultStorage.sol @@ -32,7 +32,7 @@ contract VaultStorage is Initializable, Governable { event RebasePaused(); event RebaseUnpaused(); event VaultBufferUpdated(uint256 _vaultBuffer); - event OusdMetaStrategyUpdated(address _ousdMetaStrategy); + event AMOStrategyUpdated(address _addr, bool _isAMO); event RedeemFeeUpdated(uint256 _redeemFeeBps); event PriceProviderUpdated(address _priceProvider); event AllocateThresholdUpdated(uint256 _threshold); @@ -42,7 +42,10 @@ contract VaultStorage is Initializable, Governable { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); - event NetOusdMintForStrategyThresholdChanged(uint256 _threshold); + event MintForStrategyThresholdChanged( + address _strategy, + uint256 _threshold + ); event SwapperChanged(address _address); event SwapAllowedUndervalueChanged(uint256 _basis); event SwapSlippageChanged(address _asset, uint256 _basis); @@ -74,12 +77,21 @@ contract VaultStorage is Initializable, Governable { /// @dev list of all assets supported by the vault. address[] internal allAssets; - // Strategies approved for use by the Vault + /// @param isSupported flag if strategy is approved for use by the Vault + /// @param isAMO flag if AMO strategy that can mint and burn OTokens + /// @param mintForStrategy How much OTokens are currently minted by the strategy + /// @param mintForStrategyThreshold How much net total OTokens are allowed to be minted by the strategy + /// @dev The Strategy struct has been packed to fit in a single storage slot + /// 96-bits, signed integer is capped at 39b for 18 decimal numbers struct Strategy { bool isSupported; - uint256 _deprecated; // Deprecated storage slot + bool isAMO; + int96 mintForStrategy; + int96 mintForStrategyThreshold; + // uint256 _deprecated; // Deprecated unused storage slot } - /// @dev mapping of strategy contracts to their configiration + /// @dev strategy configiration. ie is supported and is AMO + // slither-disable-next-line uninitialized-state mapping(address => Strategy) internal strategies; /// @dev list of all vault strategies address[] internal allStrategies; @@ -137,14 +149,17 @@ contract VaultStorage is Initializable, Governable { uint256 constant MINT_MINIMUM_UNIT_PRICE = 0.998e18; - /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral - address public ousdMetaStrategy = address(0); + /// @dev Deprecated: AMO strategy that is allowed to mint/burn OTokens without changing collateral + // slither-disable-next-line constable-states + address private _deprecatedOusdMetaStrategy; - /// @notice How much OTokens are currently minted by the strategy - int256 public netOusdMintedForStrategy = 0; + /// @dev Deprecated: How much OTokens are currently minted by the strategy + // slither-disable-next-line constable-states + int256 private _deprecatedNetOusdMintedForStrategy; - /// @notice How much net total OTokens are allowed to be minted by all strategies - uint256 public netOusdMintForStrategyThreshold = 0; + /// @dev Deprecated: How much net total OTokens are allowed to be minted by all strategies + // slither-disable-next-line constable-states + uint256 private _deprecatedNetOusdMintForStrategyThreshold; uint256 constant MIN_UNIT_PRICE_DRIFT = 0.7e18; uint256 constant MAX_UNIT_PRICE_DRIFT = 1.3e18; @@ -175,4 +190,19 @@ contract VaultStorage is Initializable, Governable { sstore(position, newImpl) } } + + /** + * @dev Returns the downcasted int96 from uint256, reverting on + * overflow (when the input is greater than largest int96). + * + * Based off OZ's SafeCast but implemented here as the OZ version + * used doesn't support casting to int96. + */ + function toInt96(uint256 value) internal pure returns (int96) { + require( + value <= uint96(type(int96).max), + "value doesn't fit in 96 bits" + ); + return int96(uint96(value)); + } } diff --git a/contracts/deploy/000_mock.js b/contracts/deploy/000_mock.js index 32ff97d3e3..963f95157a 100644 --- a/contracts/deploy/000_mock.js +++ b/contracts/deploy/000_mock.js @@ -1,7 +1,7 @@ const { parseUnits } = require("ethers").utils; const { isMainnetOrFork } = require("../test/helpers"); const addresses = require("../utils/addresses"); -const { threeCRVPid } = require("../utils/constants"); +const { convex_3CRV_PID } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); const { hardhatSetBalance } = require("../test/_fund"); @@ -309,12 +309,12 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { args: [mockCVX.address, mockCRV.address, mockCVX.address], }); const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(threeCRVPid, threePoolToken.address); + await mockBooster.setPool(convex_3CRV_PID, threePoolToken.address); await deploy("MockRewardPool", { from: deployerAddr, args: [ - threeCRVPid, + convex_3CRV_PID, threePoolToken.address, mockCRV.address, mockCVX.address, diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index d107360379..05da47e9f5 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -10,10 +10,12 @@ const { } = require("../test/helpers.js"); const { deployWithConfirmation, withConfirmation } = require("../utils/deploy"); const { - threeCRVPid, - metapoolLPCRVPid, - lusdMetapoolLPCRVPid, - frxEthWethPoolLpPID, + convex_3CRV_PID, + convex_OUSD_3CRV_PID, + convex_LUSD_3CRV_PID, + convex_frxETH_WETH_PID, + convex_OETH_ETH_PID, + convex_frxETH_OETH_PID, } = require("../utils/constants"); const log = require("../utils/logger")("deploy:001_core"); @@ -173,7 +175,7 @@ const deployConvexStrategy = async () => { const cVaultProxy = await ethers.getContract("VaultProxy"); const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(threeCRVPid, assetAddresses.ThreePoolToken); + await mockBooster.setPool(convex_3CRV_PID, assetAddresses.ThreePoolToken); await deployWithConfirmation("ConvexStrategyProxy", [], null, true); const cConvexStrategyProxy = await ethers.getContract("ConvexStrategyProxy"); @@ -189,7 +191,7 @@ const deployConvexStrategy = async () => { ], [ mockBooster.address, // _cvxDepositorAddress, - threeCRVPid, // _cvxDepositorPTokenId + convex_3CRV_PID, // _cvxDepositorPTokenId ], ] ); @@ -235,7 +237,7 @@ const deployConvexFrxEthWethStrategy = async () => { const cVaultProxy = await ethers.getContract("OETHVaultProxy"); const mockBooster = await ethers.getContract("MockBooster"); await mockBooster.setPool( - frxEthWethPoolLpPID, + convex_frxETH_WETH_PID, assetAddresses.CurveFrxEthWethPool ); @@ -255,7 +257,7 @@ const deployConvexFrxEthWethStrategy = async () => { ], [ mockBooster.address, // _cvxDepositorAddress, - frxEthWethPoolLpPID, // _cvxDepositorPTokenId + convex_frxETH_WETH_PID, // _cvxDepositorPTokenId ], ] ); @@ -335,7 +337,7 @@ const deployConvexLUSDMetaStrategy = async () => { LUSD.address, // LUSD mockRewardPool.address, // _cvxRewardStakerAddress, assetAddresses.LUSDMetapoolToken, // metapoolLpToken - lusdMetapoolLPCRVPid, // _cvxDepositorPTokenId + convex_LUSD_3CRV_PID, // _cvxDepositorPTokenId ], ] ); @@ -366,40 +368,51 @@ const deployConvexOUSDMetaStrategy = async () => { "ConvexOUSDMetaStrategyProxy" ); + const mockBooster = await ethers.getContract("MockBooster"); + // Get the Convex rewards pool contract + const poolInfo = await mockBooster.poolInfo(convex_OUSD_3CRV_PID); + const mockRewardPool = await ethers.getContractAt( + "MockRewardPool", + poolInfo.crvRewards + ); + const ousd = await ethers.getContract("OUSDProxy"); + const dConvexOUSDMetaStrategy = await deployWithConfirmation( "ConvexOUSDMetaStrategy", [ - [assetAddresses.ThreePool, cVaultProxy.address], - [3, assetAddresses.ThreePool, assetAddresses.ThreePoolToken], + [assetAddresses.ThreePoolOUSDMetapool, cVaultProxy.address], + [ + ousd.address, // oTokenAddress, + assetAddresses.USDT, // vaultAssetAddress (USDT) + assetAddresses.ThreePoolToken, // poolAssetAddress (3CRV) + 0, // Curve pool index for OUSD + 1, // Curve pool index for 3CRV + ], + [ + mockBooster.address, // cvxDepositorAddress, + mockRewardPool.address, // cvxRewardStakerAddress, + convex_OUSD_3CRV_PID, // cvxDepositorPTokenId + ], + assetAddresses.ThreePool, // _curve3Pool + [assetAddresses.DAI, assetAddresses.USDC, assetAddresses.USDT], // _curve3PoolAssets ] ); + const cConvexOUSDMetaStrategy = await ethers.getContractAt( "ConvexOUSDMetaStrategy", cConvexOUSDMetaStrategyProxy.address ); // Initialize Strategies - const mockBooster = await ethers.getContract("MockBooster"); - const mockRewardPool = await ethers.getContract("MockRewardPool"); - const ousd = await ethers.getContract("OUSDProxy"); - const initData = cConvexOUSDMetaStrategy.interface.encodeFunctionData( - "initialize(address[],address[],address[],(address,address,address,address,address,uint256))", + "initialize(address[],address[],address[])", [ [assetAddresses.CVX, assetAddresses.CRV], [assetAddresses.DAI, assetAddresses.USDC, assetAddresses.USDT], [ - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - assetAddresses.ThreePoolToken, - ], - [ - mockBooster.address, // _cvxDepositorAddress, - assetAddresses.ThreePoolOUSDMetapool, // metapool address, - ousd.address, // _ousdAddress, - mockRewardPool.address, // _cvxRewardStakerAddress, - assetAddresses.ThreePoolOUSDMetapool, // metapoolLpToken (metapool address), - metapoolLPCRVPid, // _cvxDepositorPTokenId + assetAddresses.ThreePoolOUSDMetapool, + assetAddresses.ThreePoolOUSDMetapool, + assetAddresses.ThreePoolOUSDMetapool, ], ] ); @@ -416,6 +429,136 @@ const deployConvexOUSDMetaStrategy = async () => { return cConvexOUSDMetaStrategy; }; +/** + * Deploys a Convex AMO Strategy for the Curve OETH/ETH pool + */ +const deployConvexOethEthAMOStrategy = async () => { + const assetAddresses = await getAssetAddresses(deployments); + const { governorAddr } = await getNamedAccounts(); + + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + + await deployWithConfirmation("ConvexEthMetaStrategyProxy", [], null, true); + const cConvexEthMetaStrategyProxy = await ethers.getContract( + "ConvexEthMetaStrategyProxy" + ); + + const mockBooster = await ethers.getContract("MockBooster"); + // Get the Convex rewards pool contract + const poolInfo = await mockBooster.poolInfo(convex_OETH_ETH_PID); + + const oeth = await ethers.getContract("OETHProxy"); + + const dConvexEthMetaStrategy = await deployWithConfirmation( + "ConvexEthMetaStrategy", + [ + [assetAddresses.CurveOethEthPool, cVaultProxy.address], + [ + oeth.address, // oTokenAddress, + assetAddresses.WETH, // vaultAssetAddress (WETH) + addresses.ETH, // poolAssetAddress (ETH) + 1, // Curve pool index for OToken OETH + 0, // Curve pool index for asset ETH + ], + [ + mockBooster.address, // cvxDepositorAddress, + poolInfo.crvRewards, // cvxRewardStakerAddress, + convex_OETH_ETH_PID, // cvxDepositorPTokenId + ], + ] + ); + const cConvexEthMetaStrategy = await ethers.getContractAt( + "ConvexEthMetaStrategy", + cConvexEthMetaStrategyProxy.address + ); + + // Initialize Strategies + const initData = cConvexEthMetaStrategy.interface.encodeFunctionData( + "initialize(address[],address[],address[])", + [ + [assetAddresses.CVX, assetAddresses.CRV], + [assetAddresses.WETH], + [assetAddresses.CurveOethEthPool], + ] + ); + + await withConfirmation( + cConvexEthMetaStrategyProxy["initialize(address,address,bytes)"]( + dConvexEthMetaStrategy.address, + governorAddr, + initData + ) + ); + log("Initialized ConvexEthMetaStrategyProxy"); + + return cConvexEthMetaStrategy; +}; + +/** + * Deploys a Convex AMO Strategy for the Curve frxETH/OETH pool + */ +const deployConvexFrxETHAMOStrategy = async () => { + const assetAddresses = await getAssetAddresses(deployments); + const { governorAddr } = await getNamedAccounts(); + + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + + await deployWithConfirmation("ConvexFrxETHAMOStrategyProxy", [], null, true); + const cConvexFrxETHAMOStrategyProxy = await ethers.getContract( + "ConvexFrxETHAMOStrategyProxy" + ); + + const mockBooster = await ethers.getContract("MockBooster"); + // Get the Convex rewards pool contract + const poolInfo = await mockBooster.poolInfo(convex_frxETH_OETH_PID); + + const oeth = await ethers.getContract("OETHProxy"); + + const dConvexFrxETHAMOStrategy = await deployWithConfirmation( + "ConvexFrxETHAMOStrategy", + [ + [assetAddresses.CurveFrxEthOethPool, cVaultProxy.address], + [ + oeth.address, // oTokenAddress, + assetAddresses.frxETH, // vaultAssetAddress (frxETH) + assetAddresses.frxETH, // poolAssetAddress (frxETH) + 1, // Curve pool index for OToken OETH + 0, // Curve pool index for asset frxETH + ], + [ + mockBooster.address, // cvxDepositorAddress, + poolInfo.crvRewards, // cvxRewardStakerAddress, + convex_frxETH_OETH_PID, // cvxDepositorPTokenId + ], + ] + ); + const cConvexFrxETHAMOStrategy = await ethers.getContractAt( + "ConvexFrxETHAMOStrategy", + cConvexFrxETHAMOStrategyProxy.address + ); + + // Initialize Strategies + const initData = cConvexFrxETHAMOStrategy.interface.encodeFunctionData( + "initialize(address[],address[],address[])", + [ + [assetAddresses.CVX, assetAddresses.CRV], + [assetAddresses.frxETH], + [assetAddresses.CurveFrxEthOethPool], + ] + ); + + await withConfirmation( + cConvexFrxETHAMOStrategyProxy["initialize(address,address,bytes)"]( + dConvexFrxETHAMOStrategy.address, + governorAddr, + initData + ) + ); + log("Initialized cConvexFrxETHAMOStrategyProxy"); + + return cConvexFrxETHAMOStrategy; +}; + /** * Configure Vault by adding supported assets and Strategies. */ @@ -460,7 +603,7 @@ const configureVault = async () => { }; /** - * Configure OETH Vault by adding supported assets and Strategies. + * Configure OETH Vault by adding supported assets, unpause capital and set strategist. */ const configureOETHVault = async () => { const assetAddresses = await getAssetAddresses(deployments); @@ -919,37 +1062,69 @@ const deployCore = async () => { log("Initialized OETH"); }; -// deploy curve metapool mocks +// deploy Curve OUSD/3Crv Metapool mocks const deployCurveMetapoolMocks = async () => { - const ousd = await ethers.getContract("OUSDProxy"); const { deployerAddr } = await hre.getNamedAccounts(); const assetAddresses = await getAssetAddresses(deployments); + const ousd = await ethers.getContract("OUSDProxy"); + await hre.deployments.deploy("MockCurveMetapool", { from: deployerAddr, args: [[ousd.address, assetAddresses.ThreePoolToken]], }); - const metapoolToken = await ethers.getContract("MockCurveMetapool"); + const curveLpToken = await ethers.getContract("MockCurveMetapool"); const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(metapoolLPCRVPid, metapoolToken.address); + await mockBooster.setPool(convex_OUSD_3CRV_PID, curveLpToken.address); }; -// deploy curve metapool mocks +// deploy Curve LUSD/3Crv Metapool mocks const deployCurveLUSDMetapoolMocks = async () => { const { deployerAddr } = await hre.getNamedAccounts(); const assetAddresses = await getAssetAddresses(deployments); - const LUSD = await ethers.getContract("MockLUSD"); - await hre.deployments.deploy("MockCurveLUSDMetapool", { from: deployerAddr, - args: [[LUSD.address, assetAddresses.ThreePoolToken]], + args: [[assetAddresses.LUSD, assetAddresses.ThreePoolToken]], + }); + + const curveLpToken = await ethers.getContract("MockCurveLUSDMetapool"); + const mockBooster = await ethers.getContract("MockBooster"); + await mockBooster.setPool(convex_LUSD_3CRV_PID, curveLpToken.address); +}; + +// deploy Curve OETH/ETH Pool mocks +const deployCurveOethEthPoolMocks = async () => { + const { deployerAddr } = await hre.getNamedAccounts(); + + const oeth = await ethers.getContract("OETHProxy"); + + await hre.deployments.deploy("MockCurveOethEthPool", { + from: deployerAddr, + args: [[addresses.ETH, oeth.address]], + }); + + const curveLpToken = await ethers.getContract("MockCurveOethEthPool"); + const mockBooster = await ethers.getContract("MockBooster"); + await mockBooster.setPool(convex_OETH_ETH_PID, curveLpToken.address); +}; + +// deploy Curve frxETH/OETH Pool mocks +const deployCurvefrxEthOethPoolMocks = async () => { + const { deployerAddr } = await hre.getNamedAccounts(); + const assetAddresses = await getAssetAddresses(deployments); + + const oeth = await ethers.getContract("OETHProxy"); + + await hre.deployments.deploy("MockCurveFrxEthOethPool", { + from: deployerAddr, + args: [[assetAddresses.frxETH, oeth.address]], }); - const LUSDMetapoolToken = await ethers.getContract("MockCurveLUSDMetapool"); + const curveLpToken = await ethers.getContract("MockCurveFrxEthOethPool"); const mockBooster = await ethers.getContract("MockBooster"); - await mockBooster.setPool(lusdMetapoolLPCRVPid, LUSDMetapoolToken.address); + await mockBooster.setPool(convex_frxETH_OETH_PID, curveLpToken.address); }; // Deploy the Flipper trading contract @@ -1217,12 +1392,16 @@ const main = async () => { await deployCore(); await deployCurveMetapoolMocks(); await deployCurveLUSDMetapoolMocks(); + await deployCurveOethEthPoolMocks(); + await deployCurvefrxEthOethPoolMocks(); await deployCompoundStrategy(); await deployAaveStrategy(); await deployThreePoolStrategy(); await deployConvexStrategy(); await deployConvexOUSDMetaStrategy(); await deployConvexLUSDMetaStrategy(); + await deployConvexOethEthAMOStrategy(); + await deployConvexFrxETHAMOStrategy(); await deployConvexFrxEthWethStrategy(); await deployFraxEthStrategy(); const [harvesterProxy, oethHarvesterProxy] = await deployHarvesters(); diff --git a/contracts/deploy/045_convex_lusd_meta_strategy.js b/contracts/deploy/045_convex_lusd_meta_strategy.js index bf59b333e7..13a34086f1 100644 --- a/contracts/deploy/045_convex_lusd_meta_strategy.js +++ b/contracts/deploy/045_convex_lusd_meta_strategy.js @@ -1,5 +1,5 @@ const generalizedConvexStratDeployment = require("../utils/generalizedConvexStratDeployment"); -const { lusdMetapoolLPCRVPid } = require("../utils/constants"); +const { convex_LUSD_3CRV_PID } = require("../utils/constants"); module.exports = generalizedConvexStratDeployment({ deployName: "045_convex_lusd_meta_strategy", @@ -14,7 +14,7 @@ module.exports = generalizedConvexStratDeployment({ metapoolLPToken: "0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA", mainTokenAddress: "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0", // LUSD cvxRewardStakerAddress: "0x2ad92A7aE036a038ff02B96c88de868ddf3f8190", - cvxDepositorPTokenId: lusdMetapoolLPCRVPid, // 33 + cvxDepositorPTokenId: convex_LUSD_3CRV_PID, // 33 redeployVault: false, deployStrategyImplementation: true, skipMainnetDeploy: false, diff --git a/contracts/deploy/055_curve_amo.js b/contracts/deploy/055_curve_amo.js index 0da9ebef45..b8827ea92e 100644 --- a/contracts/deploy/055_curve_amo.js +++ b/contracts/deploy/055_curve_amo.js @@ -1,10 +1,12 @@ -const { deploymentWithGuardianGovernor } = require("../utils/deploy"); const addresses = require("../utils/addresses"); const hre = require("hardhat"); const { utils, Contract } = require("ethers"); + const { getAssetAddresses, isMainnet } = require("../test/helpers.js"); -const { MAX_UINT256, oethPoolLpPID } = require("../utils/constants"); +const { MAX_UINT256, convex_OETH_ETH_PID } = require("../utils/constants"); +const { deploymentWithGuardianGovernor } = require("../utils/deploy"); const { impersonateAndFund } = require("../utils/signers"); + const crvRewards = "0x24b65DC1cf053A8D96872c323d29e86ec43eB33A"; const poolAddress = "0x94b17476a93b3262d87b9a326965d1e91f9c13e7"; const tokenAddress = "0x94b17476a93b3262d87b9a326965d1e91f9c13e7"; @@ -147,7 +149,7 @@ const deployConvexETHMetaStrategy = async ({ addresses.mainnet.OETHProxy, crvRewards, tokenAddress, - oethPoolLpPID, + convex_OETH_ETH_PID, ] ) ); diff --git a/contracts/deploy/075_oeth_amo_upgrade.js b/contracts/deploy/075_oeth_amo_upgrade.js index da5c690045..b2a35a0609 100644 --- a/contracts/deploy/075_oeth_amo_upgrade.js +++ b/contracts/deploy/075_oeth_amo_upgrade.js @@ -1,5 +1,5 @@ const addresses = require("../utils/addresses"); -const { oethPoolLpPID } = require("../utils/constants"); +const { convex_OETH_ETH_PID } = require("../utils/constants"); const { deploymentWithGovernanceProposal } = require("../utils/deploy"); module.exports = deploymentWithGovernanceProposal( @@ -25,7 +25,7 @@ module.exports = deploymentWithGovernanceProposal( [ addresses.mainnet.CVXBooster, addresses.mainnet.CVXETHRewardsPool, - oethPoolLpPID, + convex_OETH_ETH_PID, addresses.mainnet.OETHProxy, addresses.mainnet.WETH, ], diff --git a/contracts/deploy/078_convex_frax_strategy.js b/contracts/deploy/078_convex_frax_strategy.js index 9aca937d42..e59b84780c 100644 --- a/contracts/deploy/078_convex_frax_strategy.js +++ b/contracts/deploy/078_convex_frax_strategy.js @@ -1,12 +1,12 @@ const addresses = require("../utils/addresses"); -const { frxEthWethPoolLpPID } = require("../utils/constants"); +const { convex_frxETH_WETH_PID } = require("../utils/constants"); const { deploymentWithGovernanceProposal } = require("../utils/deploy"); module.exports = deploymentWithGovernanceProposal( { deployName: "078_convex_frax_strategy", forceDeploy: false, - // forceSkip: true, + forceSkip: true, reduceQueueTime: true, deployerIsProposer: true, // proposalId: "", @@ -46,7 +46,7 @@ module.exports = deploymentWithGovernanceProposal( addresses.mainnet.CurveFrxEthWethPool, // Curve pool addresses.mainnet.CurveFrxEthWethPool, // Curve LP token ], - [addresses.mainnet.CVXBooster, frxEthWethPoolLpPID], + [addresses.mainnet.CVXBooster, convex_frxETH_WETH_PID], ] ); const cConvexFrxEthWethStrategy = await ethers.getContractAt( diff --git a/contracts/deploy/079_ousd_amo_upgrade.js b/contracts/deploy/079_ousd_amo_upgrade.js new file mode 100644 index 0000000000..10cc61566c --- /dev/null +++ b/contracts/deploy/079_ousd_amo_upgrade.js @@ -0,0 +1,102 @@ +const { parseUnits } = require("ethers/lib/utils"); + +const addresses = require("../utils/addresses"); +const { convex_OUSD_3CRV_PID } = require("../utils/constants"); +const { deploymentWithGovernanceProposal } = require("../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "079_ousd_amo_upgrade", + forceDeploy: false, + // forceSkip: true, + reduceQueueTime: false, + deployerIsProposer: true, + // proposalId: "", + }, + async ({ ethers, deployWithConfirmation }) => { + // 1. Deploy new OUSD Vault Core and Admin implementations + // Need to override the storage safety check as we are changing the Strategy struct + const dVaultCore = await deployWithConfirmation( + "VaultCore", + [], + null, + true + ); + const dVaultAdmin = await deployWithConfirmation( + "VaultAdmin", + [], + null, + true + ); + + // Connect to the OUSD Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cVault = await ethers.getContractAt("Vault", cVaultProxy.address); + + const cConvexOUSDMetaStrategyProxy = await ethers.getContract( + "ConvexOUSDMetaStrategyProxy" + ); + + // Deploy and set the immutable variables + const dConvexOUSDMetaStrategy = await deployWithConfirmation( + "ConvexOUSDMetaStrategy", + [ + [addresses.mainnet.CurveOUSDMetaPool, addresses.mainnet.VaultProxy], + [ + addresses.mainnet.OUSDProxy, // oTokenAddress (OUSD), + addresses.mainnet.USDT, // vaultAssetAddress (USDT) + addresses.mainnet.ThreePoolToken, // poolAssetAddress (3CRV) + 0, // Curve pool index for OUSD + 1, // Curve pool index for 3CRV + ], + [ + addresses.mainnet.CVXBooster, // cvxDepositorAddress, + addresses.mainnet.CVXRewardsPool, // cvxRewardStakerAddress, + convex_OUSD_3CRV_PID, // cvxDepositorPTokenId + ], + addresses.mainnet.ThreePool, // _curve3Pool + [addresses.mainnet.DAI, addresses.mainnet.USDC, addresses.mainnet.USDT], // _curve3PoolAssets + ], + null, + true // force deploy as storage slots have changed + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade the OUSD AMO strategy.", + actions: [ + // 1. Upgrade the OUSD Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVaultCore.address], + }, + // 2. set OUSD Vault proxy to the new admin vault implementation + { + contract: cVault, + signature: "setAdminImpl(address)", + args: [dVaultAdmin.address], + }, + // 3. Flag the existing AMO strategy for Curve OUSD/3CRV pool to be an AMO in the OUSD Vault + { + contract: cVault, + signature: "setAMOStrategy(address,bool)", + args: [cConvexOUSDMetaStrategyProxy.address, true], + }, + // 4. Reset the mint threshold for the old AMO strategy as its storage has changed to 50m + { + contract: cVault, + signature: "setMintForStrategyThreshold(address,uint256)", + args: [cConvexOUSDMetaStrategyProxy.address, parseUnits("50", 24)], + }, + // 5. Upgrade the OUSD AMO strategy proxy to the new strategy implementation + { + contract: cConvexOUSDMetaStrategyProxy, + signature: "upgradeTo(address)", + args: [dConvexOUSDMetaStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/083_frax_amo.js b/contracts/deploy/083_frax_amo.js new file mode 100644 index 0000000000..1e4e5f525d --- /dev/null +++ b/contracts/deploy/083_frax_amo.js @@ -0,0 +1,172 @@ +const { parseUnits } = require("ethers/lib/utils"); + +const addresses = require("../utils/addresses"); +const { convex_frxETH_OETH_PID } = require("../utils/constants"); +const { deploymentWithGovernanceProposal } = require("../utils/deploy"); +const { getTxOpts } = require("../utils/tx"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "083_frax_amo", + forceDeploy: false, + // forceSkip: true, + reduceQueueTime: false, + deployerIsProposer: true, + // proposalId: "", + }, + async ({ ethers, deployWithConfirmation, withConfirmation }) => { + const { deployerAddr, timelockAddr } = await getNamedAccounts(); + const sDeployer = await ethers.provider.getSigner(deployerAddr); + + // 1. Deploy new OETH Vault Core and Admin implementations + // Need to override the storage safety check as we are changing the Strategy struct + const dVaultCore = await deployWithConfirmation( + "OETHVaultCore", + [], + null, + true + ); + const dVaultAdmin = await deployWithConfirmation( + "OETHVaultAdmin", + [], + null, + true + ); + + // Connect to the OETH Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cVault = await ethers.getContractAt("OETHVault", cVaultProxy.address); + + // 2. Deploy new frxETH/OETH AMO strategy + // Deploy proxy + const dConvexFrxETHAMOStrategyProxy = await deployWithConfirmation( + "ConvexFrxETHAMOStrategyProxy" + ); + const cConvexFrxETHAMOStrategyProxy = await ethers.getContract( + "ConvexFrxETHAMOStrategyProxy" + ); + + // Deploy and set the immutable variables of implementation + const dConvexFrxETHAMOStrategy = await deployWithConfirmation( + "ConvexFrxETHAMOStrategy", + [ + [ + addresses.mainnet.CurveFrxETHOETHPool, + addresses.mainnet.OETHVaultProxy, + ], + [ + addresses.mainnet.OETHProxy, // oTokenAddress (OETH), + addresses.mainnet.frxETH, // vaultAssetAddress (frxETH) + addresses.mainnet.frxETH, // poolAssetAddress (frxETH) + 1, // Curve pool index for OToken OETH + 0, // Curve pool index for asset frxETH + ], + [ + addresses.mainnet.CVXBooster, // cvxDepositorAddress, + addresses.mainnet.CVXFrxETHRewardsPool, // cvxRewardStakerAddress, + convex_frxETH_OETH_PID, // cvxDepositorPTokenId + ], + ] + ); + + const cConvexFrxETHAMOStrategy = await ethers.getContractAt( + "ConvexFrxETHAMOStrategy", + dConvexFrxETHAMOStrategyProxy.address + ); + + // 3. Initialize the new frxETH/OETH AMO strategy + // Construct initialize call data to init and configure the new strategy + const initData = cConvexFrxETHAMOStrategy.interface.encodeFunctionData( + "initialize(address[],address[],address[])", + [ + [addresses.mainnet.CRV, addresses.mainnet.CVX], + [addresses.mainnet.frxETH], + [addresses.mainnet.CurveFrxETHOETHPool], + ] + ); + + // prettier-ignore + await withConfirmation( + cConvexFrxETHAMOStrategyProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dConvexFrxETHAMOStrategy.address, + timelockAddr, + initData, + await getTxOpts() + ) + ); + console.log("Initialized Curve frxETH/ETH AMO Strategy"); + + const cHarvester = await ethers.getContractAt( + "OETHHarvester", + addresses.mainnet.OETHHarvesterProxy + ); + + const cConvexEthMetaStrategy = await ethers.getContractAt( + "ConvexEthMetaStrategy", + addresses.mainnet.ConvexOETHAMOStrategy + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade OETH Vault and deploy new AMO strategy for Curve frxETH/OETH pool.", + actions: [ + // 1. Upgrade the OETH Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVaultCore.address], + }, + // 2. set OETH Vault proxy to the new admin vault implementation + { + contract: cVault, + signature: "setAdminImpl(address)", + args: [dVaultAdmin.address], + }, + // 3. Flag the existing AMO strategy for Curve OETH/ETH pool to be an AMO in the OETH Vault + { + contract: cVault, + signature: "setAMOStrategy(address,bool)", + args: [cConvexEthMetaStrategy.address, true], + }, + // 4. Reset the mint threshold for the old AMO strategy as its storage has changed + { + contract: cVault, + signature: "setMintForStrategyThreshold(address,uint256)", + args: [cConvexEthMetaStrategy.address, parseUnits("25000")], + }, + // 5. Approve the new frxETH AMO strategy in the OETH Vault + { + contract: cVault, + signature: "approveStrategy(address)", + args: [cConvexFrxETHAMOStrategy.address], + }, + // 6. Flag the new AMO strategy for Curve frxETH/OETH pool to be an AMO in the OETH Vault + { + contract: cVault, + signature: "setAMOStrategy(address,bool)", + args: [cConvexFrxETHAMOStrategy.address, true], + }, + // 7. Set the mint threshold for the new frxETH AMO strategy + { + contract: cVault, + signature: "setMintForStrategyThreshold(address,uint256)", + args: [cConvexFrxETHAMOStrategy.address, parseUnits("25000")], + }, + // 8. Add the new frxETH AMO strategy to the OETH Harvester + { + contract: cHarvester, + signature: "setSupportedStrategy(address,bool)", + args: [cConvexFrxETHAMOStrategy.address, true], + }, + // 9. Set the harvester address on the new frxETH AMO strategy + { + contract: cConvexFrxETHAMOStrategy, + signature: "setHarvesterAddress(address)", + args: [cHarvester.address], + }, + ], + }; + } +); diff --git a/contracts/docs/AMOContractHierarchy.svg b/contracts/docs/AMOContractHierarchy.svg new file mode 100644 index 0000000000..e6542d8d9d --- /dev/null +++ b/contracts/docs/AMOContractHierarchy.svg @@ -0,0 +1,142 @@ + + + + + + +UmlClassDiagram + + + +18 + +Governable +../contracts/governance/Governable.sol + + + +242 + +BalancerEthAMOStrategy +../contracts/strategies/amo/BalancerEthAMOStrategy.sol + + + +245 + +<<Abstract>> +BaseBalancerAMOStrategy +../contracts/strategies/amo/BaseBalancerAMOStrategy.sol + + + +242->245 + + + + + +243 + +<<Abstract>> +BaseAMOStrategy +../contracts/strategies/amo/BaseAMOStrategy.sol + + + +183 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol + + + +243->183 + + + + + +245->243 + + + + + +247 + +<<Abstract>> +BaseConvexAMOStrategy +../contracts/strategies/amo/BaseConvexAMOStrategy.sol + + + +247->243 + + + + + +249 + +ConvexEthMetaStrategy +../contracts/strategies/amo/ConvexEthMetaStrategy.sol + + + +249->247 + + + + + +250 + +ConvexFrxETHAMOStrategy +../contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol + + + +250->247 + + + + + +251 + +ConvexOUSDMetaStrategy +../contracts/strategies/amo/ConvexOUSDMetaStrategy.sol + + + +251->247 + + + + + +182 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +183->18 + + + + + +183->182 + + + + + diff --git a/contracts/docs/AaveStrategyHierarchy.svg b/contracts/docs/AaveStrategyHierarchy.svg index 74f116b8ab..17ad4614b9 100644 --- a/contracts/docs/AaveStrategyHierarchy.svg +++ b/contracts/docs/AaveStrategyHierarchy.svg @@ -16,44 +16,44 @@ Governable ../contracts/governance/Governable.sol - + -150 +141 AaveStrategy ../contracts/strategies/AaveStrategy.sol - + -195 +183 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -150->195 +141->183 - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/AaveStrategySquashed.svg b/contracts/docs/AaveStrategySquashed.svg index 21d2e01374..c5e45fe801 100644 --- a/contracts/docs/AaveStrategySquashed.svg +++ b/contracts/docs/AaveStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -150 +141 AaveStrategy ../contracts/strategies/AaveStrategy.sol diff --git a/contracts/docs/BalancerEthAMOStrategyHierarchy.svg b/contracts/docs/BalancerEthAMOStrategyHierarchy.svg new file mode 100644 index 0000000000..086692b329 --- /dev/null +++ b/contracts/docs/BalancerEthAMOStrategyHierarchy.svg @@ -0,0 +1,89 @@ + + + + + + +UmlClassDiagram + + + +18 + +Governable +../contracts/governance/Governable.sol + + + +242 + +BalancerEthAMOStrategy +../contracts/strategies/amo/BalancerEthAMOStrategy.sol + + + +245 + +<<Abstract>> +BaseBalancerAMOStrategy +../contracts/strategies/amo/BaseBalancerAMOStrategy.sol + + + +242->245 + + + + + +243 + +<<Abstract>> +BaseAMOStrategy +../contracts/strategies/amo/BaseAMOStrategy.sol + + + +183 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol + + + +243->183 + + + + + +245->243 + + + + + +182 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +183->18 + + + + + +183->182 + + + + + diff --git a/contracts/docs/BalancerEthAMOStrategySquashed.svg b/contracts/docs/BalancerEthAMOStrategySquashed.svg new file mode 100644 index 0000000000..65279f7ab8 --- /dev/null +++ b/contracts/docs/BalancerEthAMOStrategySquashed.svg @@ -0,0 +1,137 @@ + + + + + + +UmlClassDiagram + + + +242 + +BalancerEthAMOStrategy +../contracts/strategies/amo/BalancerEthAMOStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> +   MAX_SLIPPAGE: uint256 <<BaseAMOStrategy>> +   lpToken: IERC20 <<BaseAMOStrategy>> +   oToken: IERC20 <<BaseAMOStrategy>> +   asset: IERC20 <<BaseAMOStrategy>> +   oTokenCoinIndex: uint128 <<BaseAMOStrategy>> +   assetCoinIndex: uint128 <<BaseAMOStrategy>> +   balancerVault: IBalancerVault <<BaseBalancerAMOStrategy>> +   balancerPoolId: bytes32 <<BaseBalancerAMOStrategy>> +   auraRewardPool: address <<BaseBalancerAMOStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, _pToken: address) <<BalancerEthAMOStrategy>> +    _isVaultAsset(_vaultAsset: address): (supported: bool) <<BaseAMOStrategy>> +    _isVaultAssets(_vaultAssets: address[]): bool <<BaseAMOStrategy>> +    _toPoolAsset(address, assets: uint256): (poolAssets: uint256) <<BalancerEthAMOStrategy>> +    _calcPoolAsset(address, vaultAssetAmount: uint256): (poolAssetAmount: uint256) <<BalancerEthAMOStrategy>> +    _toOTokens(poolAssetAmount: uint256): (oTokenAmount: uint256) <<BalancerEthAMOStrategy>> +    _getCoinIndex(_asset: address): uint128 <<BaseAMOStrategy>> +    _addLiquidityToPool(poolAmounts: uint256[2], minMintAmount: uint256): (lpDeposited: uint256) <<BaseBalancerAMOStrategy>> +    _removeLiquidityFromPool(lpTokens: uint256, poolAssetAmounts: uint256[2]) <<BaseBalancerAMOStrategy>> +    _removeOneSidedLiquidityFromPool(poolAsset: address, lpTokens: uint256, poolAssetAmount: uint256): (coinsRemoved: uint256) <<BaseBalancerAMOStrategy>> +    _getBalances(): (balances: uint256[2]) <<BaseBalancerAMOStrategy>> +    _getBalance(poolAsset: address): (balance: uint256) <<BaseBalancerAMOStrategy>> +    _getVirtualPrice(): (virtualPrice: uint256) <<BaseBalancerAMOStrategy>> +    _withdrawAsset(address, vaultAssetAmount: uint256, _recipient: address) <<BalancerEthAMOStrategy>> +    _withdrawAllAsset(_recipient: address) <<BalancerEthAMOStrategy>> +    _stakeCurveLp(lpAmount: uint256) <<BaseBalancerAMOStrategy>> +    _unStakeLpTokens(_lpAmount: uint256) <<BaseBalancerAMOStrategy>> +    _unStakeAllLpTokens() <<BaseBalancerAMOStrategy>> +    _deposit(_poolAssetAmount: uint256) <<BaseAMOStrategy>> +    _withdraw(_recipient: address, _vaultAsset: address, _vaultAssetAmount: uint256) <<BaseAMOStrategy>> +    calcLpTokensToBurn(_vaultAssetAmount: uint256): (lpToBurn: uint256) <<BaseAMOStrategy>> +    _withdrawAndRemoveFromPool(_lpTokens: uint256, removeAsset: address): (coinsRemoved: uint256) <<BaseAMOStrategy>> +    _approveBase() <<BaseBalancerAMOStrategy>> +    _max(a: int256, b: int256): int256 <<BaseAMOStrategy>> +    _removeBalancerLiquidity(lpTokens: uint256, poolAssetsAmountsOut: uint256[]) <<BaseBalancerAMOStrategy>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<BaseBalancerAMOStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<BaseAMOStrategy>> +    deposit(_vaultAsset: address, _vaultAssetAmount: uint256) <<onlyVault, onlyAsset, nonReentrant>> <<BaseAMOStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<BaseAMOStrategy>> +    withdraw(_recipient: address, _vaultAsset: address, _vaultAssetAmount: uint256) <<onlyVault, onlyAsset, nonReentrant>> <<BaseAMOStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BaseAMOStrategy>> +    supportsAsset(_vaultAsset: address): bool <<BaseAMOStrategy>> +    deposit(_vaultAssets: address[], _vaultAssetAmounts: uint256[]) <<onlyVault, onlyAssets, nonReentrant>> <<BaseAMOStrategy>> +    withdraw(_recipient: address, _vaultAssets: address[], _vaultAssetAmounts: uint256[]) <<onlyVault, onlyAssets, nonReentrant>> <<BaseAMOStrategy>> +    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    initialize(_rewardTokenAddresses: address[]) <<onlyGovernor, initializer>> <<BaseBalancerAMOStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyAsset(_vaultAsset: address) <<BaseAMOStrategy>> +    <<modifier>> onlyAssets(_vaultAssets: address[]) <<BaseAMOStrategy>> +    <<modifier>> onlyStrategist() <<BaseAMOStrategy>> +    <<modifier>> improvePoolBalance() <<BaseAMOStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<BalancerEthAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig) <<BaseAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig, _balancerConfig: BalancerConfig) <<BalancerEthAMOStrategy>> + + + diff --git a/contracts/docs/BalancerEthAMOStrategyStorage.svg b/contracts/docs/BalancerEthAMOStrategyStorage.svg new file mode 100644 index 0000000000..1217cdacbe --- /dev/null +++ b/contracts/docs/BalancerEthAMOStrategyStorage.svg @@ -0,0 +1,149 @@ + + + + + + +StorageDiagram + + + +3 + +BalancerEthAMOStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) + + + +1 + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:8->1 + + + + + +2 + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:13->2 + + + + + diff --git a/contracts/docs/CompStrategyHierarchy.svg b/contracts/docs/CompStrategyHierarchy.svg new file mode 100644 index 0000000000..3854581834 --- /dev/null +++ b/contracts/docs/CompStrategyHierarchy.svg @@ -0,0 +1,75 @@ + + + + + + +UmlClassDiagram + + + +18 + +Governable +../contracts/governance/Governable.sol + + + +142 + +<<Abstract>> +BaseCompoundStrategy +../contracts/strategies/BaseCompoundStrategy.sol + + + +183 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol + + + +142->183 + + + + + +146 + +CompoundStrategy +../contracts/strategies/CompoundStrategy.sol + + + +146->142 + + + + + +182 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +183->18 + + + + + +183->182 + + + + + diff --git a/contracts/docs/CompStrategySquashed.svg b/contracts/docs/CompStrategySquashed.svg new file mode 100644 index 0000000000..f74bc1fce8 --- /dev/null +++ b/contracts/docs/CompStrategySquashed.svg @@ -0,0 +1,99 @@ + + + + + + +UmlClassDiagram + + + +146 + +CompoundStrategy +../contracts/strategies/CompoundStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +   __reserved: int256[50] <<BaseCompoundStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, _pToken: address) <<CompoundStrategy>> +    _getCTokenFor(_asset: address): ICERC20 <<BaseCompoundStrategy>> +    _convertUnderlyingToCToken(_cToken: ICERC20, _underlying: uint256): (amount: uint256) <<BaseCompoundStrategy>> +    _deposit(_asset: address, _amount: uint256) <<CompoundStrategy>> +    _checkBalance(_cToken: ICERC20): (balance: uint256) <<CompoundStrategy>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<CompoundStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<CompoundStrategy>> +    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CompoundStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<CompoundStrategy>> +    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<CompoundStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<CompoundStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<CompoundStrategy>> +    supportsAsset(_asset: address): bool <<BaseCompoundStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<CompoundStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<event>> SkippedWithdrawal(asset: address, amount: uint256) <<CompoundStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_stratConfig: BaseStrategyConfig) <<CompoundStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> + + + diff --git a/contracts/docs/CompStrategyStorage.svg b/contracts/docs/CompStrategyStorage.svg new file mode 100644 index 0000000000..22929beac6 --- /dev/null +++ b/contracts/docs/CompStrategyStorage.svg @@ -0,0 +1,129 @@ + + + + + + +StorageDiagram + + + +3 + +CompoundStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +157-206 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +int256[50]: BaseCompoundStrategy.__reserved (1600) + + + +1 + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:8->1 + + + + + +2 + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:13->2 + + + + + diff --git a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg b/contracts/docs/ConvexEthMetaStrategyHierarchy.svg index cce9738332..b179ecf06a 100644 --- a/contracts/docs/ConvexEthMetaStrategyHierarchy.svg +++ b/contracts/docs/ConvexEthMetaStrategyHierarchy.svg @@ -4,17 +4,17 @@ - - + + UmlClassDiagram - - + + -20 - -Governable -../contracts/governance/Governable.sol +18 + +Governable +../contracts/governance/Governable.sol @@ -23,13 +23,13 @@ ConvexEthMetaStrategy ../contracts/strategies/ConvexEthMetaStrategy.sol - - -195 - -<<Abstract>> -InitializableAbstractStrategy -../contracts/utils/InitializableAbstractStrategy.sol + + +183 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol @@ -37,25 +37,25 @@ - - -194 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol - - - -195->20 - - - - - -195->194 - - + + +182 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +183->18 + + + + + +183->182 + + diff --git a/contracts/docs/ConvexEthMetaStrategySquashed.svg b/contracts/docs/ConvexEthMetaStrategySquashed.svg index 94c4f6c787..40507c788b 100644 --- a/contracts/docs/ConvexEthMetaStrategySquashed.svg +++ b/contracts/docs/ConvexEthMetaStrategySquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram @@ -119,8 +119,8 @@    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>>    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>>    checkBalance(_asset: address): (balance: uint256) <<ConvexEthMetaStrategy>> -    supportsAsset(_asset: address): bool <<ConvexEthMetaStrategy>> -    constructor(_baseConfig: BaseStrategyConfig, _convexConfig: ConvexEthMetaConfig) <<ConvexEthMetaStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig) <<BaseAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig, _convexConfig: ConvexConfig) <<ConvexEthMetaStrategy>> diff --git a/contracts/docs/ConvexEthMetaStrategyStorage.svg b/contracts/docs/ConvexEthMetaStrategyStorage.svg index 1b7fc49370..6a35a61840 100644 --- a/contracts/docs/ConvexEthMetaStrategyStorage.svg +++ b/contracts/docs/ConvexEthMetaStrategyStorage.svg @@ -4,168 +4,192 @@ - - + + StorageDiagram - + 3 - -ConvexEthMetaStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157 - -158 - -159 - -160 - -161 - -162 - -163 - -164 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -unallocated (12) - -address: _deprecated_cvxDepositorAddress (20) - -unallocated (12) - -address: _deprecated_cvxRewardStaker (20) - -uint256: _deprecated_cvxDepositorPTokenId (32) - -unallocated (12) - -address: _deprecated_curvePool (20) - -unallocated (12) - -address: _deprecated_lpToken (20) - -unallocated (12) - -address: _deprecated_oeth (20) - -unallocated (12) - -address: _deprecated_weth (20) - -uint128: _deprecated_ethCoinIndex (16) - -uint128: _deprecated_oethCoinIndex (16) + +ConvexEthMetaStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +157 + +158 + +159 + +160 + +161 + +162 + +163 + +164 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +unallocated (12) + +address: _deprecated_cvxDepositorAddress (20) + +unallocated (12) + +address: _deprecated_cvxRewardStaker (20) + +uint256: _deprecated_cvxDepositorPTokenId (32) + +unallocated (12) + +address: _deprecated_curvePool (20) + +unallocated (12) + +address: _deprecated_lpToken (20) + +unallocated (12) + +address: _deprecated_oeth (20) + +unallocated (12) + +address: _deprecated_weth (20) + +uint128: _deprecated_ethCoinIndex (16) + +uint128: _deprecated_oethCoinIndex (16) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) 1 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 3:8->1 - - + + 2 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 3:13->2 - - + + diff --git a/contracts/docs/ConvexFrxETHAMOStrategyHierarchy.svg b/contracts/docs/ConvexFrxETHAMOStrategyHierarchy.svg new file mode 100644 index 0000000000..6c5403d32f --- /dev/null +++ b/contracts/docs/ConvexFrxETHAMOStrategyHierarchy.svg @@ -0,0 +1,89 @@ + + + + + + +UmlClassDiagram + + + +18 + +Governable +../contracts/governance/Governable.sol + + + +243 + +<<Abstract>> +BaseAMOStrategy +../contracts/strategies/amo/BaseAMOStrategy.sol + + + +183 + +<<Abstract>> +InitializableAbstractStrategy +../contracts/utils/InitializableAbstractStrategy.sol + + + +243->183 + + + + + +247 + +<<Abstract>> +BaseConvexAMOStrategy +../contracts/strategies/amo/BaseConvexAMOStrategy.sol + + + +247->243 + + + + + +250 + +ConvexFrxETHAMOStrategy +../contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol + + + +250->247 + + + + + +182 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol + + + +183->18 + + + + + +183->182 + + + + + diff --git a/contracts/docs/ConvexFrxETHAMOStrategySquashed.svg b/contracts/docs/ConvexFrxETHAMOStrategySquashed.svg new file mode 100644 index 0000000000..5b0276c5a1 --- /dev/null +++ b/contracts/docs/ConvexFrxETHAMOStrategySquashed.svg @@ -0,0 +1,137 @@ + + + + + + +UmlClassDiagram + + + +250 + +ConvexFrxETHAMOStrategy +../contracts/strategies/amo/ConvexFrxETHAMOStrategy.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> +   MAX_SLIPPAGE: uint256 <<BaseAMOStrategy>> +   lpToken: IERC20 <<BaseAMOStrategy>> +   oToken: IERC20 <<BaseAMOStrategy>> +   asset: IERC20 <<BaseAMOStrategy>> +   oTokenCoinIndex: uint128 <<BaseAMOStrategy>> +   assetCoinIndex: uint128 <<BaseAMOStrategy>> +   cvxDepositorAddress: address <<BaseConvexAMOStrategy>> +   cvxRewardStaker: IRewardStaking <<BaseConvexAMOStrategy>> +   cvxDepositorPTokenId: uint256 <<BaseConvexAMOStrategy>> +   curvePool: ICurveETHPoolV1 <<BaseConvexAMOStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<InitializableAbstractStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, _pToken: address) <<ConvexFrxETHAMOStrategy>> +    _isVaultAsset(_vaultAsset: address): (supported: bool) <<BaseAMOStrategy>> +    _isVaultAssets(_vaultAssets: address[]): bool <<BaseAMOStrategy>> +    _toPoolAsset(address, assets: uint256): (poolAssets: uint256) <<ConvexFrxETHAMOStrategy>> +    _calcPoolAsset(address, vaultAssetAmount: uint256): (poolAssetAmount: uint256) <<ConvexFrxETHAMOStrategy>> +    _toOTokens(poolAssetAmount: uint256): (oethAmount: uint256) <<ConvexFrxETHAMOStrategy>> +    _getCoinIndex(_asset: address): uint128 <<BaseAMOStrategy>> +    _addLiquidityToPool(poolAmounts: uint256[2], minLpAmount: uint256): (lpDeposited: uint256) <<BaseConvexAMOStrategy>> +    _removeLiquidityFromPool(lpTokens: uint256, minPoolAssetAmounts: uint256[2]) <<BaseConvexAMOStrategy>> +    _removeOneSidedLiquidityFromPool(poolAsset: address, lpTokens: uint256, minPoolAssetAmount: uint256): (coinsRemoved: uint256) <<BaseConvexAMOStrategy>> +    _getBalances(): (balances: uint256[2]) <<BaseConvexAMOStrategy>> +    _getBalance(poolAsset: address): (balance: uint256) <<BaseConvexAMOStrategy>> +    _getVirtualPrice(): (virtualPrice: uint256) <<BaseConvexAMOStrategy>> +    _withdrawAsset(address, vaultAssetAmount: uint256, _recipient: address) <<ConvexFrxETHAMOStrategy>> +    _withdrawAllAsset(_recipient: address) <<ConvexFrxETHAMOStrategy>> +    _stakeCurveLp(lpDeposited: uint256) <<BaseConvexAMOStrategy>> +    _unStakeLpTokens(_lpAmount: uint256) <<BaseConvexAMOStrategy>> +    _unStakeAllLpTokens() <<BaseConvexAMOStrategy>> +    _deposit(_poolAssetAmount: uint256) <<BaseAMOStrategy>> +    _withdraw(_recipient: address, _vaultAsset: address, _vaultAssetAmount: uint256) <<BaseAMOStrategy>> +    calcLpTokensToBurn(_vaultAssetAmount: uint256): (lpToBurn: uint256) <<BaseAMOStrategy>> +    _withdrawAndRemoveFromPool(_lpTokens: uint256, removeAsset: address): (coinsRemoved: uint256) <<BaseAMOStrategy>> +    _approveBase() <<ConvexFrxETHAMOStrategy>> +    _max(a: int256, b: int256): int256 <<BaseAMOStrategy>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<BaseConvexAMOStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<onlyGovernor, nonReentrant>> <<BaseAMOStrategy>> +    deposit(_vaultAsset: address, _vaultAssetAmount: uint256) <<onlyVault, onlyAsset, nonReentrant>> <<BaseAMOStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<BaseAMOStrategy>> +    withdraw(_recipient: address, _vaultAsset: address, _vaultAssetAmount: uint256) <<onlyVault, onlyAsset, nonReentrant>> <<BaseAMOStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<BaseAMOStrategy>> +    supportsAsset(_vaultAsset: address): bool <<BaseAMOStrategy>> +    deposit(_vaultAssets: address[], _vaultAssetAmounts: uint256[]) <<onlyVault, onlyAssets, nonReentrant>> <<BaseAMOStrategy>> +    withdraw(_recipient: address, _vaultAssets: address[], _vaultAssetAmounts: uint256[]) <<onlyVault, onlyAssets, nonReentrant>> <<BaseAMOStrategy>> +    mintAndAddOTokens(_oTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    removeAndBurnOTokens(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    removeOnlyAssets(_lpTokens: uint256) <<onlyStrategist, nonReentrant, improvePoolBalance>> <<BaseAMOStrategy>> +    initialize(_rewardTokenAddresses: address[]) <<onlyGovernor, initializer>> <<BaseConvexAMOStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    <<modifier>> onlyAsset(_vaultAsset: address) <<BaseAMOStrategy>> +    <<modifier>> onlyAssets(_vaultAssets: address[]) <<BaseAMOStrategy>> +    <<modifier>> onlyStrategist() <<BaseAMOStrategy>> +    <<modifier>> improvePoolBalance() <<BaseAMOStrategy>> +    constructor() <<Governable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<ConvexFrxETHAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig) <<BaseAMOStrategy>> +    constructor(_baseConfig: BaseStrategyConfig, _amoConfig: AMOConfig, _convexConfig: ConvexConfig) <<ConvexFrxETHAMOStrategy>> + + + diff --git a/contracts/docs/ConvexFrxETHAMOStrategyStorage.svg b/contracts/docs/ConvexFrxETHAMOStrategyStorage.svg new file mode 100644 index 0000000000..65d3911800 --- /dev/null +++ b/contracts/docs/ConvexFrxETHAMOStrategyStorage.svg @@ -0,0 +1,149 @@ + + + + + + +StorageDiagram + + + +3 + +ConvexFrxETHAMOStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) + + + +1 + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:8->1 + + + + + +2 + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +3:13->2 + + + + + diff --git a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg b/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg index 3981425aed..bedca15ca1 100644 --- a/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg +++ b/contracts/docs/ConvexOUSDMetaStrategyHierarchy.svg @@ -16,7 +16,7 @@ Governable ../contracts/governance/Governable.sol - + 152 @@ -29,8 +29,8 @@ 154 <<Abstract>> -BaseCurveStrategy -../contracts/strategies/BaseCurveStrategy.sol +BaseAMOStrategy +../contracts/strategies/amo/BaseAMOStrategy.sol diff --git a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg b/contracts/docs/ConvexOUSDMetaStrategySquashed.svg index 6d896989c3..5ac1c79ce9 100644 --- a/contracts/docs/ConvexOUSDMetaStrategySquashed.svg +++ b/contracts/docs/ConvexOUSDMetaStrategySquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram diff --git a/contracts/docs/ConvexOUSDMetaStrategyStorage.svg b/contracts/docs/ConvexOUSDMetaStrategyStorage.svg new file mode 100644 index 0000000000..c1f602d187 --- /dev/null +++ b/contracts/docs/ConvexOUSDMetaStrategyStorage.svg @@ -0,0 +1,266 @@ + + + + + + +StorageDiagram + + + +5 + +ConvexOUSDMetaStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +157 + +158-206 + +207 + +208 + +209 + +210 + +211 + +212 + +213 + +214 + +215 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +unallocated (12) + +address: _deprecated_pTokenAddresses (20) + +int256[49]: __reserved (1568) + +unallocated (12) + +address: _deprecated_cvxDepositorAddress (20) + +unallocated (12) + +address: _deprecated_cvxRewardStakerAddress (20) + +uint256: _deprecated_cvxDepositorPTokenId (32) + +unallocated (12) + +address: _deprecated_metapool (20) + +unallocated (12) + +address: _deprecated_metapoolMainToken (20) + +unallocated (12) + +address: _deprecated_metapoolLPToken (20) + +address[]: _deprecated_metapoolAssets (32) + +uint256: _deprecated_maxWithdrawalSlippage (32) + +uint128: _deprecated_mainCoinIndex (16) + +uint128: _deprecated_crvCoinIndex (16) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) + + + +1 + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +5:8->1 + + + + + +2 + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +5:13->2 + + + + + +3 + +int256[49]: __reserved <<Array>> + +slot + +158 + +159 + +160-204 + +205 + +206 + +type: variable (bytes) + +int256 (32) + +int256 (32) + +---- (1440) + +int256 (32) + +int256 (32) + + + +5:21->3 + + + + + +4 + +address[]: _deprecated_metapoolAssets <<Array>> +0x51858de9989bf7441865ebdadbf7382c8838edbf830f5d86a9a51ac773676dd6 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) + + + +5:29->4 + + + + + diff --git a/contracts/docs/DripperStorage.svg b/contracts/docs/DripperStorage.svg index 9ee1fef514..93d8a709db 100644 --- a/contracts/docs/DripperStorage.svg +++ b/contracts/docs/DripperStorage.svg @@ -4,50 +4,74 @@ - - + + StorageDiagram - + 2 - -Dripper <<Contract>> - -slot - -0 - -1 - -type: <inherited contract>.variable (bytes) - -uint256: dripDuration (32) - -Drip: drip (32) + +Dripper <<Contract>> + +slot + +0 + +1 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +uint256: dripDuration (32) + +Drip: drip (32) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) 1 - -Drip <<Struct>> - -slot - -1 - -type: variable (bytes) - -uint192: perBlock (24) - -uint64: lastCollect (8) + +Drip <<Struct>> + +slot + +1 + +type: variable (bytes) + +uint192: perBlock (24) + +uint64: lastCollect (8) 2:4->1 - - + + diff --git a/contracts/docs/FluxStrategyHierarchy.svg b/contracts/docs/FluxStrategyHierarchy.svg index fec3306738..67c272d190 100644 --- a/contracts/docs/FluxStrategyHierarchy.svg +++ b/contracts/docs/FluxStrategyHierarchy.svg @@ -16,25 +16,25 @@ Governable ../contracts/governance/Governable.sol - + -151 +142 <<Abstract>> BaseCompoundStrategy ../contracts/strategies/BaseCompoundStrategy.sol - + -195 +183 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -151->195 +142->183 @@ -64,23 +64,23 @@ - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/FraxETHStrategyHierarchy.svg b/contracts/docs/FraxETHStrategyHierarchy.svg index 6ed96c287b..c2aaee8d4a 100644 --- a/contracts/docs/FraxETHStrategyHierarchy.svg +++ b/contracts/docs/FraxETHStrategyHierarchy.svg @@ -36,9 +36,9 @@ - + -195 +183 <<Abstract>> InitializableAbstractStrategy @@ -50,23 +50,23 @@ - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/FraxETHStrategyStorage.svg b/contracts/docs/FraxETHStrategyStorage.svg index 29e3982d71..0f776dd40d 100644 --- a/contracts/docs/FraxETHStrategyStorage.svg +++ b/contracts/docs/FraxETHStrategyStorage.svg @@ -4,246 +4,138 @@ - - + + StorageDiagram - - + + -6 - -FraxETHStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -157 - -158 - -159-208 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_platformAddress (20) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_vaultAddress (20) - -mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) - -address[]: InitializableAbstractStrategy.assetsMapped (32) - -unallocated (12) - -address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) - -uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: InitializableAbstractStrategy.harvesterAddress (20) - -address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) - -int256[98]: InitializableAbstractStrategy._reserved (3136) - -unallocated (12) - -address: Generalized4626Strategy._deprecate_shareToken (20) - -unallocated (12) - -address: Generalized4626Strategy._deprecate_assetToken (20) - -uint256[50]: Generalized4626Strategy.__gap (1600) +3 + +FraxETHStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +157 + +158 + +159-208 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_platformAddress (20) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_vaultAddress (20) + +mapping(address=>address): InitializableAbstractStrategy.assetToPToken (32) + +address[]: InitializableAbstractStrategy.assetsMapped (32) + +unallocated (12) + +address: InitializableAbstractStrategy._deprecated_rewardTokenAddress (20) + +uint256: InitializableAbstractStrategy._deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: InitializableAbstractStrategy.harvesterAddress (20) + +address[]: InitializableAbstractStrategy.rewardTokenAddresses (32) + +int256[98]: InitializableAbstractStrategy._reserved (3136) + +unallocated (12) + +address: Generalized4626Strategy._deprecate_shareToken (20) + +unallocated (12) + +address: Generalized4626Strategy._deprecate_assetToken (20) + +uint256[50]: Generalized4626Strategy.__gap (1600) 1 - -uint256[50]: ______gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -6:8->1 - - +3:8->1 + + 2 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -6:13->2 - - - - - -3 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -6:18->3 - - - - - -4 - -int256[98]: _reserved <<Array>> - -slot - -59 - -60 - -61-154 - -155 - -156 - -type: variable (bytes) - -int256 (32) - -int256 (32) - ----- (3008) - -int256 (32) - -int256 (32) - - - -6:24->4 - - - - - -5 - -uint256[50]: __gap <<Array>> - -slot - -159 - -160 - -161-206 - -207 - -208 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) - - - -6:32->5 - - +3:13->2 + + diff --git a/contracts/docs/Generalized4626StrategyHierarchy.svg b/contracts/docs/Generalized4626StrategyHierarchy.svg index e864cfa167..33cb1371dd 100644 --- a/contracts/docs/Generalized4626StrategyHierarchy.svg +++ b/contracts/docs/Generalized4626StrategyHierarchy.svg @@ -23,9 +23,9 @@ Generalized4626Strategy ../contracts/strategies/Generalized4626Strategy.sol - + -195 +183 <<Abstract>> InitializableAbstractStrategy @@ -37,23 +37,23 @@ - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/InitializableAbstractStrategyStorage.svg b/contracts/docs/InitializableAbstractStrategyStorage.svg index 0e309f6c7e..1ef41d155e 100644 --- a/contracts/docs/InitializableAbstractStrategyStorage.svg +++ b/contracts/docs/InitializableAbstractStrategyStorage.svg @@ -4,194 +4,140 @@ - - + + StorageDiagram - - + + -5 - -InitializableAbstractStrategy <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59-156 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -unallocated (12) - -address: _deprecated_platformAddress (20) - -unallocated (12) - -address: _deprecated_vaultAddress (20) - -mapping(address=>address): assetToPToken (32) - -address[]: assetsMapped (32) - -unallocated (12) - -address: _deprecated_rewardTokenAddress (20) - -uint256: _deprecated_rewardLiquidationThreshold (32) - -unallocated (12) - -address: harvesterAddress (20) - -address[]: rewardTokenAddresses (32) - -int256[98]: _reserved (3136) +3 + +InitializableAbstractStrategy <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59-156 + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +unallocated (12) + +address: _deprecated_platformAddress (20) + +unallocated (12) + +address: _deprecated_vaultAddress (20) + +mapping(address=>address): assetToPToken (32) + +address[]: assetsMapped (32) + +unallocated (12) + +address: _deprecated_rewardTokenAddress (20) + +uint256: _deprecated_rewardLiquidationThreshold (32) + +unallocated (12) + +address: harvesterAddress (20) + +address[]: rewardTokenAddresses (32) + +int256[98]: _reserved (3136) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) 1 - -uint256[50]: ______gap <<Array>> - -slot - -1 - -2 - -3-48 - -49 - -50 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (1472) - -uint256 (32) - -uint256 (32) + +address[]: assetsMapped <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -5:8->1 - - +3:8->1 + + 2 - -address[]: assetsMapped <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -5:13->2 - - - - - -3 - -address[]: rewardTokenAddresses <<Array>> -0xa2999d817b6757290b50e8ecf3fa939673403dd35c97de392fdb343b4015ce9e - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -5:18->3 - - - - - -4 - -int256[98]: _reserved <<Array>> - -slot - -59 - -60 - -61-154 - -155 - -156 - -type: variable (bytes) - -int256 (32) - -int256 (32) - ----- (3008) - -int256 (32) - -int256 (32) - - - -5:24->4 - - +3:13->2 + + diff --git a/contracts/docs/InitializableERC20DetailedStorage.svg b/contracts/docs/InitializableERC20DetailedStorage.svg index 7ce5a3ae58..4e6b237563 100644 --- a/contracts/docs/InitializableERC20DetailedStorage.svg +++ b/contracts/docs/InitializableERC20DetailedStorage.svg @@ -4,74 +4,38 @@ - - + + StorageDiagram - - - -2 - -InitializableERC20Detailed <<Contract>> - -slot - -0-99 - -100 - -101 - -102 - -type: <inherited contract>.variable (bytes) - -uint256[100]: _____gap (3200) - -string: _name (32) - -string: _symbol (32) - -unallocated (31) - -uint8: _decimals (1) - + - + 1 - -uint256[100]: _____gap <<Array>> - -slot - -0 - -1 - -2-97 - -98 - -99 - -type: variable (bytes) - -uint256 (32) - -uint256 (32) - ----- (3072) - -uint256 (32) - -uint256 (32) - - - -2:6->1 - - + +InitializableERC20Detailed <<Contract>> + +slot + +0-99 + +100 + +101 + +102 + +type: <inherited contract>.variable (bytes) + +uint256[100]: _____gap (3200) + +string: _name (32) + +string: _symbol (32) + +unallocated (31) + +uint8: _decimals (1) diff --git a/contracts/docs/MorphoAaveStrategyHierarchy.svg b/contracts/docs/MorphoAaveStrategyHierarchy.svg index 4705321bfe..a6c651905f 100644 --- a/contracts/docs/MorphoAaveStrategyHierarchy.svg +++ b/contracts/docs/MorphoAaveStrategyHierarchy.svg @@ -16,44 +16,44 @@ Governable ../contracts/governance/Governable.sol - + -176 +164 MorphoAaveStrategy ../contracts/strategies/MorphoAaveStrategy.sol - + -195 +183 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -176->195 +164->183 - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/MorphoAaveStrategySquashed.svg b/contracts/docs/MorphoAaveStrategySquashed.svg index 3b1982d39c..b633c9da67 100644 --- a/contracts/docs/MorphoAaveStrategySquashed.svg +++ b/contracts/docs/MorphoAaveStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -176 +164 MorphoAaveStrategy ../contracts/strategies/MorphoAaveStrategy.sol diff --git a/contracts/docs/MorphoCompStrategyHierarchy.svg b/contracts/docs/MorphoCompStrategyHierarchy.svg index 2800787262..c8df8924c6 100644 --- a/contracts/docs/MorphoCompStrategyHierarchy.svg +++ b/contracts/docs/MorphoCompStrategyHierarchy.svg @@ -16,58 +16,58 @@ Governable ../contracts/governance/Governable.sol - + -151 +142 <<Abstract>> BaseCompoundStrategy ../contracts/strategies/BaseCompoundStrategy.sol - + -195 +183 <<Abstract>> InitializableAbstractStrategy ../contracts/utils/InitializableAbstractStrategy.sol - + -151->195 +142->183 - + -177 +165 MorphoCompoundStrategy ../contracts/strategies/MorphoCompoundStrategy.sol - + -177->151 +165->142 - + -194 +182 <<Abstract>> Initializable ../contracts/utils/Initializable.sol - + -195->20 +183->18 - + -195->194 +183->182 diff --git a/contracts/docs/MorphoCompStrategySquashed.svg b/contracts/docs/MorphoCompStrategySquashed.svg index f862df9dd0..91fe55fc8f 100644 --- a/contracts/docs/MorphoCompStrategySquashed.svg +++ b/contracts/docs/MorphoCompStrategySquashed.svg @@ -9,9 +9,9 @@ UmlClassDiagram - + -177 +165 MorphoCompoundStrategy ../contracts/strategies/MorphoCompoundStrategy.sol diff --git a/contracts/docs/OETHDripperStorage.svg b/contracts/docs/OETHDripperStorage.svg index a6b80c2e8b..bd00bc6df0 100644 --- a/contracts/docs/OETHDripperStorage.svg +++ b/contracts/docs/OETHDripperStorage.svg @@ -4,50 +4,74 @@ - - + + StorageDiagram - + 2 - -OETHDripper <<Contract>> - -slot - -0 - -1 - -type: <inherited contract>.variable (bytes) - -uint256: Dripper.dripDuration (32) - -Drip: Dripper.drip (32) + +OETHDripper <<Contract>> + +slot + +0 + +1 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +type: <inherited contract>.variable (bytes) + +uint256: Dripper.dripDuration (32) + +Drip: Dripper.drip (32) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) 1 - -Drip <<Struct>> - -slot - -1 - -type: variable (bytes) - -uint192: perBlock (24) - -uint64: lastCollect (8) + +Drip <<Struct>> + +slot + +1 + +type: variable (bytes) + +uint192: perBlock (24) + +uint64: lastCollect (8) 2:4->1 - - + + diff --git a/contracts/docs/OETHVaultAdminSquashed.svg b/contracts/docs/OETHVaultAdminSquashed.svg index 3a14885577..7cc285c59b 100644 --- a/contracts/docs/OETHVaultAdminSquashed.svg +++ b/contracts/docs/OETHVaultAdminSquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram @@ -76,7 +76,7 @@    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>>    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>>    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setMintForStrategyThreshold(_strategy: address, _threshold: uint256) <<onlyGovernor>> <<VaultAdmin>>    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>>    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>>    swapper(): (swapper_: address) <<VaultAdmin>> @@ -92,7 +92,7 @@    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>>    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>>    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> +    setAMOStrategy(_strategyAddress: address, _isAMO: bool) <<onlyGovernor>> <<VaultAdmin>>    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>>    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>>    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> @@ -115,7 +115,7 @@    <<event>> RebasePaused() <<VaultStorage>>    <<event>> RebaseUnpaused() <<VaultStorage>>    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> +    <<event>> AMOStrategyUpdated(_addr: address, _isAMO: bool) <<VaultStorage>>    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>>    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>>    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> @@ -125,7 +125,7 @@    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>>    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>>    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> +    <<event>> MintForStrategyThresholdChanged(_strategy: address, _threshold: uint256) <<VaultStorage>>    <<event>> SwapperChanged(_address: address) <<VaultStorage>>    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>>    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> diff --git a/contracts/docs/OETHVaultCoreSquashed.svg b/contracts/docs/OETHVaultCoreSquashed.svg index e2dbd8c5ef..a38eddad57 100644 --- a/contracts/docs/OETHVaultCoreSquashed.svg +++ b/contracts/docs/OETHVaultCoreSquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram @@ -85,9 +85,9 @@    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>>    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>>    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> +    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>>    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> +    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>>    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>>    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>>    rebase() <<nonReentrant>> <<VaultCore>> @@ -99,43 +99,43 @@    getAllAssets(): address[] <<VaultCore>>    getStrategyCount(): uint256 <<VaultCore>>    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> +    getStrategyConfig(_strategy: address): (config: VaultStorage.Strategy) <<VaultCore>> +    isSupportedAsset(_asset: address): bool <<VaultCore>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> AssetSupported(_asset: address) <<VaultStorage>> +    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> +    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> +    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> +    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> +    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> CapitalPaused() <<VaultStorage>> +    <<event>> CapitalUnpaused() <<VaultStorage>> +    <<event>> RebasePaused() <<VaultStorage>> +    <<event>> RebaseUnpaused() <<VaultStorage>> +    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> +    <<event>> AMOStrategyUpdated(_addr: address, _isAMO: bool) <<VaultStorage>> +    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> +    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> +    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> +    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> +    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> +    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> +    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> +    <<event>> MintForStrategyThresholdChanged(_strategy: address, _threshold: uint256) <<VaultStorage>> +    <<event>> SwapperChanged(_address: address) <<VaultStorage>> +    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> +    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> +    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> whenNotRebasePaused() <<VaultCore>> +    <<modifier>> whenNotCapitalPaused() <<VaultCore>>    constructor() <<Governable>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> diff --git a/contracts/docs/OETHVaultHierarchy.svg b/contracts/docs/OETHVaultHierarchy.svg index 6e3546cc1d..4cd1038d3a 100644 --- a/contracts/docs/OETHVaultHierarchy.svg +++ b/contracts/docs/OETHVaultHierarchy.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram diff --git a/contracts/docs/OETHVaultStorage.svg b/contracts/docs/OETHVaultStorage.svg index c950373509..1f685677ae 100644 --- a/contracts/docs/OETHVaultStorage.svg +++ b/contracts/docs/OETHVaultStorage.svg @@ -4,262 +4,294 @@ - - + + StorageDiagram - + 6 - -OETHVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) + +OETHVaultCore <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59 + +60 + +61 + +62 + +63 + +64 + +65 + +66 + +67 + +68 + +69 + +70 + +71 + +72 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +0xa2b..bd9 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +mapping(address=>Asset): VaultStorage.assets (32) + +address[]: VaultStorage.allAssets (32) + +mapping(address=>Strategy): VaultStorage.strategies (32) + +address[]: VaultStorage.allStrategies (32) + +unallocated (10) + +bool: VaultStorage.capitalPaused (1) + +bool: VaultStorage.rebasePaused (1) + +address: VaultStorage.priceProvider (20) + +uint256: VaultStorage.redeemFeeBps (32) + +uint256: VaultStorage.vaultBuffer (32) + +uint256: VaultStorage.autoAllocateThreshold (32) + +uint256: VaultStorage.rebaseThreshold (32) + +unallocated (12) + +OUSD: VaultStorage.oUSD (20) + +unallocated (12) + +address: VaultStorage._deprecated_rebaseHooksAddr (20) + +unallocated (12) + +address: VaultStorage._deprecated_uniswapAddr (20) + +unallocated (12) + +address: VaultStorage.strategistAddr (20) + +mapping(address=>address): VaultStorage.assetDefaultStrategies (32) + +uint256: VaultStorage.maxSupplyDiff (32) + +unallocated (12) + +address: VaultStorage.trusteeAddress (20) + +uint256: VaultStorage.trusteeFeeBps (32) + +address[]: VaultStorage._deprecated_swapTokens (32) + +unallocated (12) + +address: VaultStorage._deprecatedOusdMetaStrategy (20) + +int256: VaultStorage._deprecatedNetOusdMintedForStrategy (32) + +uint256: VaultStorage._deprecatedNetOusdMintForStrategyThreshold (32) + +SwapConfig: VaultStorage.swapConfig (32) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) + +unallocated (12) + +address: OUSD.vault.governor.admin.impl (20) 1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) + +Asset <<Struct>> + +offset + +0 + +type: variable (bytes) + +unallocated (27) + +uint16: allowedOracleSlippageBps (2) + +uint8: decimals (1) + +UnitConversion: unitConversion (1) + +bool: isSupported (1) 6:8->1 - - + + 2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: allAssets <<Array>> +0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 6:10->2 - - + + 3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) + +Strategy <<Struct>> + +offset + +0 + +type: variable (bytes) + +unallocated (6) + +int96: mintForStrategyThreshold (12) + +int96: mintForStrategy (12) + +bool: isAMO (1) + +bool: isSupported (1) -6:13->3 - - +6:15->3 + + 4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: allStrategies <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -6:15->4 - - +6:17->4 + + 5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) + +SwapConfig <<Struct>> + +slot + +72 + +type: variable (bytes) + +unallocated (10) + +uint16: allowedUndervalueBps (2) + +address: swapper (20) -6:37->5 - - +6:39->5 + + diff --git a/contracts/docs/VaultAdminSquashed.svg b/contracts/docs/VaultAdminSquashed.svg index 939e2c043f..a9d66bbbf0 100644 --- a/contracts/docs/VaultAdminSquashed.svg +++ b/contracts/docs/VaultAdminSquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram @@ -76,7 +76,7 @@    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>>    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>>    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setMintForStrategyThreshold(_strategy: address, _threshold: uint256) <<onlyGovernor>> <<VaultAdmin>>    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>>    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>>    swapper(): (swapper_: address) <<VaultAdmin>> @@ -92,7 +92,7 @@    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>>    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>>    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> +    setAMOStrategy(_strategyAddress: address, _isAMO: bool) <<onlyGovernor>> <<VaultAdmin>>    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>>    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>>    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> @@ -115,7 +115,7 @@    <<event>> RebasePaused() <<VaultStorage>>    <<event>> RebaseUnpaused() <<VaultStorage>>    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> +    <<event>> AMOStrategyUpdated(_addr: address, _isAMO: bool) <<VaultStorage>>    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>>    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>>    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> @@ -125,7 +125,7 @@    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>>    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>>    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> +    <<event>> MintForStrategyThresholdChanged(_strategy: address, _threshold: uint256) <<VaultStorage>>    <<event>> SwapperChanged(_address: address) <<VaultStorage>>    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>>    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> diff --git a/contracts/docs/VaultCoreSquashed.svg b/contracts/docs/VaultCoreSquashed.svg index 64e07ea5fe..28567833ba 100644 --- a/contracts/docs/VaultCoreSquashed.svg +++ b/contracts/docs/VaultCoreSquashed.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram @@ -85,9 +85,9 @@    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>>    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>>    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> +    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>>    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> +    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>>    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>>    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>>    rebase() <<nonReentrant>> <<VaultCore>> @@ -99,43 +99,43 @@    getAllAssets(): address[] <<VaultCore>>    getStrategyCount(): uint256 <<VaultCore>>    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> +    getStrategyConfig(_strategy: address): (config: VaultStorage.Strategy) <<VaultCore>> +    isSupportedAsset(_asset: address): bool <<VaultCore>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> AssetSupported(_asset: address) <<VaultStorage>> +    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> +    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> +    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> +    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> +    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> CapitalPaused() <<VaultStorage>> +    <<event>> CapitalUnpaused() <<VaultStorage>> +    <<event>> RebasePaused() <<VaultStorage>> +    <<event>> RebaseUnpaused() <<VaultStorage>> +    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> +    <<event>> AMOStrategyUpdated(_addr: address, _isAMO: bool) <<VaultStorage>> +    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> +    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> +    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> +    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> +    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> +    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> +    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> +    <<event>> MintForStrategyThresholdChanged(_strategy: address, _threshold: uint256) <<VaultStorage>> +    <<event>> SwapperChanged(_address: address) <<VaultStorage>> +    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> +    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> +    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> whenNotRebasePaused() <<VaultCore>> +    <<modifier>> whenNotCapitalPaused() <<VaultCore>>    constructor() <<Governable>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> diff --git a/contracts/docs/VaultHierarchy.svg b/contracts/docs/VaultHierarchy.svg index 626d89b4bb..5fd283b0ba 100644 --- a/contracts/docs/VaultHierarchy.svg +++ b/contracts/docs/VaultHierarchy.svg @@ -4,9 +4,9 @@ - - + + UmlClassDiagram diff --git a/contracts/docs/VaultStorage.svg b/contracts/docs/VaultStorage.svg index bb504649df..f1cf0e107b 100644 --- a/contracts/docs/VaultStorage.svg +++ b/contracts/docs/VaultStorage.svg @@ -4,262 +4,294 @@ - - + + StorageDiagram - + 6 - -VaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) + +VaultCore <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59 + +60 + +61 + +62 + +63 + +64 + +65 + +66 + +67 + +68 + +69 + +70 + +71 + +72 + +0x360..bbc + +0x44c..1db + +0x53b..535 + +0x7be..a4a + +0xa2b..bd9 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +mapping(address=>Asset): VaultStorage.assets (32) + +address[]: VaultStorage.allAssets (32) + +mapping(address=>Strategy): VaultStorage.strategies (32) + +address[]: VaultStorage.allStrategies (32) + +unallocated (10) + +bool: VaultStorage.capitalPaused (1) + +bool: VaultStorage.rebasePaused (1) + +address: VaultStorage.priceProvider (20) + +uint256: VaultStorage.redeemFeeBps (32) + +uint256: VaultStorage.vaultBuffer (32) + +uint256: VaultStorage.autoAllocateThreshold (32) + +uint256: VaultStorage.rebaseThreshold (32) + +unallocated (12) + +OUSD: VaultStorage.oUSD (20) + +unallocated (12) + +address: VaultStorage._deprecated_rebaseHooksAddr (20) + +unallocated (12) + +address: VaultStorage._deprecated_uniswapAddr (20) + +unallocated (12) + +address: VaultStorage.strategistAddr (20) + +mapping(address=>address): VaultStorage.assetDefaultStrategies (32) + +uint256: VaultStorage.maxSupplyDiff (32) + +unallocated (12) + +address: VaultStorage.trusteeAddress (20) + +uint256: VaultStorage.trusteeFeeBps (32) + +address[]: VaultStorage._deprecated_swapTokens (32) + +unallocated (12) + +address: VaultStorage._deprecatedOusdMetaStrategy (20) + +int256: VaultStorage._deprecatedNetOusdMintedForStrategy (32) + +uint256: VaultStorage._deprecatedNetOusdMintForStrategyThreshold (32) + +SwapConfig: VaultStorage.swapConfig (32) + +unallocated (12) + +address (20) + +unallocated (12) + +address: OUSD.pending.governor (20) + +unallocated (31) + +bool: OUSD.reentry.status (1) + +unallocated (12) + +address: OUSD.governor (20) + +unallocated (12) + +address: OUSD.vault.governor.admin.impl (20) 1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) + +Asset <<Struct>> + +offset + +0 + +type: variable (bytes) + +unallocated (27) + +uint16: allowedOracleSlippageBps (2) + +uint8: decimals (1) + +UnitConversion: unitConversion (1) + +bool: isSupported (1) 6:8->1 - - + + 2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: allAssets <<Array>> +0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) 6:10->2 - - + + 3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) + +Strategy <<Struct>> + +offset + +0 + +type: variable (bytes) + +unallocated (6) + +int96: mintForStrategyThreshold (12) + +int96: mintForStrategy (12) + +bool: isAMO (1) + +bool: isSupported (1) -6:13->3 - - +6:15->3 + + 4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: allStrategies <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -6:15->4 - - +6:17->4 + + 5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) + +SwapConfig <<Struct>> + +slot + +72 + +type: variable (bytes) + +unallocated (10) + +uint16: allowedUndervalueBps (2) + +address: swapper (20) -6:37->5 - - +6:39->5 + + diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index d1f745b912..08cc7c9031 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -14,19 +14,23 @@ sol2uml .. -s -d 0 -b Flipper -o FlipperSquashed.svg sol2uml storage .. -c Flipper -o FlipperStorage.svg # contracts/harvest -sol2uml .. -v -hv -hf -he -hs -hl -b Dripper -o DripperHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b Dripper -o DripperHierarchy.svg sol2uml .. -s -d 0 -b Dripper -o DripperSquashed.svg -sol2uml storage .. -c Dripper -o DripperStorage.svg +sol2uml storage .. -c Dripper -o DripperStorage.svg \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address -sol2uml .. -v -hv -hf -he -hs -hl -b OETHDripper -o OETHDripperHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHDripper -o OETHDripperHierarchy.svg sol2uml .. -s -d 0 -b OETHDripper -o OETHDripperSquashed.svg -sol2uml storage .. -c OETHDripper -o OETHDripperStorage.svg +sol2uml storage .. -c OETHDripper -o OETHDripperStorage.svg \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address -sol2uml .. -v -hv -hf -he -hs -hl -b Harvester -o HarvesterHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b Harvester -o HarvesterHierarchy.svg sol2uml .. -s -d 0 -b Harvester -o HarvesterSquashed.svg sol2uml storage .. -c Harvester -o HarvesterStorage.svg -sol2uml .. -v -hv -hf -he -hs -hl -b OETHHarvester -o OETHHarvesterHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHHarvester -o OETHHarvesterHierarchy.svg sol2uml .. -s -d 0 -b OETHHarvester -o OETHHarvesterSquashed.svg sol2uml storage .. -c OETHHarvester -o OETHHarvesterStorage.svg @@ -62,28 +66,23 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b AaveStrategy -o AaveStrategyHierarchy.s sol2uml .. -s -d 0 -b AaveStrategy -o AaveStrategySquashed.svg sol2uml storage .. -c AaveStrategy -o AaveStrategyStorage.svg --hideExpand ______gap,_reserved +sol2uml .. -v -hv -hf -he -hs -hl -hi -b CompoundStrategy -o CompStrategyHierarchy.svg +sol2uml .. -s -d 0 -b CompoundStrategy -o CompStrategySquashed.svg +sol2uml storage .. -c CompoundStrategy -o CompStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved + sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexThreePoolStrategy,ConvexTwoPoolStrategy -o ConvexStrategiesHierarchy.svg sol2uml .. -s -d 0 -b ConvexThreePoolStrategy -o ConvexThreePoolStrategySquashed.svg sol2uml storage .. -c ConvexThreePoolStrategy -o ConvexThreePoolStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved sol2uml .. -s -d 0 -b ConvexTwoPoolStrategy -o ConvexTwoPoolStrategySquashed.svg sol2uml storage .. -c ConvexTwoPoolStrategy -o ConvexTwoPoolStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved -sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexEthMetaStrategy -o ConvexEthMetaStrategyHierarchy.svg -sol2uml .. -s -d 0 -b ConvexEthMetaStrategy -o ConvexEthMetaStrategySquashed.svg -sol2uml storage .. -c ConvexEthMetaStrategy -o ConvexEthMetaStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved - -sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyHierarchy.svg -sol2uml .. -s -d 0 -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategySquashed.svg -# Failed to find user defined type "IERC20" in attribute "metapoolMainToken" of type "1"" -# sol2uml storage .. -c ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyStorage.svg - sol2uml .. -v -hv -hf -he -hs -hl -hi -b FluxStrategy -o FluxStrategyHierarchy.svg sol2uml .. -s -d 0 -b FluxStrategy -o FluxStrategySquashed.svg sol2uml storage .. -c FluxStrategy -o FluxStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved sol2uml .. -v -hv -hf -he -hs -hl -hi -b FraxETHStrategy -o FraxETHStrategyHierarchy.svg sol2uml .. -s -d 0 -b FraxETHStrategy -o FraxETHStrategySquashed.svg -sol2uml storage .. -c FraxETHStrategy -o FraxETHStrategyStorage.svg +sol2uml storage .. -c FraxETHStrategy -o FraxETHStrategyStorage.svg --hideExpand ______gap,__gap,_reserved sol2uml .. -v -hv -hf -he -hs -hl -hi -b Generalized4626Strategy -o Generalized4626StrategyHierarchy.svg sol2uml .. -s -d 0 -b Generalized4626Strategy -o Generalized4626StrategySquashed.svg @@ -97,6 +96,37 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b MorphoCompoundStrategy -o MorphoCompStr sol2uml .. -s -d 0 -b MorphoCompoundStrategy -o MorphoCompStrategySquashed.svg sol2uml storage .. -c MorphoCompoundStrategy -o MorphoCompStrategyStorage.svg --hideExpand ______gap,_reserved,__reserved +# contracts/strategies/amo +sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexEthMetaStrategy,ConvexFrxETHAMOStrategy,ConvexOUSDMetaStrategy,BalancerEthAMOStrategy -o AMOContractHierarchy.svg + +sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexEthMetaStrategy -o ConvexEthMetaStrategyHierarchy.svg +sol2uml .. -s -d 0 -b ConvexEthMetaStrategy -o ConvexEthMetaStrategySquashed.svg +sol2uml storage .. -c ConvexEthMetaStrategy -o ConvexEthMetaStrategyStorage.svg \ + --hideExpand ______gap,_reserved \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address + +sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexFrxETHAMOStrategy -o ConvexFrxETHAMOStrategyHierarchy.svg +sol2uml .. -s -d 0 -b ConvexFrxETHAMOStrategy -o ConvexFrxETHAMOStrategySquashed.svg +sol2uml storage .. -c ConvexFrxETHAMOStrategy -o ConvexFrxETHAMOStrategyStorage.svg \ + --hideExpand ______gap,_reserved \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address + +sol2uml .. -v -hv -hf -he -hs -hl -hi -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyHierarchy.svg +sol2uml .. -s -d 0 -b ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategySquashed.svg +sol2uml storage .. -c ConvexOUSDMetaStrategy -o ConvexOUSDMetaStrategyStorage.svg \ + --hideExpand ______gap,_reserved \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address + +sol2uml .. -v -hv -hf -he -hs -hl -hi -b BalancerEthAMOStrategy -o BalancerEthAMOStrategyHierarchy.svg +sol2uml .. -s -d 0 -b BalancerEthAMOStrategy -o BalancerEthAMOStrategySquashed.svg +sol2uml storage .. -c BalancerEthAMOStrategy -o BalancerEthAMOStrategyStorage.svg \ + --hideExpand ______gap,_reserved \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address + # contracts/strategies/balancer sol2uml .. -v -hv -hf -he -hs -hl -hi -b BalancerMetaPoolStrategy -o BalancerMetaPoolStrategyHierarchy.svg sol2uml .. -s -d 0 -b BalancerMetaPoolStrategy -o BalancerMetaPoolStrategySquashed.svg @@ -134,24 +164,34 @@ sol2uml .. -s -d 0 -b WOETH -o WOETHSquashed.svg sol2uml storage .. -c WOETH -o WOETHStorage.svg # contracts/vault -sol2uml .. -v -hv -hf -he -hs -hl -b VaultCore,VaultAdmin -o VaultHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b VaultCore,VaultAdmin -o VaultHierarchy.svg sol2uml .. -s -d 0 -b VaultCore -o VaultCoreSquashed.svg sol2uml .. -s -d 0 -b VaultAdmin -o VaultAdminSquashed.svg -sol2uml storage .. -c VaultCore -o VaultStorage.svg --hideExpand ______gap,_deprecated_swapTokens +sol2uml storage .. -c VaultCore -o VaultStorage.svg \ + --hideExpand ______gap,_deprecated_swapTokens \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,OUSD.vault.governor.admin.impl,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address,address -sol2uml .. -v -hv -hf -he -hs -hl -b OETHVaultCore,OETHVaultAdmin -o OETHVaultHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHVaultCore,OETHVaultAdmin -o OETHVaultHierarchy.svg sol2uml .. -s -d 0 -b OETHVaultCore -o OETHVaultCoreSquashed.svg sol2uml .. -s -d 0 -b OETHVaultAdmin -o OETHVaultAdminSquashed.svg -sol2uml storage .. -c OETHVaultCore -o OETHVaultStorage.svg --hideExpand ______gap,_deprecated_swapTokens +sol2uml storage .. -c OETHVaultCore -o OETHVaultStorage.svg \ + --hideExpand ______gap,_deprecated_swapTokens \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status,OUSD.vault.governor.admin.impl,0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc \ + --slotTypes address,address,bool,address,address # contracts/utils sol2uml .. -v -hv -hf -he -hs -hl -b InitializableAbstractStrategy -o InitializableAbstractStrategyHierarchy.svg sol2uml .. -s -d 0 -b InitializableAbstractStrategy -o InitializableAbstractStrategySquashed.svg -sol2uml storage .. -c InitializableAbstractStrategy -o InitializableAbstractStrategyStorage.svg +sol2uml storage .. -c InitializableAbstractStrategy -o InitializableAbstractStrategyStorage.svg \ + --hideExpand ______gap,_reserved \ + --slotNames OUSD.governor,OUSD.pending.governor,OUSD.reentry.status \ + --slotTypes address,address,bool sol2uml .. -v -hv -hf -he -hs -hl -b InitializableERC20Detailed -o InitializableERC20DetailedHierarchy.svg sol2uml .. -s -d 0 -b InitializableERC20Detailed -o InitializableERC20DetailedSquashed.svg -sol2uml storage .. -c InitializableERC20Detailed -o InitializableERC20DetailedStorage.svg +sol2uml storage .. -c InitializableERC20Detailed -o InitializableERC20DetailedStorage.svg \ + --hideExpand _____gap sol2uml .. -v -hv -hf -b StableMath -o StableMathHierarchy.svg sol2uml .. -s -d 0 -b StableMath -o StableMathSquashed.svg diff --git a/contracts/docs/plantuml/oethContracts.png b/contracts/docs/plantuml/oethContracts.png index 0cbff92f47..8f2e537905 100644 Binary files a/contracts/docs/plantuml/oethContracts.png and b/contracts/docs/plantuml/oethContracts.png differ diff --git a/contracts/docs/plantuml/oethContracts.puml b/contracts/docs/plantuml/oethContracts.puml index b86afac379..4b166e77da 100644 --- a/contracts/docs/plantuml/oethContracts.puml +++ b/contracts/docs/plantuml/oethContracts.puml @@ -55,6 +55,11 @@ object "MorphoAaveStrategy" as morphAaveStrat <><> #DeepSkyBlue { asset: WETH Aave token: aWETH } +object "ConvexFrxETHAMOStrategy" as frxAMOStrat <><> #DeepSkyBlue { + asset: frxETH + Curve metapool: OETHfrxETH-f + Convex pool: cvxOETHfrxETH-f +} object "BalancerMetaPoolStrategy" as balancerStrat <><> #DeepSkyBlue { assets: rETH, WETH @@ -198,6 +203,10 @@ harv <..> cvxStrat oethv <...> cvxStrat oeth <... cvxStrat +harv <..> frxAMOStrat +oethv <...> frxAMOStrat +oeth <... frxAMOStrat + ' cvxStrat ..> crvPool ' cvxStrat ..> cvxPool ' cvxStrat ...> weth diff --git a/contracts/tasks/amoStrategy.js b/contracts/tasks/amoStrategy.js index da95a4f644..e6e5f7ee96 100644 --- a/contracts/tasks/amoStrategy.js +++ b/contracts/tasks/amoStrategy.js @@ -279,33 +279,32 @@ async function amoStrategyTask(taskArguments, hre) { ); // Strategy's net minted and threshold - const netMintedForStrategy = await vault.netOusdMintedForStrategy({ + const strategyConfig = await vault.getStrategyConfig(amoStrategy.address, { blockTag, }); - const netMintedForStrategyThreshold = - await vault.netOusdMintForStrategyThreshold({ blockTag }); - const netMintedForStrategyDiff = - netMintedForStrategyThreshold.sub(netMintedForStrategy); + const mintForStrategyDiff = strategyConfig.mintForStrategyThreshold.sub( + strategyConfig.mintForStrategy + ); output( displayProperty( "\nNet minted for strategy", assetSymbol, - netMintedForStrategy + strategyConfig.mintForStrategy ) ); output( displayProperty( "Net minted threshold", assetSymbol, - netMintedForStrategyThreshold + strategyConfig.mintForStrategyThreshold ) ); output( displayProperty( "Net minted for strat diff", assetSymbol, - netMintedForStrategyDiff + mintForStrategyDiff ) ); } diff --git a/contracts/tasks/curve.js b/contracts/tasks/curve.js index 23bf6493cb..bb03ec8629 100644 --- a/contracts/tasks/curve.js +++ b/contracts/tasks/curve.js @@ -2,7 +2,7 @@ const { BigNumber } = require("ethers"); const { formatUnits, parseUnits } = require("ethers/lib/utils"); const ousdPoolAbi = require("../test/abi/ousdMetapool.json"); -const oethPoolAbi = require("../test/abi/oethMetapool.json"); +const oethPoolAbi = require("../test/abi/curveOethEthPool.json"); const addresses = require("../utils/addresses"); const { resolveAsset } = require("../utils/assets"); const { getDiffBlocks } = require("./block"); diff --git a/contracts/test/_fund.js b/contracts/test/_fund.js index de0f3ebc6b..a91419c0a9 100644 --- a/contracts/test/_fund.js +++ b/contracts/test/_fund.js @@ -45,6 +45,9 @@ const balancesContractSlotCache = { const findBalancesSlot = async (tokenAddress) => { tokenAddress = tokenAddress.toLowerCase(); if (balancesContractSlotCache[tokenAddress]) { + log( + `Found balance slot ${balancesContractSlotCache[tokenAddress][0]} for token ${tokenAddress} in cache` + ); return balancesContractSlotCache[tokenAddress]; } diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index e803d5b71f..9186a27a07 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -9,7 +9,7 @@ const addresses = require("../utils/addresses"); const { balancer_rETH_WETH_PID, balancer_wstETH_sfrxETH_rETH_PID, - oethPoolLpPID, + convex_OETH_ETH_PID, } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); const { impersonateAndFund } = require("../utils/signers"); @@ -72,11 +72,16 @@ async function constructNewContract(fixture, implContractName) { return [ [addresses.mainnet.CurveOETHMetaPool, addresses.mainnet.OETHVaultProxy], [ - addresses.mainnet.CVXBooster, - addresses.mainnet.CVXETHRewardsPool, - oethPoolLpPID, - addresses.mainnet.OETHProxy, - addresses.mainnet.WETH, + addresses.mainnet.OETHProxy, // oTokenAddress, + addresses.mainnet.WETH, // vaultAssetAddress (WETH) + addresses.ETH, // poolAssetAddress (ETH) + 1, // Curve pool index for OToken OETH + 0, // Curve pool index for asset ETH + ], + [ + addresses.mainnet.CVXBooster, // cvxDepositorAddress, + addresses.mainnet.CVXETHRewardsPool, // cvxRewardStakerAddress, + convex_OETH_ETH_PID, // cvxDepositorPTokenId ], ]; } @@ -140,7 +145,7 @@ async function hotDeployOption( "fraxEthStrategy", // fixtureStrategyVarName "FraxETHStrategy" // implContractName ); - } else if (fixtureName === "convexOETHMetaVaultFixture") { + } else if (fixtureName === "convexOethEthAmoFixture") { await hotDeployFixture( fixture, // fixture "convexEthMetaStrategy", // fixtureStrategyVarName diff --git a/contracts/test/abi/oethMetapool.json b/contracts/test/abi/curveOethEthPool.json similarity index 100% rename from contracts/test/abi/oethMetapool.json rename to contracts/test/abi/curveOethEthPool.json diff --git a/contracts/test/behaviour/amo.js b/contracts/test/behaviour/amo.js new file mode 100644 index 0000000000..ed3922b113 --- /dev/null +++ b/contracts/test/behaviour/amo.js @@ -0,0 +1,806 @@ +const { expect } = require("chai"); +const { parseUnits } = require("ethers/lib/utils"); +const { BigNumber } = require("ethers"); + +const { units } = require("../helpers"); +const { MAX_UINT256 } = require("../../utils/constants"); +const { impersonateAndFund } = require("../../utils/signers"); +const addresses = require("../../utils/addresses"); + +/** + * + * @param {*} context a function that returns a fixture with the additional properties: + * - strategy: the strategy to test + * - oToken: the OToken. eg OETH or USDD + * - vaultAsset: the token of the vault collateral asset. eg WETH, frxETH, DAI/USDC/USDT + * - poolAsset: the token of the Curve or Balancer asset. eg ETH, frxETH, 3Crv + * - assetDivisor: 3 for the OUSD AMO strategy that uses the 3Pool. Others are 1 + * - curvePool: The Curve pool contract else undefined + * - balancerPool: The Balancer pool contract else undefined + * - vault: Vault or OETHVault contract + * - convexPID: Convex Pool Identifier. eg 56 for OUSD/3Crv + * @example + shouldBehaveLikeStrategy(() => ({ + ...fixture, + strategy: fixture.convexEthMetaStrategy, + oToken: fixture.OETH, + vaultAsset: fixture.weth, + poolAssetAddress: addresses.ETH, + assetIndex: 0, + assetDivisor: 1 + curvePool: fixture.CurveOethEthPool, + vault: fixture.oethVault, + convexPID: convex_frxETH_WETH_PID, + })); + */ +const shouldBehaveLikeAmo = (context) => { + /***************************************** + Assert Deposit + *****************************************/ + + const assertDeposit = async ( + tx, + assetAmount, + oTokenAmount, + checkBalanceBefore = BigNumber.from(0) + ) => { + const { assetDivisor, curvePool, oToken, strategy, vaultAsset } = context(); + // strategy Deposit event for the vault asset + await expect(tx) + .to.emit(strategy, "Deposit") + .withArgs(vaultAsset.address, curvePool.address, assetAmount); + // There is no asset transfer to the strategy as that happens before the vault calls deposit + + // strategy Deposit event for the OToken + await expect(tx) + .to.emit(strategy, "Deposit") + .withArgs(oToken.address, curvePool.address, oTokenAmount); + // OToken is minted to the the strategy + await expect(tx) + .to.emit(oToken, "Transfer") + .withArgs(addresses.zero, strategy.address, oTokenAmount); + + // Strategy balance has to be 2x the deposit amount + // for the OUSD AMO using the 3Pool, the balance is + // split across DAI, USDC and USDT so is divided by 3 + const vaultAssetDecimals = await vaultAsset.decimals(); + const assetMultiplier = BigNumber.from(10).pow(18 - vaultAssetDecimals); + // scale up to 18 decimals + const assetAmountScaled = assetAmount.mul(assetMultiplier); + const checkBalanceIncreaseScaled = assetAmountScaled + .add(oTokenAmount) + .div(assetDivisor); + // scale back down to the asset decimals + const checkBalanceIncreaseExpected = + checkBalanceIncreaseScaled.div(assetMultiplier); + expect( + await strategy.checkBalance(vaultAsset.address) + ).to.be.approxEqualTolerance( + checkBalanceBefore.add(checkBalanceIncreaseExpected), + 0.01 // 1 basis point tolerance + ); + }; + + /***************************************** + Assert Withdraw + *****************************************/ + + const assertWithdraw = async (tx, withdrawAmount, oTokenAmount) => { + const { curvePool, oToken, strategy, vaultAsset } = context(); + + // strategy Withdrawal event for the vault asset + await expect(tx).to.emit(strategy, "Withdrawal").withNamedArgs({ + _asset: vaultAsset.address, + _pToken: curvePool.address, + }); + // .withArgs(vaultAsset.address, curvePool.address, withdrawAmount); + + // strategy Withdrawal event for the OToken + await expect(tx).to.emit(strategy, "Withdrawal").withNamedArgs({ + _asset: oToken.address, + _pToken: curvePool.address, + }); + const receipt = await tx.wait(); + const oTokenWithdrawEvent = receipt.events.find( + (e) => e.event === "Withdrawal" && e.args[0] === oToken.address + ); + expect(oTokenWithdrawEvent).to.not.be.undefined; + expect(oTokenWithdrawEvent.args[0]).to.eq(oToken.address); + // OToken amount == deposit amount scaled up to 18 decimals + expect(oTokenWithdrawEvent.args[2]).to.approxEqualTolerance( + oTokenAmount, + 0.01 + ); + }; + + /***************************************** + Assert Rebalancing + *****************************************/ + + const assertMintOTokens = async (oTokenAmount) => { + const { curvePool, oToken, strategist, strategy } = context(); + const tx = strategy.connect(strategist).mintAndAddOTokens(oTokenAmount); + + await expect(tx) + .to.emit(strategy, "Deposit") + .withArgs(oToken.address, curvePool.address, oTokenAmount); + // OToken is minted to the the strategy + await expect(tx) + .to.emit(oToken, "Transfer") + .withArgs(addresses.zero, strategy.address, oTokenAmount); + // OToken is transferred to the Curve pool + await expect(tx) + .to.emit(oToken, "Transfer") + .withArgs(strategy.address, curvePool.address, oTokenAmount); + }; + const assertRemoveOTokens = async (oTokenAmount) => { + const { curvePool, oToken, strategist, strategy } = context(); + + const tx = await strategy + .connect(strategist) + .removeAndBurnOTokens(oTokenAmount); + + await expect(tx) + .to.emit(strategy, "Withdrawal") + .withArgs(oToken.address, curvePool.address, oTokenAmount); + // OToken is removed from the pool to the strategy + await expect(tx) + .to.emit(oToken, "Transfer") + .withArgs(curvePool.address, strategy.address, oTokenAmount); + // OToken is burned from the strategy + await expect(tx) + .to.emit(oToken, "Transfer") + .withArgs(strategy.address, addresses.zero, oTokenAmount); + }; + const assertRemoveAssets = async (lpTokens) => { + const { + assetDivisor, + curvePool, + poolAssetAddress, + strategist, + strategy, + vaultAsset, + } = context(); + + const tx = await strategy.connect(strategist).removeOnlyAssets(lpTokens); + + const vaultAssetDecimals = await vaultAsset.decimals(); + const assetMultiplier = BigNumber.from(10).pow(18 - vaultAssetDecimals); + // scale down to the asset's decimals + const assetAmount = lpTokens.div(assetMultiplier).div(assetDivisor); + + // TODO need to loop over all 3 assets (DAI/USDT/USDC) for OUSD AMO + await expect(tx).to.emit(strategy, "Withdrawal").withNamedArgs({ + _asset: vaultAsset.address, + _pToken: curvePool.address, + }); + + // TODO Ignore for now if OUSD AMO as assetAmount depends on the 3Pool balances + const threePoolToken = await ethers.getContract("Mock3CRV"); + if (poolAssetAddress !== threePoolToken.address) { + const receipt = await tx.wait(); + const WithdrawEvent = receipt.events.find( + (e) => e.event === "Withdrawal" && e.args[0] === vaultAsset.address + ); + expect(WithdrawEvent).to.not.be.undefined; + expect(WithdrawEvent.args[2]).to.eq(assetAmount); + + // Asset token is removed from the pool to the strategy + // ETH transfer from pool will not emit a Transfer event + if (poolAssetAddress !== addresses.ETH) { + await expect(tx) + .to.emit(vaultAsset, "Transfer") + .withArgs(curvePool.address, strategy.address, assetAmount); + // The transfer to the vault can go via another contract. eg WETH or 3Pool + } + } + }; + + /***************************************** + Assert Failed Rebalancing + *****************************************/ + + const assertMintOTokensFail = async (oTokenAmount, errorMsg) => { + const { strategist, strategy } = context(); + const tx = strategy.connect(strategist).mintAndAddOTokens(oTokenAmount); + await expect(tx).to.revertedWith(errorMsg); + }; + const assertRemoveOTokensFail = async (oTokenAmount, errorMsg) => { + const { strategist, strategy } = context(); + const tx = strategy.connect(strategist).removeAndBurnOTokens(oTokenAmount); + await expect(tx).to.revertedWith(errorMsg); + }; + const assertRemoveAssetsFail = async (lpTokens, errorMsg) => { + const { strategist, strategy } = context(); + const tx = strategy.connect(strategist).removeOnlyAssets(lpTokens); + await expect(tx).to.revertedWith(errorMsg); + }; + + /***************************************** + Unit Tests + *****************************************/ + + describe("AMO behaviour", () => { + const littleOTokens = parseUnits("1"); + const lotOTokens = parseUnits("25"); + const littleLpTokens = littleOTokens; + const lotLpTokens = lotOTokens; + it("Should have AMO configured", async () => { + const { + curvePool, + balancerPool, + oToken, + poolAssetAddress, + vaultAsset, + strategy, + } = context(); + expect(await strategy.lpToken()).to.equal( + curvePool.address || balancerPool.address + ); + expect(await strategy.vaultAsset()).to.equal(vaultAsset.address); + expect(await strategy.poolAsset()).to.equal(poolAssetAddress); + expect(await strategy.oToken()).to.equal(oToken.address); + }); + describe("with no assets in the strategy", () => { + describe("with no assets in the pool", () => { + it("Should deposit vault assets. OTokens == assets", async () => { + const { strategy, vault, vaultAsset } = context(); + + const depositAmountStr = "1000"; + const assetAmount = await units(depositAmountStr, vaultAsset); + const oTokenAmount = parseUnits(depositAmountStr); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner)["deposit(address,uint256)"](vaultAsset.address, assetAmount); + + await assertDeposit(tx, assetAmount, oTokenAmount); + }); + it("Should deposit vault assets using depositAll. OTokens == assets", async () => { + const { strategy, vault, vaultAsset } = context(); + + const depositAmountStr = "1000"; + const assetAmount = await units(depositAmountStr, vaultAsset); + const oTokenAmount = parseUnits(depositAmountStr); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + const tx = await strategy.connect(vaultSigner).depositAll(); + + await assertDeposit(tx, assetAmount, oTokenAmount); + }); + }); + }); + describe("with assets in the strategy", () => { + const initialDepositAmount = "800"; + beforeEach(async () => { + const { strategy, vault } = context(); + await fundStrategy(context(), initialDepositAmount); + const vaultSigner = await impersonateAndFund(vault.address); + await strategy.connect(vaultSigner).depositAll(); + }); + describe("with a balanced pool of OTokens and vault assets", () => { + beforeEach(async () => { + // Add 1/5 of the liquidity not owned by the strategy + curveAddLiquidity({ + ...context(), + assetAmount: "200", + oTokenAmount: "200", + }); + }); + it("Should deposit vault assets. OTokens == assets", async () => { + const { vaultAsset, strategy, vault } = context(); + + const depositAmountStr = "1000"; + const assetAmount = await units(depositAmountStr, vaultAsset); + const oTokenAmount = parseUnits(depositAmountStr); + + const checkBalanceBefore = await strategy.checkBalance( + vaultAsset.address + ); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + const tx = await strategy.connect(vaultSigner).depositAll(); + + await assertDeposit( + tx, + assetAmount, + oTokenAmount, + checkBalanceBefore + ); + }); + it("Should withdraw vault assets. OTokens == assets", async () => { + const { vault, vaultAsset, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + // TODO remove the -10 + const withdrawAmount = ( + await units(initialDepositAmount, vaultAsset) + ).sub(10); + const oTokenAmount = parseUnits(initialDepositAmount); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + vaultAsset.address, + withdrawAmount + ); + + await assertWithdraw(tx, withdrawAmount, oTokenAmount); + }); + it("Should withdraw all assets. OTokens == assets", async () => { + const { vault, vaultAsset, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const withdrawAmount = await units(initialDepositAmount, vaultAsset); + const oTokenAmount = parseUnits(initialDepositAmount); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner).withdrawAll(); + + await assertWithdraw(tx, withdrawAmount, oTokenAmount); + }); + it("Should not mint a little OTokens", async () => { + // diffBefore == 0 and diffAfter < 0 + // diffBefore > diffAfter + await assertMintOTokensFail(littleOTokens, "OTokens balance worse"); + }); + it("Should not remove a little OTokens", async () => { + // diffBefore == 0 and diffAfter > 0 + await assertRemoveOTokensFail(littleOTokens, "OTokens overshot peg"); + }); + it("Should not remove a little pool assets", async () => { + // diffBefore == 0 and diffAfter < 0 + // diffAfter > diffBefore + await assertRemoveAssetsFail(littleLpTokens, "OTokens balance worse"); + }); + it("Should not allow non-strategist to rebalance", async () => { + const { strategy, josh, harvester, timelock, governor, vault } = + context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const harvesterSigner = await impersonateAndFund(harvester.address); + + for (const signer of [ + josh, + harvesterSigner, + timelock, + governor, + vaultSigner, + ]) { + await expect( + strategy.connect(signer).mintAndAddOTokens(1000) + ).to.revertedWith("Caller is not the Strategist"); + await expect( + strategy.connect(signer).removeAndBurnOTokens(1000) + ).to.revertedWith("Caller is not the Strategist"); + await expect( + strategy.connect(signer).removeOnlyAssets(1000) + ).to.revertedWith("Caller is not the Strategist"); + } + }); + }); + describe("with a little more OTokens in the pool", () => { + beforeEach(async () => { + // Add 1/5 of the liquidity not owned by the strategy + await curveAddLiquidity({ + ...context(), + poolAssetAmount: "200", + oTokenAmount: "220", + }); + }); + it("Should deposit vault assets. OTokens == assets", async () => { + const { curvePool, vaultAsset, oToken, strategy, vault } = context(); + const depositAmountStr = "1000"; + const depositAmount = await units(depositAmountStr, vaultAsset); + const oTokenAmount = parseUnits(depositAmountStr); + + await fundStrategy(context(), depositAmount); + const vaultSigner = await impersonateAndFund(vault.address); + const tx = await strategy.connect(vaultSigner).depositAll(); + + await expect(tx) + .to.emit(strategy, "Deposit") + .withArgs(vaultAsset.address, curvePool.address, depositAmount); + // OToken amount == deposit amount scaled up to 18 decimals + await expect(tx) + .to.emit(strategy, "Deposit") + .withArgs(oToken.address, curvePool.address, oTokenAmount); + }); + it("Should withdraw vault assets. OTokens > assets", async () => { + const { curvePool, vault, vaultAsset, oToken, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + // For OUSD, this is scaled to 6 decimals + const initialDepositAmountScaled = await units( + initialDepositAmount, + vaultAsset + ); + // Withdraw 1% less than the initial deposit amount + const withdrawAssetAmount = initialDepositAmountScaled + .mul(99) + .div(100); + const oTokenAmount = parseUnits(initialDepositAmount) + .mul(99) + .div(100); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + vaultAsset.address, + withdrawAssetAmount + ); + + await expect(tx) + .to.emit(strategy, "Withdrawal") + .withArgs( + vaultAsset.address, + curvePool.address, + withdrawAssetAmount + ); + await expect(tx).to.emit(strategy, "Withdrawal").withNamedArgs({ + _asset: oToken.address, + _pToken: curvePool.address, + }); + const receipt = await tx.wait(); + const oTokenWithdrawEvent = receipt.events.find( + (e) => e.event === "Withdrawal" && e.args[0] === oToken.address + ); + expect(oTokenWithdrawEvent).to.not.be.undefined; + expect(oTokenWithdrawEvent.args[0]).to.eq(oToken.address); + // OToken amount > deposit amount scaled up to 18 decimals + expect(oTokenWithdrawEvent.args[2]).to.gt( + oTokenAmount.add(littleOTokens) + ); + expect(oTokenWithdrawEvent.args[2]).to.lt(oTokenAmount.mul(2)); + }); + it("Should not mint a little OTokens", async () => { + // diffBefore < 0 and diffBefore > diffAfter + await assertMintOTokensFail(littleOTokens, "OTokens balance worse"); + }); + it("Should remove a little OTokens", async () => { + // diffBefore < 0 and diffBefore < diffAfter < 0 + await assertRemoveOTokens(littleOTokens); + }); + it("Should not remove a lot of OTokens", async () => { + // diffBefore < 0 and diffBefore < diffAfter and diffAfter > 0 + await assertRemoveOTokensFail(lotOTokens, "OTokens overshot peg"); + }); + it("Should not remove a little pool assets", async () => { + // diffBefore < 0, diffAfter < 0 and diffAfter > diffBefore + // diffAfter > diffBefore + await assertRemoveAssetsFail(littleLpTokens, "OTokens balance worse"); + }); + }); + describe("with a lot more OTokens in the pool", () => { + beforeEach(async () => { + await curveAddLiquidity({ + ...context(), + poolAssetAmount: "0", + oTokenAmount: "900", + }); + }); + it("Should deposit vault assets. OTokens == assets", async () => { + const { vaultAsset, strategy, vault } = context(); + + const checkBalanceBefore = await strategy.checkBalance( + vaultAsset.address + ); + + const depositAmountStr = "300"; + const assetAmount = await units(depositAmountStr, vaultAsset); + let oTokenAmount = parseUnits(depositAmountStr); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + const tx = await strategy.connect(vaultSigner).depositAll(); + + await assertDeposit( + tx, + assetAmount, + oTokenAmount, + checkBalanceBefore + ); + }); + // TODO fix for OUSD AMO + it.skip("Should withdraw vault assets. OTokens > assets", async () => { + const { vault, vaultAsset, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const { assetAmount, oTokenAmount } = await calcWithdrawAmounts( + context() + ); + const assetAmountAdjusted = assetAmount.sub(10); + const oTokenAmountAdjusted = oTokenAmount.sub(10); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + vaultAsset.address, + assetAmountAdjusted + ); + + await assertWithdraw(tx, assetAmountAdjusted, oTokenAmountAdjusted); + }); + it("Should withdraw all assets. OTokens > assets", async () => { + const { vault, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const { assetAmount, oTokenAmount } = await calcWithdrawAmounts( + context() + ); + + const tx = await strategy.connect(vaultSigner).withdrawAll(); + + await assertWithdraw(tx, assetAmount, oTokenAmount); + }); + }); + describe("with a little more pool assets in the pool", () => { + beforeEach(async () => { + await curveAddLiquidity({ + ...context(), + poolAssetAmount: "210", + oTokenAmount: "200", + }); + }); + it("Should deposit vault assets. OTokens > assets", async () => { + const { assetIndex, curvePool, vaultAsset, strategy, vault } = + context(); + + const checkBalanceBefore = await strategy.checkBalance( + vaultAsset.address + ); + + const depositAmountStr = "300"; + const assetAmount = await units(depositAmountStr, vaultAsset); + const balances = await curvePool.get_balances(); + let oTokenAmount = calcOTokensMinted( + depositAmountStr, + balances, + assetIndex + ); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + const tx = await strategy.connect(vaultSigner).depositAll(); + + await assertDeposit( + tx, + assetAmount, + oTokenAmount, + checkBalanceBefore + ); + }); + // Should withdraw remove assets. OTokens < assets + it("Should mint a little OTokens", async () => { + // diffBefore > 0, diffBefore > diffAfter > 0 + await assertMintOTokens(littleOTokens); + }); + it("Should not mint a lot of OTokens", async () => { + // diffBefore > 0, diffBefore > diffAfter < 0 + await assertMintOTokensFail(lotOTokens, "Assets overshot peg"); + }); + it("Should not remove a little OTokens", async () => { + // diffBefore > 0 and diffAfter > diffBefore + await assertRemoveOTokensFail(littleOTokens, "Assets balance worse"); + }); + it("Should remove a little pool assets", async () => { + // diffBefore > 0 and diffAfter < diffBefore + await assertRemoveAssets(littleLpTokens); + }); + it("Should not remove a lot pool assets", async () => { + // diffBefore > 0 and diffAfter < 0 + await assertRemoveAssetsFail(lotLpTokens, "Assets overshot peg"); + }); + }); + describe("with a lot more pool assets in the pool", () => { + beforeEach(async () => { + await curveAddLiquidity({ + ...context(), + poolAssetAmount: "700", + oTokenAmount: "0", + }); + }); + it("Should deposit vault assets. OTokens == 2x assets", async () => { + const { vaultAsset, strategy, vault } = context(); + + const checkBalanceBefore = await strategy.checkBalance( + vaultAsset.address + ); + + const depositAmountStr = "300"; + const assetAmount = await units(depositAmountStr, vaultAsset); + // Max OTokens minted is 2x the deposit amount + let oTokenAmount = parseUnits(depositAmountStr).mul(2); + + await fundStrategy(context(), assetAmount); + const vaultSigner = await impersonateAndFund(vault.address); + + const tx = await strategy.connect(vaultSigner).depositAll(); + + await assertDeposit( + tx, + assetAmount, + oTokenAmount, + checkBalanceBefore + ); + }); + // TODO fix for OUSD AMO + it.skip("Should withdraw vault assets. OTokens < assets", async () => { + const { vault, vaultAsset, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + + const { assetAmount, oTokenAmount } = await calcWithdrawAmounts( + context() + ); + const assetAmountAdjusted = assetAmount.sub(20); + const oTokenAmountAdjusted = oTokenAmount.sub(20); + + // prettier-ignore + const tx = await strategy + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + vaultAsset.address, + assetAmountAdjusted + ); + + await assertWithdraw(tx, assetAmountAdjusted, oTokenAmountAdjusted); + }); + it("Should withdraw all assets. OTokens < assets", async () => { + const { vault, strategy } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const { assetAmount, oTokenAmount } = await calcWithdrawAmounts( + context() + ); + + const tx = await strategy.connect(vaultSigner).withdrawAll(); + + await assertWithdraw(tx, assetAmount, oTokenAmount); + }); + }); + }); + }); +}; + +const fundStrategy = async (fixture, amount = "1000") => { + const { strategy, vaultAsset, vault } = fixture; + + const vaultSigner = await impersonateAndFund(vault.address); + + // deposit some assets into the strategy so we can withdraw them + const depositAmount = BigNumber.isBigNumber(amount) + ? amount + : await units(amount, vaultAsset); + // mint some test assets to the vault + // can't mint directly to strategy as that requires ETH and with throw the OETH AMO tests + await vaultAsset.connect(vaultSigner).mint(depositAmount); + // transfer test assets to the strategy + await vaultAsset + .connect(vaultSigner) + .transfer(strategy.address, depositAmount); +}; + +const curveAddLiquidity = async (fixture) => { + const { + poolAssetAmount, + curvePool, + josh, + oToken, + oTokenAmount, + poolAssetAddress, + vault, + vaultAsset, + } = fixture; + + // Give Josh some pool assets if not ETH. eg 3Crv, WETH or frxETH + if (poolAssetAddress !== addresses.ETH) { + const poolAsset = await ethers.getContractAt( + "MintableERC20", + poolAssetAddress + ); + // Mint some Curve pool assets + // Note for 3Crv this will not increase the total supply of the 3Pool + await poolAsset.connect(josh).mint(parseUnits(poolAssetAmount ?? "0")); + // Approve the Curve pool to transfer the pool assets + await poolAsset.connect(josh).approve(curvePool.address, MAX_UINT256); + } + + // Give Josh some OTokens by minting them + if (oTokenAmount && oTokenAmount !== "0") { + const mintAmount = await units(oTokenAmount ?? "0", vaultAsset); + await vaultAsset.connect(josh).mint(mintAmount); + await vaultAsset.connect(josh).approve(vault.address, MAX_UINT256); + await vault.connect(josh).mint(vaultAsset.address, mintAmount, 0); + // Approve the Curve pool to transfer OTokens + await oToken.connect(josh).approve(curvePool.address, MAX_UINT256); + } + + const amounts = poolAmounts(fixture); + + const value = + poolAssetAddress === addresses.ETH ? parseUnits(poolAssetAmount ?? "0") : 0; + + // prettier-ignore + await curvePool + .connect(josh)["add_liquidity(uint256[2],uint256)"](amounts, 0, {value}); +}; + +/// Returns the pool amounts in the order of the Curve pool +const poolAmounts = (fixture) => { + const { assetIndex, poolAssetAmount, oTokenAmount } = fixture; + + if (assetIndex !== 0 && assetIndex !== 1) { + throw Error("Missing assetIndex of the pool"); + } + + const poolAssetAmountScaled = parseUnits(poolAssetAmount ?? "0"); + const oTokenAmountScaled = parseUnits(oTokenAmount ?? "0"); + return assetIndex === 0 + ? [poolAssetAmountScaled, oTokenAmountScaled] + : [oTokenAmountScaled, poolAssetAmountScaled]; +}; + +const calcOTokensMinted = (depositAmountStr, balances, assetIndex) => { + let oTokenAmount = parseUnits(depositAmountStr); + + const assetBalance = balances[assetIndex]; + const oTokenBalance = balances[(assetIndex + 1) % 2]; + const balanceDiff = assetBalance.sub(oTokenBalance); + + // If more assets than OTokens + if (balanceDiff.gt(0)) { + oTokenAmount = balanceDiff.gt(oTokenAmount.mul(2)) + ? oTokenAmount.mul(2) + : oTokenAmount.add(balanceDiff); + } + + return oTokenAmount; +}; + +const calcWithdrawAmounts = async (fixture) => { + const { curvePool, convexPID, strategy, assetIndex } = fixture; + + // get balances from the Curve pool + const balances = await curvePool.get_balances(); + const totalLpSupply = await curvePool.totalSupply(); + + // get the strategy's Curve LP tokens from Convex pool + // Get the Convex rewards pool contract + const mockBooster = await ethers.getContract("MockBooster"); + const poolInfo = await mockBooster.poolInfo(convexPID); + const convexRewardPool = await ethers.getContractAt( + "MockRewardPool", + poolInfo.crvRewards + ); + const strategyLpTokens = await convexRewardPool.balanceOf(strategy.address); + + const withdrawBalances = balances.map((bal) => + bal.mul(strategyLpTokens).div(totalLpSupply) + ); + + const assetAmount = withdrawBalances[assetIndex]; + const oTokenAmount = withdrawBalances[(assetIndex + 1) % 2]; + + return { + assetAmount, + oTokenAmount, + }; +}; + +module.exports = { + shouldBehaveLikeAmo, +}; diff --git a/contracts/test/behaviour/strategy.js b/contracts/test/behaviour/strategy.js index 7faf1c2f37..9a16ceaa64 100644 --- a/contracts/test/behaviour/strategy.js +++ b/contracts/test/behaviour/strategy.js @@ -97,9 +97,9 @@ const shouldBehaveLikeStrategy = (context) => { // mint some test assets directly into the strategy contract await asset.connect(strategySigner).mint(depositAmount); + // prettier-ignore const tx = await strategy - .connect(vaultSigner) - .deposit(asset.address, depositAmount); + .connect(vaultSigner)["deposit(address,uint256)"](asset.address, depositAmount); const platformAddress = await strategy.assetToPToken(asset.address); await expect(tx) @@ -122,10 +122,10 @@ const shouldBehaveLikeStrategy = (context) => { const harvesterSigner = await impersonateAndFund(harvester.address); for (const signer of [harvesterSigner, governor, strategist, matt]) { + // prettier-ignore await expect( strategy - .connect(signer) - .deposit(assets[0].address, parseUnits("10")) + .connect(signer)["deposit(address,uint256)"](assets[0].address, parseUnits("10")) ).to.revertedWith("Caller is not the Vault"); } }); @@ -157,7 +157,9 @@ const shouldBehaveLikeStrategy = (context) => { for (const asset of assets) { await expect( - strategy.connect(vaultSigner).deposit(asset.address, 0) + // prettier-ignore + strategy + .connect(vaultSigner)["deposit(address,uint256)"](asset.address, 0) ).to.be.revertedWith("Must deposit something"); } }); @@ -177,9 +179,13 @@ const shouldBehaveLikeStrategy = (context) => { for (const asset of assets) { await expect( + // prettier-ignore strategy - .connect(vaultSigner) - .withdraw(vault.address, asset.address, 0) + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + asset.address, + 0 + ) ).to.be.revertedWith("Must withdraw something"); } }); @@ -197,9 +203,13 @@ const shouldBehaveLikeStrategy = (context) => { const harvesterSigner = await impersonateAndFund(harvester.address); for (const signer of [harvesterSigner, governor, strategist, matt]) { await expect( + // prettier-ignore strategy - .connect(signer) - .withdraw(vault.address, assets[0].address, parseUnits("10")) + .connect(signer)["withdraw(address,address,uint256)"]( + vault.address, + assets[0].address, + parseUnits("10") + ) ).to.revertedWith("Caller is not the Vault"); } }); @@ -231,15 +241,19 @@ const shouldBehaveLikeStrategy = (context) => { }); describe("with assets in the strategy", () => { beforeEach(async () => { - const { assets, strategy, vault } = context(); - const strategySigner = await impersonateAndFund(strategy.address); + const { assets, strategy, vault } = await context(); const vaultSigner = await impersonateAndFund(vault.address); // deposit some assets into the strategy so we can withdraw them for (const [i, asset] of assets.entries()) { - const depositAmount = await units("10000", asset); - // mint some test assets directly into the strategy contract - await asset.connect(strategySigner).mint(depositAmount.mul(i + 1)); + const depositAmount = (await units("10000", asset)).mul(i + 1); + // mint some test assets to the vault + // can't mint directly to strategy as that requires ETH and with throw the OETH AMO tests + await asset.connect(vaultSigner).mint(depositAmount); + // transfer test assets to the strategy + await asset + .connect(vaultSigner) + .transfer(strategy.address, depositAmount); } await strategy.connect(vaultSigner).depositAll(); }); @@ -268,9 +282,13 @@ const shouldBehaveLikeStrategy = (context) => { const platformAddress = await strategy.assetToPToken(asset.address); const withdrawAmount = (await units("8000", asset)).mul(i + 1); + // prettier-ignore const tx = await strategy - .connect(vaultSigner) - .withdraw(vault.address, asset.address, withdrawAmount); + .connect(vaultSigner)["withdraw(address,address,uint256)"]( + vault.address, + asset.address, + withdrawAmount + ); await expect(tx) .to.emit(strategy, "Withdrawal") @@ -411,6 +429,23 @@ const shouldBehaveLikeStrategy = (context) => { ).to.revertedWith("Caller is not the Governor"); } }); + it("Should allow governor to reset allowances", async () => { + const { strategy, governor } = context(); + await expect(strategy.connect(governor).safeApproveAllTokens()).to.not.be + .reverted; + }); + it("Should not allow non-governor to reset allowances", async () => { + const { anna, strategist, strategy, harvester, vault } = context(); + + const vaultSigner = await impersonateAndFund(vault.address); + const harvesterSigner = await impersonateAndFund(harvester.address); + + for (const signer of [anna, strategist, harvesterSigner, vaultSigner]) { + await expect( + strategy.connect(signer).safeApproveAllTokens() + ).to.be.revertedWith("Caller is not the Governor"); + } + }); it("Should allow reward tokens to be set by the governor", async () => { const { governor, strategy, comp, crv, bal } = context(); diff --git a/contracts/test/buyback/buyback.fork-test.js b/contracts/test/buyback/buyback.fork-test.js index d50bacced6..1466581704 100644 --- a/contracts/test/buyback/buyback.fork-test.js +++ b/contracts/test/buyback/buyback.fork-test.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { createFixtureLoader, buybackFixture } = require("../_fixture"); +const { createFixtureLoader, buybackFixture } = require("../fixture/_fixture"); const { ousdUnits, oethUnits } = require("../helpers"); const loadFixture = createFixtureLoader(buybackFixture); diff --git a/contracts/test/buyback/buyback.js b/contracts/test/buyback/buyback.js index 2da8340f58..1191822bfb 100644 --- a/contracts/test/buyback/buyback.js +++ b/contracts/test/buyback/buyback.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { createFixtureLoader, buybackFixture } = require("../_fixture"); +const { createFixtureLoader, buybackFixture } = require("../fixture/_fixture"); const { ousdUnits, usdcUnits, oethUnits } = require("../helpers"); const loadFixture = createFixtureLoader(buybackFixture); diff --git a/contracts/test/compensation/compensationClaims.js b/contracts/test/compensation/compensationClaims.js index 506d0fb2ed..a9827584d1 100644 --- a/contracts/test/compensation/compensationClaims.js +++ b/contracts/test/compensation/compensationClaims.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { parseUnits } = require("ethers").utils; -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, usdcUnits, diff --git a/contracts/test/_fixture.js b/contracts/test/fixture/_fixture.js similarity index 83% rename from contracts/test/_fixture.js rename to contracts/test/fixture/_fixture.js index 5ec447b68c..38e3560b09 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/fixture/_fixture.js @@ -2,24 +2,31 @@ const hre = require("hardhat"); const { ethers } = hre; const { BigNumber } = ethers; const { expect } = require("chai"); -const { formatUnits } = require("ethers/lib/utils"); +const { + defaultAbiCoder, + formatUnits, + parseEther, + parseUnits, +} = require("ethers/lib/utils"); const mocha = require("mocha"); -require("./_global-hooks"); +require("../_global-hooks"); -const { hotDeployOption } = require("./_hot-deploy.js"); -const addresses = require("../utils/addresses"); -const { setFraxOraclePrice } = require("../utils/frax"); +const { hotDeployOption } = require("../_hot-deploy.js"); +const addresses = require("../../utils/addresses"); +const { setFraxOraclePrice } = require("../../utils/frax"); //const { setChainlinkOraclePrice } = require("../utils/oracle"); const { balancer_rETH_WETH_PID, balancer_stETH_WETH_PID, -} = require("../utils/constants"); +} = require("../../utils/constants"); const { fundAccounts, fundAccountsForOETHUnitTests, -} = require("../utils/funding"); -const { replaceContractAt } = require("../utils/hardhat"); +} = require("../../utils/funding"); +const balancerStrategyDeployment = require("../../utils/balancerStrategyDeployment"); +const { replaceContractAt } = require("../../utils/hardhat"); +const { impersonateAndFund } = require("../../utils/signers"); const { getAssetAddresses, daiUnits, @@ -28,29 +35,25 @@ const { ousdUnits, units, isFork, -} = require("./helpers"); -const { hardhatSetBalance, setERC20TokenBalance } = require("./_fund"); +} = require("../helpers"); +const { hardhatSetBalance, setERC20TokenBalance } = require("../_fund"); -const daiAbi = require("./abi/dai.json").abi; -const usdtAbi = require("./abi/usdt.json").abi; -const erc20Abi = require("./abi/erc20.json"); -const morphoAbi = require("./abi/morpho.json"); -const morphoLensAbi = require("./abi/morphoLens.json"); -const crvMinterAbi = require("./abi/crvMinter.json"); -const sdaiAbi = require("./abi/sDAI.json"); +const daiAbi = require("../abi/dai.json").abi; +const usdtAbi = require("../abi/usdt.json").abi; +const erc20Abi = require("../abi/erc20.json"); +const morphoAbi = require("../abi/morpho.json"); +const morphoLensAbi = require("../abi/morphoLens.json"); +const crvMinterAbi = require("../abi/crvMinter.json"); +const sdaiAbi = require("../abi/sDAI.json"); // const curveFactoryAbi = require("./abi/curveFactory.json") -const ousdMetapoolAbi = require("./abi/ousdMetapool.json"); -const oethMetapoolAbi = require("./abi/oethMetapool.json"); -const threepoolLPAbi = require("./abi/threepoolLP.json"); -const threepoolSwapAbi = require("./abi/threepoolSwap.json"); - -const sfrxETHAbi = require("./abi/sfrxETH.json"); -const { defaultAbiCoder, parseUnits, parseEther } = require("ethers/lib/utils"); -const balancerStrategyDeployment = require("../utils/balancerStrategyDeployment"); -const { impersonateAndFund } = require("../utils/signers"); +const ousdMetapoolAbi = require("../abi/ousdMetapool.json"); +const curveOethEthPoolAbi = require("../abi/curveOethEthPool.json"); +const threepoolLPAbi = require("../abi/threepoolLP.json"); +const threepoolSwapAbi = require("../abi/threepoolSwap.json"); +const sfrxETHAbi = require("../abi/sfrxETH.json"); -const log = require("../utils/logger")("test:fixtures"); +const log = require("../../utils/logger")("test:fixtures"); let snapshotId; @@ -136,20 +139,12 @@ const defaultFixture = deployments.createFixture(async () => { convexStrategyProxy.address ); - const convexFrxEthWethStrategyProxy = await ethers.getContract( - "ConvexFrxEthWethStrategyProxy" - ); - const convexFrxEthWethStrategy = await ethers.getContractAt( - "ConvexTwoPoolStrategy", - convexFrxEthWethStrategyProxy.address - ); - - const OUSDmetaStrategyProxy = await ethers.getContract( + const convexOusdAMOStrategyProxy = await ethers.getContract( "ConvexOUSDMetaStrategyProxy" ); - const OUSDmetaStrategy = await ethers.getContractAt( + const convexOusdAMOStrategy = await ethers.getContractAt( "ConvexOUSDMetaStrategy", - OUSDmetaStrategyProxy.address + convexOusdAMOStrategyProxy.address ); const aaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); @@ -158,6 +153,38 @@ const defaultFixture = deployments.createFixture(async () => { aaveStrategyProxy.address ); + const LUSDMetaStrategyProxy = await ethers.getContract( + "ConvexLUSDMetaStrategyProxy" + ); + const LUSDMetaStrategy = await ethers.getContractAt( + "ConvexGeneralizedMetaStrategy", + LUSDMetaStrategyProxy.address + ); + + const fraxEthStrategyProxy = await ethers.getContract("FraxETHStrategyProxy"); + const fraxEthStrategy = await ethers.getContractAt( + "FraxETHStrategy", + fraxEthStrategyProxy.address + ); + + // AMO strategy for Curve OETH/ETH pool + const convexEthMetaStrategyProxy = await ethers.getContract( + "ConvexEthMetaStrategyProxy" + ); + const convexEthMetaStrategy = await ethers.getContractAt( + "ConvexEthMetaStrategy", + convexEthMetaStrategyProxy.address + ); + + // AMO strategy for Curve frxETH/OETH pool + const convexFrxETHAMOStrategyProxy = await ethers.getContract( + "ConvexFrxETHAMOStrategyProxy" + ); + const convexFrxETHAMOStrategy = await ethers.getContractAt( + "ConvexFrxETHAMOStrategy", + convexFrxETHAMOStrategyProxy.address + ); + const oracleRouter = await ethers.getContract("OracleRouter"); const oethOracleRouter = await ethers.getContract( isFork ? "OETHOracleRouter" : "OracleRouter" @@ -207,12 +234,6 @@ const defaultFixture = deployments.createFixture(async () => { threePoolToken, metapoolToken, morpho, - morphoCompoundStrategy, - fraxEthStrategy, - balancerREthStrategy, - makerDsrStrategy, - morphoAaveStrategy, - oethMorphoAaveStrategy, morphoLens, LUSDMetapoolToken, threePoolGauge, @@ -222,18 +243,20 @@ const defaultFixture = deployments.createFixture(async () => { flipper, cvx, cvxBooster, - cvxRewardPool, - LUSDMetaStrategy, + makerDsrStrategy, + morphoAaveStrategy, + morphoCompoundStrategy, + oethMorphoAaveStrategy, + balancerREthStrategy, + fluxStrategy, oethDripper, oethZapper, + vaultValueChecker, + oethVaultValueChecker, swapper, mockSwapper, swapper1Inch, - mock1InchSwapRouter, - convexEthMetaStrategy, - fluxStrategy, - vaultValueChecker, - oethVaultValueChecker; + mock1InchSwapRouter; if (isFork) { usdt = await ethers.getContractAt(usdtAbi, addresses.mainnet.USDT); @@ -282,11 +305,8 @@ const defaultFixture = deployments.createFixture(async () => { "MockBooster", addresses.mainnet.CVXBooster ); - cvxRewardPool = await ethers.getContractAt( - "IRewardStaking", - addresses.mainnet.CVXRewardsPool - ); + // Strategies that have not been deployed in 001_core const makerDsrStrategyProxy = await ethers.getContract( "MakerDsrStrategyProxy" ); @@ -295,14 +315,6 @@ const defaultFixture = deployments.createFixture(async () => { makerDsrStrategyProxy.address ); - const morphoCompoundStrategyProxy = await ethers.getContract( - "MorphoCompoundStrategyProxy" - ); - morphoCompoundStrategy = await ethers.getContractAt( - "MorphoCompoundStrategy", - morphoCompoundStrategyProxy.address - ); - const morphoAaveStrategyProxy = await ethers.getContract( "MorphoAaveStrategyProxy" ); @@ -311,6 +323,14 @@ const defaultFixture = deployments.createFixture(async () => { morphoAaveStrategyProxy.address ); + const morphoCompoundStrategyProxy = await ethers.getContract( + "MorphoCompoundStrategyProxy" + ); + morphoCompoundStrategy = await ethers.getContractAt( + "MorphoCompoundStrategy", + morphoCompoundStrategyProxy.address + ); + const oethMorphoAaveStrategyProxy = await ethers.getContract( "OETHMorphoAaveStrategyProxy" ); @@ -319,14 +339,6 @@ const defaultFixture = deployments.createFixture(async () => { oethMorphoAaveStrategyProxy.address ); - const fraxEthStrategyProxy = await ethers.getContract( - "FraxETHStrategyProxy" - ); - fraxEthStrategy = await ethers.getContractAt( - "FraxETHStrategy", - fraxEthStrategyProxy.address - ); - const balancerRethStrategyProxy = await ethers.getContract( "OETHBalancerMetaPoolrEthStrategyProxy" ); @@ -335,12 +347,10 @@ const defaultFixture = deployments.createFixture(async () => { balancerRethStrategyProxy.address ); - const convexEthMetaStrategyProxy = await ethers.getContract( - "ConvexEthMetaStrategyProxy" - ); - convexEthMetaStrategy = await ethers.getContractAt( - "ConvexEthMetaStrategy", - convexEthMetaStrategyProxy.address + const fluxStrategyProxy = await ethers.getContract("FluxStrategyProxy"); + fluxStrategy = await ethers.getContractAt( + "CompoundStrategy", + fluxStrategyProxy.address ); const oethDripperProxy = await ethers.getContract("OETHDripperProxy"); @@ -351,16 +361,10 @@ const defaultFixture = deployments.createFixture(async () => { oethZapper = await ethers.getContract("OETHZapper"); - swapper = await ethers.getContract("Swapper1InchV5"); - - const fluxStrategyProxy = await ethers.getContract("FluxStrategyProxy"); - fluxStrategy = await ethers.getContractAt( - "CompoundStrategy", - fluxStrategyProxy.address - ); - vaultValueChecker = await ethers.getContract("VaultValueChecker"); oethVaultValueChecker = await ethers.getContract("OETHVaultValueChecker"); + + swapper = await ethers.getContract("Swapper1InchV5"); } else { usdt = await ethers.getContract("MockUSDT"); dai = await ethers.getContract("MockDAI"); @@ -393,7 +397,6 @@ const defaultFixture = deployments.createFixture(async () => { LUSDMetapoolToken = await ethers.getContract("MockCurveLUSDMetapool"); threePoolGauge = await ethers.getContract("MockCurveGauge"); cvxBooster = await ethers.getContract("MockBooster"); - cvxRewardPool = await ethers.getContract("MockRewardPool"); adai = await ethers.getContract("MockADAI"); aaveToken = await ethers.getContract("MockAAVEToken"); @@ -440,21 +443,6 @@ const defaultFixture = deployments.createFixture(async () => { flipper = await ethers.getContract("Flipper"); - const LUSDMetaStrategyProxy = await ethers.getContract( - "ConvexLUSDMetaStrategyProxy" - ); - LUSDMetaStrategy = await ethers.getContractAt( - "ConvexGeneralizedMetaStrategy", - LUSDMetaStrategyProxy.address - ); - - const fraxEthStrategyProxy = await ethers.getContract( - "FraxETHStrategyProxy" - ); - fraxEthStrategy = await ethers.getContractAt( - "FraxETHStrategy", - fraxEthStrategyProxy.address - ); swapper = await ethers.getContract("MockSwapper"); mockSwapper = await ethers.getContract("MockSwapper"); swapper1Inch = await ethers.getContract("Swapper1InchV5"); @@ -556,27 +544,29 @@ const defaultFixture = deployments.createFixture(async () => { ausdc, // CompoundStrategy contract factory to deploy CompoundStrategyFactory, - // ThreePool + // Curve crv, crvMinter, threePool, threePoolGauge, threePoolToken, metapoolToken, + LUSDMetapoolToken, + // Morpho morpho, morphoLens, - LUSDMetapoolToken, + // Strategies threePoolStrategy, convexStrategy, - OUSDmetaStrategy, + convexOusdAMOStrategy, LUSDMetaStrategy, makerDsrStrategy, morphoCompoundStrategy, morphoAaveStrategy, + // Convex cvx, cvxBooster, - cvxRewardPool, - + // Aave aaveStrategy, aaveToken, aaveAddressProvider, @@ -606,7 +596,7 @@ const defaultFixture = deployments.createFixture(async () => { oethMorphoAaveStrategy, woeth, convexEthMetaStrategy, - convexFrxEthWethStrategy, + convexFrxETHAMOStrategy, oethDripper, oethHarvester, oethZapper, @@ -1118,21 +1108,19 @@ async function balancerWstEthFixture() { } /** - * Configure a Vault with only the Meta strategy. + * Configure a Vault with only the OUSD AMO Strategy using the Curve OUSD/3CRV pool. */ -async function convexMetaVaultFixture() { +async function convexOusdAmoFixture() { const fixture = await defaultFixture(); if (isFork) { const { josh, matt, anna, domen, daniel, franck, ousd } = fixture; - // const curveFactoryAddress = '0xB9fC157394Af804a3578134A6585C0dc9cc990d4' - const threepoolLP = await ethers.getContractAt( threepoolLPAbi, addresses.mainnet.ThreePoolToken ); - const ousdMetaPool = await ethers.getContractAt( + const curveOusd3CrvMetapool = await ethers.getContractAt( ousdMetapoolAbi, addresses.mainnet.CurveOUSDMetaPool ); @@ -1142,7 +1130,7 @@ async function convexMetaVaultFixture() { ); // const curveFactory = await ethers.getContractAt(curveFactoryAbi, curveFactoryAddress) - const balances = await ousdMetaPool.get_balances(); + const balances = await curveOusd3CrvMetapool.get_balances(); log(`Metapool balance 0: ${formatUnits(balances[0])}`); log(`Metapool balance 1: ${formatUnits(balances[1])}`); @@ -1151,12 +1139,12 @@ async function convexMetaVaultFixture() { await setERC20TokenBalance(domen.address, threepoolLP, "1000000", hre); for (const user of [josh, matt, anna, domen, daniel, franck]) { - // Approve OUSD MetaPool contract to move funds - await resetAllowance(threepoolLP, user, ousdMetaPool.address); - await resetAllowance(ousd, user, ousdMetaPool.address); + // Approve OUSD/3Crv Metapool contract to move funds + await resetAllowance(threepoolLP, user, curveOusd3CrvMetapool.address); + await resetAllowance(ousd, user, curveOusd3CrvMetapool.address); } - fixture.ousdMetaPool = ousdMetaPool; + fixture.curveOusd3CrvMetapool = curveOusd3CrvMetapool; fixture.threePoolToken = threepoolLP; fixture.threepoolSwap = threepoolSwap; } else { @@ -1167,35 +1155,51 @@ async function convexMetaVaultFixture() { // Add Convex Meta strategy await fixture.vault .connect(sGovernor) - .approveStrategy(fixture.OUSDmetaStrategy.address); + .approveStrategy(fixture.convexOusdAMOStrategy.address); + + // Impersonate the OUSD Vault + fixture.vaultSigner = await impersonateAndFund(fixture.vault.address); // set meta strategy on vault so meta strategy is allowed to mint OUSD await fixture.vault .connect(sGovernor) - .setOusdMetaStrategy(fixture.OUSDmetaStrategy.address); + .setAMOStrategy(fixture.convexOusdAMOStrategy.address, true); // set OUSD mint threshold to 50 million await fixture.vault .connect(sGovernor) - .setNetOusdMintForStrategyThreshold(parseUnits("50", 24)); + .setMintForStrategyThreshold( + fixture.convexOusdAMOStrategy.address, + parseUnits("50", 24) + ); await fixture.harvester .connect(sGovernor) - .setSupportedStrategy(fixture.OUSDmetaStrategy.address, true); + .setSupportedStrategy(fixture.convexOusdAMOStrategy.address, true); await fixture.vault .connect(sGovernor) .setAssetDefaultStrategy( fixture.usdt.address, - fixture.OUSDmetaStrategy.address + fixture.convexOusdAMOStrategy.address ); await fixture.vault .connect(sGovernor) .setAssetDefaultStrategy( fixture.usdc.address, - fixture.OUSDmetaStrategy.address + fixture.convexOusdAMOStrategy.address ); + + const assetAddresses = await getAssetAddresses(deployments); + fixture.curveOusd3CrvMetapool = await ethers.getContractAt( + ousdMetapoolAbi, + assetAddresses.ThreePoolOUSDMetapool + ); + fixture.threePoolToken = await ethers.getContractAt( + threepoolLPAbi, + assetAddresses.ThreePoolToken + ); } return fixture; @@ -1533,9 +1537,9 @@ async function convexLUSDMetaVaultFixture() { } /** - * Configure a Vault with only the OETH/(W)ETH Curve Metastrategy. + * Configure a Vault with only the AMO strategy for the Curve OETH/ETH pool. */ -async function convexOETHMetaVaultFixture( +async function convexOethEthAmoFixture( config = { wethMintAmount: 0, depositToStrategy: false, @@ -1545,29 +1549,72 @@ async function convexOETHMetaVaultFixture( } ) { const fixture = await oethDefaultFixture(); - await hotDeployOption(fixture, "convexOETHMetaVaultFixture", { + await hotDeployOption(fixture, "convexOethEthAmoFixture", { isOethFixture: true, }); const { convexEthMetaStrategy, oeth, + oethHarvester, oethVault, josh, + governor, strategist, timelock, weth, crv, } = fixture; - await impersonateAndFund(josh.address); - await setERC20TokenBalance(josh.address, weth, "10000000", hre); - await setERC20TokenBalance(josh.address, crv, "10000000", hre); + if (isFork) { + await setERC20TokenBalance(josh.address, weth, "10000000", hre); + await setERC20TokenBalance(josh.address, crv, "10000000", hre); + + // Impersonate the Curve gauge that holds all the LP tokens + fixture.curveOethEthGaugeSigner = await impersonateAndFund( + addresses.mainnet.CurveOETHGauge + ); + + // Convex pool that records the deposited balances + fixture.convexOethEthRewardsPool = await ethers.getContractAt( + "IRewardStaking", + addresses.mainnet.CVXETHRewardsPool + ); + + fixture.curveOethEthPool = await ethers.getContractAt( + curveOethEthPoolAbi, + addresses.mainnet.CurveOETHMetaPool + ); + } else { + // Approve strategy for unit tests + await oethVault + .connect(governor) + .approveStrategy(convexEthMetaStrategy.address); + await oethVault + .connect(governor) + .setAMOStrategy(convexEthMetaStrategy.address, true); + + await convexEthMetaStrategy + .connect(governor) + .setHarvesterAddress(oethHarvester.address); + await oethHarvester + .connect(governor) + .setSupportedStrategy(convexEthMetaStrategy.address, true); + + const assetAddresses = await getAssetAddresses(deployments); + fixture.curveOethEthPool = await ethers.getContractAt( + curveOethEthPoolAbi, + assetAddresses.CurveOethEthPool + ); + } // Update the strategy threshold to 500k ETH await oethVault .connect(timelock) - .setNetOusdMintForStrategyThreshold(parseUnits("500", 21)); + .setMintForStrategyThreshold( + convexEthMetaStrategy.address, + parseUnits("500", 21) + ); // Impersonate the OETH Vault fixture.oethVaultSigner = await impersonateAndFund(oethVault.address); @@ -1576,18 +1623,7 @@ async function convexOETHMetaVaultFixture( addresses.mainnet.CurveOETHGauge ); - // Convex pool that records the deposited balances - fixture.cvxRewardPool = await ethers.getContractAt( - "IRewardStaking", - addresses.mainnet.CVXETHRewardsPool - ); - - fixture.oethMetaPool = await ethers.getContractAt( - oethMetapoolAbi, - addresses.mainnet.CurveOETHMetaPool - ); - - // mint some OETH using WETH is configured + // mint some OETH using WETH if configured if (config?.wethMintAmount > 0) { const wethAmount = parseUnits(config.wethMintAmount.toString()); await oethVault.connect(josh).rebase(); @@ -1600,7 +1636,7 @@ async function convexOETHMetaVaultFixture( // This will sit in the vault, not the strategy await oethVault.connect(josh).mint(weth.address, wethAmount, 0); - // Add ETH to the Metapool + // Add ETH to the Curve pool if (config?.depositToStrategy) { // The strategist deposits the WETH to the AMO strategy await oethVault @@ -1614,8 +1650,8 @@ async function convexOETHMetaVaultFixture( } if (config?.balancePool) { - const ethBalance = await fixture.oethMetaPool.balances(0); - const oethBalance = await fixture.oethMetaPool.balances(1); + const ethBalance = await fixture.curveOethEthPool.balances(0); + const oethBalance = await fixture.curveOethEthPool.balances(1); const diff = parseInt( ethBalance.sub(oethBalance).div(oethUnits("1")).toString() @@ -1636,7 +1672,7 @@ async function convexOETHMetaVaultFixture( const ethAmount = parseUnits(config.poolAddEthAmount.toString(), 18); // prettier-ignore - await fixture.oethMetaPool + await fixture.curveOethEthPool .connect(josh)["add_liquidity(uint256[2],uint256)"]([ethAmount, 0], 0, { value: ethAmount, }); @@ -1645,22 +1681,180 @@ async function convexOETHMetaVaultFixture( const { oethWhaleAddress } = addresses.mainnet; fixture.oethWhale = await impersonateAndFund(oethWhaleAddress); - // Add OETH to the Metapool + // Add OETH to the Curve pool if (config?.poolAddOethAmount > 0) { const poolAddOethAmountUnits = parseUnits( config.poolAddOethAmount.toString() ); const oethAmount = await oeth.balanceOf(oethWhaleAddress); - log(`OETH whale balance : ${formatUnits(oethAmount)}`); - log(`OETH to add to Metapool: ${formatUnits(poolAddOethAmountUnits)}`); + log(`OETH whale balance : ${formatUnits(oethAmount)}`); + log(`OETH to add to Curve pool: ${formatUnits(poolAddOethAmountUnits)}`); expect(oethAmount).to.be.gte(poolAddOethAmountUnits); await oeth .connect(fixture.oethWhale) - .approve(fixture.oethMetaPool.address, poolAddOethAmountUnits); + .approve(fixture.curveOethEthPool.address, poolAddOethAmountUnits); // prettier-ignore - await fixture.oethMetaPool + await fixture.curveOethEthPool + .connect(fixture.oethWhale)["add_liquidity(uint256[2],uint256)"]([0, poolAddOethAmountUnits], 0); + } + + return fixture; +} + +/** + * Configure a Vault with only the AMO strategy for the Curve frxETH/OETH pool. + */ +async function convexFrxEthAmoFixture( + config = { + frxEthMintAmount: 0, + depositToStrategy: false, + poolAddFrxEthAmount: 0, + poolAddOethAmount: 0, + } +) { + const fixture = await oethDefaultFixture(); + + const { + convexFrxETHAMOStrategy, + oethHarvester, + crv, + frxETH, + oeth, + oethVault, + josh, + strategist, + timelock, + governor, + } = fixture; + + if (isFork) { + await setERC20TokenBalance(josh.address, frxETH, "10000000", hre); + await setERC20TokenBalance(josh.address, crv, "10000000", hre); + + // Convex pool that records the deposited balances + fixture.convexFrxEthOethRewardsPool = await ethers.getContractAt( + "IRewardStaking", + addresses.mainnet.CVXFrxETHRewardsPool + ); + + fixture.curveFrxEthOethPool = await ethers.getContractAt( + curveOethEthPoolAbi, + addresses.mainnet.CurveFrxETHOETHPool + ); + } else { + // Approve strategy for unit tests + await oethVault + .connect(governor) + .approveStrategy(convexFrxETHAMOStrategy.address); + await oethVault + .connect(governor) + .setAMOStrategy(convexFrxETHAMOStrategy.address, true); + + await convexFrxETHAMOStrategy + .connect(governor) + .setHarvesterAddress(oethHarvester.address); + await oethHarvester + .connect(governor) + .setSupportedStrategy(convexFrxETHAMOStrategy.address, true); + + const assetAddresses = await getAssetAddresses(deployments); + fixture.curveFrxEthOethPool = await ethers.getContractAt( + curveOethEthPoolAbi, + assetAddresses.CurveFrxEthOethPool + ); + } + + // Disable default strategy for frxETH + await oethVault + .connect(timelock) + .setAssetDefaultStrategy(frxETH.address, addresses.zero); + + // Update the strategy threshold to 500k ETH + await oethVault + .connect(timelock) + .setMintForStrategyThreshold( + convexFrxETHAMOStrategy.address, + parseUnits("500", 21) + ); + + // Impersonate the OETH Vault + fixture.oethVaultSigner = await impersonateAndFund(oethVault.address); + // Impersonate the Curve frxETH/OETH gauge that holds all the LP tokens + fixture.curveFrxETHOETHGaugeSigner = await impersonateAndFund( + addresses.mainnet.CurveFrxETHOETHGauge + ); + + // Set frxETH/ETH price above 0.998 so we can mint OETH using frxETH + await setFraxOraclePrice(parseUnits("0.999", 18)); + + // mint some OETH using frxETH if configured + if (config?.frxEthMintAmount > 0) { + const frxEthAmount = parseUnits(config.frxEthMintAmount.toString()); + await oethVault.connect(josh).rebase(); + await oethVault.connect(josh).allocate(); + + // Approve the Vault to transfer frxETH + await frxETH.connect(josh).approve(oethVault.address, frxEthAmount); + + // Mint OETH with frxETH + // This will sit in the vault, not the strategy + await oethVault.connect(josh).mint(frxETH.address, frxEthAmount, 0); + + // Add frxETH to the Curve Pool + if (config?.depositToStrategy) { + // The strategist deposits the frxETH to the AMO strategy + await oethVault + .connect(strategist) + .depositToStrategy( + convexFrxETHAMOStrategy.address, + [frxETH.address], + [frxEthAmount] + ); + } + } + + // Add frxETH to the Curve pool + if (config?.poolAddFrxEthAmount > 0) { + const frxEthAmount = parseUnits(config.poolAddFrxEthAmount.toString(), 18); + + // Josh approves the Curve pool + await frxETH + .connect(josh) + .approve(fixture.curveFrxEthOethPool.address, frxEthAmount); + log( + `Josh has ${formatUnits( + await frxETH.balanceOf(josh.address) + )} frxETH before adding ${formatUnits( + frxEthAmount + )} to the Curve frxETH/OETH pool` + ); + + // prettier-ignore + await fixture.curveFrxEthOethPool + .connect(josh)["add_liquidity(uint256[2],uint256)"]([frxEthAmount, 0], 0); + } + + const { oethWhaleAddress } = addresses.mainnet; + fixture.oethWhale = await impersonateAndFund(oethWhaleAddress); + + // Add OETH to the Curve pool + if (config?.poolAddOethAmount > 0) { + const poolAddOethAmountUnits = parseUnits( + config.poolAddOethAmount.toString() + ); + + const oethAmount = await oeth.balanceOf(oethWhaleAddress); + log(`OETH whale balance : ${formatUnits(oethAmount)}`); + log(`OETH to add to Curve pool: ${formatUnits(poolAddOethAmountUnits)}`); + expect(oethAmount).to.be.gte(poolAddOethAmountUnits); + await oeth + .connect(fixture.oethWhale) + .approve(fixture.curveFrxEthOethPool.address, poolAddOethAmountUnits); + + // prettier-ignore + await fixture.curveFrxEthOethPool .connect(fixture.oethWhale)["add_liquidity(uint256[2],uint256)"]([0, poolAddOethAmountUnits], 0); } @@ -1680,7 +1874,6 @@ async function convexFrxEthFixture( const fixture = await oethDefaultFixture(); const { - convexFrxEthWethStrategy, governor, oethHarvester, oethVault, @@ -1691,6 +1884,14 @@ async function convexFrxEthFixture( weth, } = fixture; + fixture.convexFrxEthWethStrategyProxy = await ethers.getContract( + "ConvexFrxEthWethStrategyProxy" + ); + fixture.convexFrxEthWethStrategy = await ethers.getContractAt( + "ConvexTwoPoolStrategy", + fixture.convexFrxEthWethStrategyProxy.address + ); + if (isFork) { await setERC20TokenBalance(josh.address, frxETH, "10000000", hre); await setERC20TokenBalance(josh.address, weth, "10000000", hre); @@ -1703,7 +1904,7 @@ async function convexFrxEthFixture( ); fixture.curveFrxEthWethPool = await ethers.getContractAt( - oethMetapoolAbi, + curveOethEthPoolAbi, addresses.mainnet.CurveFrxEthWethPool ); @@ -1713,17 +1914,17 @@ async function convexFrxEthFixture( // Approve strategy for unit tests await oethVault .connect(governor) - .approveStrategy(convexFrxEthWethStrategy.address); - await convexFrxEthWethStrategy + .approveStrategy(fixture.convexFrxEthWethStrategy.address); + await fixture.convexFrxEthWethStrategy .connect(governor) .setHarvesterAddress(oethHarvester.address); await oethHarvester .connect(governor) - .setSupportedStrategy(convexFrxEthWethStrategy.address, true); + .setSupportedStrategy(fixture.convexFrxEthWethStrategy.address, true); const assetAddresses = await getAssetAddresses(deployments); fixture.curveFrxEthWethPool = await ethers.getContractAt( - oethMetapoolAbi, + curveOethEthPoolAbi, assetAddresses.CurveFrxEthWethPool ); } @@ -1754,7 +1955,7 @@ async function convexFrxEthFixture( await oethVault .connect(strategist) .depositToStrategy( - convexFrxEthWethStrategy.address, + fixture.convexFrxEthWethStrategy.address, [weth.address], [wethAmount] ); @@ -1779,7 +1980,7 @@ async function convexFrxEthFixture( await oethVault .connect(strategist) .depositToStrategy( - convexFrxEthWethStrategy.address, + fixture.convexFrxEthWethStrategy.address, [frxETH.address], [frxEthAmount] ); @@ -2192,12 +2393,12 @@ async function harvesterFixture() { * Hardhat will reset the state of the network to what it was at the point after the fixture was initially executed. * The returned `loadFixture` function is typically inlcuded in the beforeEach(). * @example - * const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture); + * const loadFixture = createFixtureLoader(convexOethEthAmoFixture); * beforeEach(async () => { * fixture = await loadFixture(); * }); * @example - * const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + * const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { * wethMintAmount: 5000, * depositToStrategy: false, * }); @@ -2233,41 +2434,42 @@ mocha.after(async () => { }); module.exports = { - createFixtureLoader, - loadDefaultFixture, - resetAllowance, - defaultFixture, - oethDefaultFixture, - mockVaultFixture, + aaveVaultFixture, + balancerREthFixture, + balancerWstEthFixture, + tiltBalancerMetaStableWETHPool, + untiltBalancerMetaStableWETHPool, + buybackFixture, compoundFixture, compoundVaultFixture, - multiStrategyVaultFixture, - threepoolFixture, - threepoolVaultFixture, - convexVaultFixture, - convexMetaVaultFixture, - convexOETHMetaVaultFixture, + convexFrxEthAmoFixture, convexFrxEthFixture, convexGeneralizedMetaForkedFixture, convexLUSDMetaVaultFixture, + convexOethEthAmoFixture, + convexOusdAmoFixture, + convexVaultFixture, + createFixtureLoader, + defaultFixture, + fluxStrategyFixture, + fraxETHStrategyFixture, + hackedVaultFixture, + harvesterFixture, + loadDefaultFixture, makerDsrFixture, - morphoCompoundFixture, + mockVaultFixture, morphoAaveFixture, - aaveVaultFixture, - hackedVaultFixture, - rebornFixture, - balancerREthFixture, - balancerWstEthFixture, - tiltBalancerMetaStableWETHPool, - untiltBalancerMetaStableWETHPool, - fraxETHStrategyFixture, - oethMorphoAaveFixture, + morphoCompoundFixture, + multiStrategyVaultFixture, oeth1InchSwapperFixture, oethCollateralSwapFixture, + oethDefaultFixture, + oethMorphoAaveFixture, ousdCollateralSwapFixture, - fluxStrategyFixture, - buybackFixture, - harvesterFixture, nodeSnapshot, nodeRevert, + rebornFixture, + resetAllowance, + threepoolFixture, + threepoolVaultFixture, }; diff --git a/contracts/test/_metastrategies-fixtures.js b/contracts/test/fixture/_metastrategies-fixtures.js similarity index 83% rename from contracts/test/_metastrategies-fixtures.js rename to contracts/test/fixture/_metastrategies-fixtures.js index 2f029044b9..75a389101d 100644 --- a/contracts/test/_metastrategies-fixtures.js +++ b/contracts/test/fixture/_metastrategies-fixtures.js @@ -2,36 +2,37 @@ const hre = require("hardhat"); const { ethers } = hre; const { formatUnits } = require("ethers/lib/utils"); -const { ousdUnits, units } = require("./helpers"); -const { convexMetaVaultFixture, resetAllowance } = require("./_fixture"); -const addresses = require("../utils/addresses"); -const erc20Abi = require("./abi/erc20.json"); -const { impersonateAndFund } = require("../utils/signers"); -const { setERC20TokenBalance } = require("./_fund"); +const { ousdUnits, units } = require("../helpers"); +const { convexOusdAmoFixture, resetAllowance } = require("./_fixture"); +const addresses = require("../../utils/addresses"); +const { impersonateAndFund } = require("../../utils/signers"); +const { setERC20TokenBalance } = require("../_fund"); -const log = require("../utils/logger")("test:fixtures:strategies:meta"); +const erc20Abi = require("../abi/erc20.json"); + +const log = require("../../utils/logger")("test:fixtures:strategies:meta"); // NOTE: This can cause a change in setup from mainnet. // However, mint/redeem tests, without any changes, are tested // in vault.fork-test.js, so this should be fine. async function withDefaultOUSDMetapoolStrategiesSet() { - const fixture = await convexMetaVaultFixture(); + const fixture = await convexOusdAmoFixture(); - const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy, daniel } = + const { vault, timelock, dai, usdt, usdc, convexOusdAMOStrategy, daniel } = fixture; await vault .connect(timelock) - .setAssetDefaultStrategy(dai.address, OUSDmetaStrategy.address); + .setAssetDefaultStrategy(dai.address, convexOusdAMOStrategy.address); await vault .connect(timelock) - .setAssetDefaultStrategy(usdt.address, OUSDmetaStrategy.address); + .setAssetDefaultStrategy(usdt.address, convexOusdAMOStrategy.address); await vault .connect(timelock) - .setAssetDefaultStrategy(usdc.address, OUSDmetaStrategy.address); + .setAssetDefaultStrategy(usdc.address, convexOusdAMOStrategy.address); fixture.cvxRewardPool = await ethers.getContractAt( "IRewardStaking", @@ -58,10 +59,10 @@ async function withBalancedOUSDMetaPool() { } async function balanceOUSDMetaPool(fixture) { - const { ousdMetaPool } = fixture; + const { curveOusd3CrvMetapool } = fixture; log(`Metapool balances before being balanced`); - const balancesBefore = await fixture.ousdMetaPool.get_balances(); + const balancesBefore = await fixture.curveOusd3CrvMetapool.get_balances(); const coinOne3CrvValueBefore = await get3CRVLiquidity( fixture, balancesBefore[0] @@ -73,10 +74,10 @@ async function balanceOUSDMetaPool(fixture) { ); log(`Metapool balance 1: ${formatUnits(balancesBefore[1])} 3CRV`); - await _balanceMetaPool(fixture, ousdMetaPool); + await _balanceMetaPool(fixture, curveOusd3CrvMetapool); log(`Metapool balances after being balanced`); - const balancesAfter = await fixture.ousdMetaPool.get_balances(); + const balancesAfter = await fixture.curveOusd3CrvMetapool.get_balances(); const coinOne3CrvValueAfter = await get3CRVLiquidity( fixture, balancesAfter[0] @@ -151,9 +152,9 @@ async function withCRV3TitledOUSDMetapool() { } async function tiltTo3CRV_OUSDMetapool(fixture, amount) { - const { ousdMetaPool } = fixture; + const { curveOusd3CrvMetapool } = fixture; - await tiltTo3CRV_Metapool(fixture, ousdMetaPool, amount); + await tiltTo3CRV_Metapool(fixture, curveOusd3CrvMetapool, amount); } /* Tilt towards 3CRV by checking liquidity @@ -217,7 +218,7 @@ async function tiltToMainToken(fixture) { } async function tiltTo3CRV_Metapool(fixture, metapool, amount) { - const { vault, domen, ousdMetaPool } = fixture; + const { vault, domen, curveOusd3CrvMetapool } = fixture; // Balance metapool await _balanceMetaPool(fixture, metapool); @@ -226,7 +227,9 @@ async function tiltTo3CRV_Metapool(fixture, metapool, amount) { // Tilt to 3CRV by a million const exchangeSign = "exchange(int128,int128,uint256,uint256)"; // make metapool make exchange on itself. It should always have enough OUSD/3crv to do this - const metapoolSigner = await impersonateAndFund(ousdMetaPool.address); + const metapoolSigner = await impersonateAndFund( + curveOusd3CrvMetapool.address + ); await metapool.connect(metapoolSigner)[exchangeSign](1, 0, amount.div(2), 0); @@ -243,7 +246,7 @@ async function withOUSDTitledMetapool() { } async function tiltToOUSD_OUSDMetapool(fixture, amount) { - const { vault, domen, ousdMetaPool } = fixture; + const { vault, domen, curveOusd3CrvMetapool } = fixture; // Balance metapool await balanceOUSDMetaPool(fixture); @@ -253,9 +256,11 @@ async function tiltToOUSD_OUSDMetapool(fixture, amount) { // Tilt to 3CRV by a million const exchangeSign = "exchange(int128,int128,uint256,uint256)"; // make metapool make exchange on itself. It should always have enough OUSD/3crv to do this - const metapoolSigner = await impersonateAndFund(ousdMetaPool.address); + const metapoolSigner = await impersonateAndFund( + curveOusd3CrvMetapool.address + ); - await ousdMetaPool + await curveOusd3CrvMetapool .connect(metapoolSigner) // eslint-disable-next-line [exchangeSign](0, 1, amount.div(2), 0); @@ -266,8 +271,8 @@ async function tiltToOUSD_OUSDMetapool(fixture, amount) { // Convert 3CRV value to OUSD/3CRV Metapool LP tokens async function getOUSDLiquidity(fixture, crv3Amount) { - const { ousdMetaPool } = fixture; - return _getCoinLiquidity(ousdMetaPool, crv3Amount); + const { curveOusd3CrvMetapool } = fixture; + return _getCoinLiquidity(curveOusd3CrvMetapool, crv3Amount); } // Convert USD value to 3Pool LP tokens @@ -284,7 +289,7 @@ async function _getCoinLiquidity(pool, value) { } module.exports = { - convexMetaVaultFixture, + convexOusdAmoFixture, withDefaultOUSDMetapoolStrategiesSet, withBalancedOUSDMetaPool, diff --git a/contracts/test/flipper/flipper.js b/contracts/test/flipper/flipper.js index 660a47e8fb..82efefecbc 100644 --- a/contracts/test/flipper/flipper.js +++ b/contracts/test/flipper/flipper.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { createFixtureLoader, defaultFixture } = require("../_fixture"); +const { createFixtureLoader, defaultFixture } = require("../fixture/_fixture"); const { daiUnits, ousdUnits, diff --git a/contracts/test/governor.js b/contracts/test/governor.js index 3208cb8a4d..8ef927e7eb 100644 --- a/contracts/test/governor.js +++ b/contracts/test/governor.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("./_fixture"); +const { loadDefaultFixture } = require("./fixture/_fixture"); const { propose, proposeAndExecute, advanceTime } = require("./helpers"); const { proposeArgs } = require("../utils/governor"); diff --git a/contracts/test/hacks/reborn.js b/contracts/test/hacks/reborn.js index 3e8f51283f..6cee632888 100644 --- a/contracts/test/hacks/reborn.js +++ b/contracts/test/hacks/reborn.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { createFixtureLoader, rebornFixture } = require("../_fixture"); +const { createFixtureLoader, rebornFixture } = require("../fixture/_fixture"); const { isFork, daiUnits, ousdUnits } = require("../helpers"); describe("Reborn Attack Protection", function () { diff --git a/contracts/test/hacks/reentrant.js b/contracts/test/hacks/reentrant.js index 6892d52886..b70b7d7a1f 100644 --- a/contracts/test/hacks/reentrant.js +++ b/contracts/test/hacks/reentrant.js @@ -1,6 +1,9 @@ const { expect } = require("chai"); -const { createFixtureLoader, hackedVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + hackedVaultFixture, +} = require("../fixture/_fixture"); const { isFork } = require("../helpers"); describe("Reentry Attack Protection", function () { diff --git a/contracts/test/harvest/ousd-harvest-crv.fork-test.js b/contracts/test/harvest/ousd-harvest-crv.fork-test.js index f79dbd1f72..68b19bac5e 100644 --- a/contracts/test/harvest/ousd-harvest-crv.fork-test.js +++ b/contracts/test/harvest/ousd-harvest-crv.fork-test.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const { parseUnits } = require("ethers").utils; const { usdtUnits } = require("../helpers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { MAX_UINT256 } = require("../../utils/constants"); describe("ForkTest: Harvest OUSD", function () { @@ -20,13 +20,13 @@ describe("ForkTest: Harvest OUSD", function () { expect(crvTokenConfig.liquidationLimit).to.be.eq(parseUnits("4000", 18)); }); it("should harvest", async function () { - const { crv, timelock, harvester, OUSDmetaStrategy } = fixture; + const { crv, timelock, harvester, convexOusdAMOStrategy } = fixture; const balanceBeforeHarvest = await crv.balanceOf(harvester.address); // prettier-ignore await harvester - .connect(timelock)["harvest(address)"](OUSDmetaStrategy.address); + .connect(timelock)["harvest(address)"](convexOusdAMOStrategy.address); const balanceAfterHarvest = await crv.balanceOf(harvester.address); @@ -34,13 +34,13 @@ describe("ForkTest: Harvest OUSD", function () { expect(crvHarvested).to.be.gt(parseUnits("20000", 18)); }); it("should harvest and swap", async function () { - const { anna, OUSDmetaStrategy, dripper, harvester, usdt } = fixture; + const { anna, convexOusdAMOStrategy, dripper, harvester, usdt } = fixture; const usdtBalanceBeforeDripper = await usdt.balanceOf(dripper.address); // prettier-ignore await harvester - .connect(anna)["harvestAndSwap(address)"](OUSDmetaStrategy.address); + .connect(anna)["harvestAndSwap(address)"](convexOusdAMOStrategy.address); const usdtBalanceAfterDripper = await usdt.balanceOf(dripper.address); const usdtSwapped = usdtBalanceAfterDripper.sub(usdtBalanceBeforeDripper); @@ -70,12 +70,12 @@ describe("ForkTest: Harvest OUSD", function () { * - depth of the SushiSwap pool is not deep enough to handle the swap without * hitting the slippage limit. */ - it("should not harvest and swap", async function () { - const { anna, OUSDmetaStrategy, harvester } = fixture; + it.skip("should not harvest and swap", async function () { + const { anna, convexOusdAMOStrategy, harvester } = fixture; // prettier-ignore const tx = harvester - .connect(anna)["harvestAndSwap(address)"](OUSDmetaStrategy.address); + .connect(anna)["harvestAndSwap(address)"](convexOusdAMOStrategy.address); await expect(tx).to.be.revertedWith( "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT" ); @@ -103,15 +103,15 @@ describe("ForkTest: Harvest OUSD", function () { * If strategy doesn't have enough CRV not nearly enough rewards are going to be * harvested for the test to pass. */ - it("should harvest and swap", async function () { - const { crv, OUSDmetaStrategy, dripper, harvester, timelock, usdt } = + it.skip("should harvest and swap", async function () { + const { crv, convexOusdAMOStrategy, dripper, harvester, timelock, usdt } = fixture; const balanceBeforeDripper = await usdt.balanceOf(dripper.address); // prettier-ignore await harvester - .connect(timelock)["harvest(address)"](OUSDmetaStrategy.address); + .connect(timelock)["harvest(address)"](convexOusdAMOStrategy.address); await harvester.connect(timelock).swapRewardToken(crv.address); const balanceAfterDripper = await usdt.balanceOf(dripper.address); diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index f455815fe1..07a675294d 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -411,6 +411,7 @@ const getAssetAddresses = async (deployments) => { STKAAVE: addresses.mainnet.STKAAVE, OGN: addresses.mainnet.OGN, OGV: addresses.mainnet.OGV, + LUSD: addresses.mainnet.LUSD, RewardsSource: addresses.mainnet.RewardsSource, RETH: addresses.mainnet.rETH, frxETH: addresses.mainnet.frxETH, @@ -454,6 +455,7 @@ const getAssetAddresses = async (deployments) => { STKAAVE: (await deployments.get("MockStkAave")).address, OGN: (await deployments.get("MockOGN")).address, OGV: (await deployments.get("MockOGV")).address, + LUSD: (await ethers.getContract("MockLUSD")).address, RETH: (await deployments.get("MockRETH")).address, stETH: (await deployments.get("MockstETH")).address, frxETH: (await deployments.get("MockfrxETH")).address, @@ -504,6 +506,28 @@ const getAssetAddresses = async (deployments) => { // do nothing } + try { + /* This pool gets deployed in 001_core instead of 000_mocks as it contains OETH. + * Just return without the Curve pool info if it is not yet available. + */ + addressMap.CurveOethEthPool = ( + await deployments.get("MockCurveOethEthPool") + ).address; + } catch (e) { + // do nothing + } + + try { + /* This pool gets deployed in 001_core instead of 000_mocks as it contains OETH. + * Just return without the Curve pool info if it is not yet available. + */ + addressMap.CurveFrxEthOethPool = ( + await deployments.get("MockCurveFrxEthOethPool") + ).address; + } catch (e) { + // do nothing + } + return addressMap; } }; diff --git a/contracts/test/liquidity/liquidity-reward.js b/contracts/test/liquidity/liquidity-reward.js index 78a80aca2c..c804b7100a 100644 --- a/contracts/test/liquidity/liquidity-reward.js +++ b/contracts/test/liquidity/liquidity-reward.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { advanceBlocks, isFork } = require("../helpers"); describe("Liquidity Reward", function () { diff --git a/contracts/test/oracle/aura-feed.fork-test.js b/contracts/test/oracle/aura-feed.fork-test.js index af0141ca76..23737564bf 100644 --- a/contracts/test/oracle/aura-feed.fork-test.js +++ b/contracts/test/oracle/aura-feed.fork-test.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { oethUnits } = require("../helpers"); const addresses = require("../../utils/addresses"); const { hotDeployOption } = require("../_hot-deploy"); diff --git a/contracts/test/oracle/aura-feed.js b/contracts/test/oracle/aura-feed.js index 8ab2d0949a..433e548827 100644 --- a/contracts/test/oracle/aura-feed.js +++ b/contracts/test/oracle/aura-feed.js @@ -1,8 +1,8 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { oethUnits } = require("../helpers"); -describe("ForkTest: Aura/WETH Price Feed", function () { +describe("Aura/WETH Price Feed", function () { this.timeout(0); let fixture; diff --git a/contracts/test/oracle/oracle.fork-test.js b/contracts/test/oracle/oracle.fork-test.js index 1da7bb1741..c407cf388f 100644 --- a/contracts/test/oracle/oracle.fork-test.js +++ b/contracts/test/oracle/oracle.fork-test.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { parseUnits } = require("ethers/lib/utils"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { isCI } = require("../helpers"); describe("ForkTest: OETH Oracle Routers", function () { diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js index 943f5b3723..bbcf3bf051 100644 --- a/contracts/test/oracle/oracle.js +++ b/contracts/test/oracle/oracle.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, setOracleTokenPriceUsd } = require("../helpers"); /* diff --git a/contracts/test/staking/airdrop-staking.js b/contracts/test/staking/airdrop-staking.js index 16b6ade48c..01fafe8f27 100644 --- a/contracts/test/staking/airdrop-staking.js +++ b/contracts/test/staking/airdrop-staking.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { BigNumber } = require("ethers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { advanceTime, isFork } = require("../helpers"); const day = 24 * 60 * 60; diff --git a/contracts/test/staking/single-asset-staking.js b/contracts/test/staking/single-asset-staking.js index 385deed9c8..b067457ee7 100644 --- a/contracts/test/staking/single-asset-staking.js +++ b/contracts/test/staking/single-asset-staking.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ognUnits, advanceTime, isFork } = require("../helpers"); const day = 24 * 60 * 60; diff --git a/contracts/test/strategies/3pool-standalone.js b/contracts/test/strategies/3pool-standalone.js index 4603d24b8c..45523fc2d6 100644 --- a/contracts/test/strategies/3pool-standalone.js +++ b/contracts/test/strategies/3pool-standalone.js @@ -2,7 +2,10 @@ const { expect } = require("chai"); const { utils } = require("ethers"); const { BigNumber } = require("ethers"); -const { createFixtureLoader, threepoolFixture } = require("../_fixture"); +const { + createFixtureLoader, + threepoolFixture, +} = require("../fixture/_fixture"); const { units } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); diff --git a/contracts/test/strategies/3pool.js b/contracts/test/strategies/3pool.js index 244eaa7ad6..d62cccdd74 100644 --- a/contracts/test/strategies/3pool.js +++ b/contracts/test/strategies/3pool.js @@ -1,6 +1,9 @@ const { expect } = require("chai"); -const { createFixtureLoader, threepoolVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + threepoolVaultFixture, +} = require("../fixture/_fixture"); const { ousdUnits, units, expectApproxSupply, isFork } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); diff --git a/contracts/test/strategies/aave.js b/contracts/test/strategies/aave.js index 7eed9728cd..3fe799356b 100644 --- a/contracts/test/strategies/aave.js +++ b/contracts/test/strategies/aave.js @@ -1,7 +1,10 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { createFixtureLoader, aaveVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + aaveVaultFixture, +} = require("../fixture/_fixture"); const { ousdUnits, units, diff --git a/contracts/test/strategies/balancerMetaStablePool.fork-test.js b/contracts/test/strategies/balancerMetaStablePool.fork-test.js index 92defeeee9..728834e2a3 100644 --- a/contracts/test/strategies/balancerMetaStablePool.fork-test.js +++ b/contracts/test/strategies/balancerMetaStablePool.fork-test.js @@ -13,7 +13,7 @@ const { createFixtureLoader, tiltBalancerMetaStableWETHPool, untiltBalancerMetaStableWETHPool, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const temporaryFork = require("../../utils/temporaryFork"); const { impersonateAndFund } = require("../../utils/signers"); diff --git a/contracts/test/strategies/balancerPoolReentrancy.fork-test.js b/contracts/test/strategies/balancerPoolReentrancy.fork-test.js index ae78f84051..f9beb4a638 100644 --- a/contracts/test/strategies/balancerPoolReentrancy.fork-test.js +++ b/contracts/test/strategies/balancerPoolReentrancy.fork-test.js @@ -3,9 +3,12 @@ const { ethers } = hre; const { expect } = require("chai"); const { isCI } = require("../helpers"); -const { balancerREthFixture, createFixtureLoader } = require("../_fixture"); -const { deployWithConfirmation } = require("../../utils/deploy"); +const { + balancerREthFixture, + createFixtureLoader, +} = require("../fixture/_fixture"); const addresses = require("../../utils/addresses"); +const { deployWithConfirmation } = require("../../utils/deploy"); const { setERC20TokenBalance } = require("../_fund"); describe("ForkTest: Balancer MetaStablePool - Read-only Reentrancy", function () { diff --git a/contracts/test/strategies/compound.js b/contracts/test/strategies/compound.js index e3621278ce..8e2740f6d0 100644 --- a/contracts/test/strategies/compound.js +++ b/contracts/test/strategies/compound.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { impersonateAndFund } = require("../../utils/signers"); -const { createFixtureLoader, compoundFixture } = require("../_fixture"); +const { createFixtureLoader, compoundFixture } = require("../fixture/_fixture"); const { usdcUnits, isFork } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); diff --git a/contracts/test/strategies/convex-frxeth.fork-test.js b/contracts/test/strategies/convex-frxeth.fork-test.js index 4cf81e8e58..8f1f324627 100644 --- a/contracts/test/strategies/convex-frxeth.fork-test.js +++ b/contracts/test/strategies/convex-frxeth.fork-test.js @@ -6,15 +6,18 @@ const { createFixtureLoader, convexFrxEthFixture, loadDefaultFixture, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const addresses = require("../../utils/addresses"); const { resolveAsset } = require("../../utils/assets"); -const { frxEthWethPoolLpPID, MAX_UINT256 } = require("../../utils/constants"); +const { + convex_frxETH_WETH_PID, + MAX_UINT256, +} = require("../../utils/constants"); const { impersonateAndFund } = require("../../utils/signers.js"); const log = require("../../utils/logger")("test:fork:convex:frxETH/WETH"); -describe("ForkTest: Convex frxETH/WETH Strategy", function () { +describe.skip("ForkTest: Convex frxETH/WETH Strategy", function () { this.timeout(0); // Retry up to 3 times on CI this.retries(isCI ? 3 : 0); @@ -74,7 +77,7 @@ describe("ForkTest: Convex frxETH/WETH Strategy", function () { addresses.mainnet.ConvexFrxEthWethRewardsPool ); expect(await convexFrxEthWethStrategy.cvxDepositorPoolId()).to.equal( - frxEthWethPoolLpPID + convex_frxETH_WETH_PID ); // Storage slots diff --git a/contracts/test/strategies/convex.js b/contracts/test/strategies/convex.js index 0754f6f7b2..c6de2231ae 100644 --- a/contracts/test/strategies/convex.js +++ b/contracts/test/strategies/convex.js @@ -1,6 +1,9 @@ const { expect } = require("chai"); -const { createFixtureLoader, convexVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + convexVaultFixture, +} = require("../fixture/_fixture"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); diff --git a/contracts/test/strategies/convexFrxEthWeth.js b/contracts/test/strategies/convexFrxEthWeth.js index 3553ab897e..870d59fb46 100644 --- a/contracts/test/strategies/convexFrxEthWeth.js +++ b/contracts/test/strategies/convexFrxEthWeth.js @@ -1,6 +1,9 @@ const { expect } = require("chai"); -const { createFixtureLoader, convexFrxEthFixture } = require("../_fixture"); +const { + createFixtureLoader, + convexFrxEthFixture, +} = require("../fixture/_fixture"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); diff --git a/contracts/test/strategies/dripper.js b/contracts/test/strategies/dripper.js index 82bbf72fe9..9f21122b40 100644 --- a/contracts/test/strategies/dripper.js +++ b/contracts/test/strategies/dripper.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { usdtUnits, advanceTime } = require("../helpers"); diff --git a/contracts/test/strategies/flux.fork-test.js b/contracts/test/strategies/flux.fork-test.js index 551fbf755e..451a7e69e2 100644 --- a/contracts/test/strategies/flux.fork-test.js +++ b/contracts/test/strategies/flux.fork-test.js @@ -1,7 +1,10 @@ const { expect } = require("chai"); const addresses = require("../../utils/addresses"); -const { fluxStrategyFixture, createFixtureLoader } = require("../_fixture"); +const { + fluxStrategyFixture, + createFixtureLoader, +} = require("../fixture/_fixture"); const { units, ousdUnits, isCI } = require("../helpers"); const { impersonateAndFund } = require("../../utils/signers"); diff --git a/contracts/test/strategies/fraxeth.fork-test.js b/contracts/test/strategies/fraxeth.fork-test.js index 1d6aa93114..a590c4fb66 100644 --- a/contracts/test/strategies/fraxeth.fork-test.js +++ b/contracts/test/strategies/fraxeth.fork-test.js @@ -6,7 +6,7 @@ const { units, oethUnits, isCI } = require("../helpers"); const { createFixtureLoader, fraxETHStrategyFixture, -} = require("./../_fixture"); +} = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); const { setERC20TokenBalance } = require("../_fund"); diff --git a/contracts/test/strategies/fraxeth.js b/contracts/test/strategies/fraxeth.js index 796d826151..33d626e1ce 100644 --- a/contracts/test/strategies/fraxeth.js +++ b/contracts/test/strategies/fraxeth.js @@ -1,15 +1,14 @@ const { expect } = require("chai"); +const { BigNumber } = require("ethers"); const { oethUnits, units } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); - const { createFixtureLoader, fraxETHStrategyFixture, -} = require("./../_fixture"); -const { BigNumber } = require("ethers"); +} = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); describe("FraxETH Strategy", function () { diff --git a/contracts/test/strategies/generalized-meta.fork-test.js b/contracts/test/strategies/generalized-meta.fork-test.js index 4680a40cea..19953b5440 100644 --- a/contracts/test/strategies/generalized-meta.fork-test.js +++ b/contracts/test/strategies/generalized-meta.fork-test.js @@ -6,11 +6,11 @@ const { units, ousdUnits, isCI } = require("../helpers"); const { createFixtureLoader, convexGeneralizedMetaForkedFixture, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const { tiltToMainToken, tiltTo3CRV_Metapool_automatic, -} = require("../_metastrategies-fixtures"); +} = require("../fixture/_metastrategies-fixtures"); const { impersonateAndFund } = require("../../utils/signers"); const metastrategies = [ diff --git a/contracts/test/strategies/generalized-meta.js b/contracts/test/strategies/generalized-meta.js index bff8a376a1..b40ed2614d 100644 --- a/contracts/test/strategies/generalized-meta.js +++ b/contracts/test/strategies/generalized-meta.js @@ -3,7 +3,7 @@ const { expect } = require("chai"); const { createFixtureLoader, convexLUSDMetaVaultFixture, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const { daiUnits, ousdUnits, diff --git a/contracts/test/strategies/meta.js b/contracts/test/strategies/meta.js deleted file mode 100644 index 3ae850e44b..0000000000 --- a/contracts/test/strategies/meta.js +++ /dev/null @@ -1,126 +0,0 @@ -const { expect } = require("chai"); -const { BigNumber } = require("ethers"); - -const { convexMetaVaultFixture, createFixtureLoader } = require("../_fixture"); -const { ousdUnits, units, expectApproxSupply, isFork } = require("../helpers"); -const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); -const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); - -describe("OUSD AMO strategy using Curve OUSD/3CRV pool", function () { - if (isFork) { - this.timeout(0); - } - - const loadFixture = createFixtureLoader(convexMetaVaultFixture); - let fixture; - beforeEach(async function () { - fixture = await loadFixture(); - }); - - shouldBehaveLikeGovernable(() => ({ - ...fixture, - strategy: fixture.OUSDmetaStrategy, - })); - - shouldBehaveLikeHarvestable(() => ({ - ...fixture, - strategy: fixture.OUSDmetaStrategy, - })); - - const mint = async (amount, asset) => { - const { anna, vault } = fixture; - await asset.connect(anna).mint(await units(amount, asset)); - await asset - .connect(anna) - .approve(vault.address, await units(amount, asset)); - return await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - }; - - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { anna, cvxBooster, ousd, metapoolToken, usdt } = fixture; - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("30000.00", usdt); - await expectApproxSupply(ousd, ousdUnits("60200")); - await expect(anna).to.have.a.balanceOf("30000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("60000", metapoolToken); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { anna, cvxBooster, ousd, metapoolToken, usdc } = fixture; - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("50000.00", usdc); - await expectApproxSupply(ousd, ousdUnits("100200")); - await expect(anna).to.have.a.balanceOf("50000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("100000", metapoolToken); - }); - - it("Should use a minimum LP token amount when depositing USDT into metapool", async function () { - const { usdt } = fixture; - await expect(mint("29000", usdt)).to.be.revertedWith( - "Slippage ruined your day" - ); - }); - - it("Should use a minimum LP token amount when depositing USDC into metapool", async function () { - const { usdc } = fixture; - await expect(mint("29000", usdc)).to.be.revertedWith( - "Slippage ruined your day" - ); - }); - }); - - describe("Redeem", function () { - it("Should be able to unstake from gauge and return USDT", async function () { - const { dai, usdc, usdt, ousd, anna, vault } = fixture; - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("10000.00", dai); - await mint("10000.00", usdc); - await mint("10000.00", usdt); - await vault.connect(anna).redeem(ousdUnits("20000"), 0); - // Dai minted OUSD has not been deployed to Metastrategy for that reason the - // total supply of OUSD has not doubled - await expectApproxSupply(ousd, ousdUnits("10200")); - }); - }); - - describe("AMO", function () { - it("Should not allow too large mintForStrategy", async () => { - const { vault, governor, anna } = fixture; - const MAX_UINT = BigNumber.from( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ); - - await vault.connect(governor).setOusdMetaStrategy(anna.address); - - await expect( - vault.connect(anna).mintForStrategy(MAX_UINT) - ).to.be.revertedWith("Amount too high"); - - await expect( - vault.connect(anna).mintForStrategy(MAX_UINT.div(2).sub(1)) - ).to.be.revertedWith( - "Minted ousd surpassed netOusdMintForStrategyThreshold." - ); - }); - - it("Should not allow too large burnForStrategy", async () => { - const { vault, governor, anna } = fixture; - const MAX_UINT = BigNumber.from( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ); - - await vault.connect(governor).setOusdMetaStrategy(anna.address); - - await expect( - vault.connect(anna).burnForStrategy(MAX_UINT) - ).to.be.revertedWith("Amount too high"); - - await expect( - vault.connect(anna).burnForStrategy(MAX_UINT.div(2).sub(1)) - ).to.be.revertedWith("Attempting to burn too much OUSD."); - }); - }); -}); diff --git a/contracts/test/strategies/morpho-aave.fork-test.js b/contracts/test/strategies/morpho-aave.fork-test.js index e6d1e25a91..8d4dba140b 100644 --- a/contracts/test/strategies/morpho-aave.fork-test.js +++ b/contracts/test/strategies/morpho-aave.fork-test.js @@ -7,7 +7,10 @@ const { advanceTime, isCI, } = require("../helpers"); -const { createFixtureLoader, morphoAaveFixture } = require("../_fixture"); +const { + createFixtureLoader, + morphoAaveFixture, +} = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); describe("ForkTest: Morpho Aave Strategy", function () { diff --git a/contracts/test/strategies/morpho-comp.fork-test.js b/contracts/test/strategies/morpho-comp.fork-test.js index 11cfc122f5..473c87f62d 100644 --- a/contracts/test/strategies/morpho-comp.fork-test.js +++ b/contracts/test/strategies/morpho-comp.fork-test.js @@ -8,7 +8,10 @@ const { advanceTime, isCI, } = require("../helpers"); -const { createFixtureLoader, morphoCompoundFixture } = require("../_fixture"); +const { + createFixtureLoader, + morphoCompoundFixture, +} = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); describe("ForkTest: Morpho Compound Strategy", function () { diff --git a/contracts/test/strategies/oeth-metapool.fork-test.js b/contracts/test/strategies/oeth-amo-curve-eth.fork-test.js similarity index 72% rename from contracts/test/strategies/oeth-metapool.fork-test.js rename to contracts/test/strategies/oeth-amo-curve-eth.fork-test.js index 1c8bfa0294..911da8980b 100644 --- a/contracts/test/strategies/oeth-metapool.fork-test.js +++ b/contracts/test/strategies/oeth-amo-curve-eth.fork-test.js @@ -3,16 +3,16 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); const { run } = require("hardhat"); const addresses = require("../../utils/addresses"); -const { oethPoolLpPID } = require("../../utils/constants"); +const { convex_OETH_ETH_PID } = require("../../utils/constants"); const { units, oethUnits, isCI } = require("../helpers"); const { createFixtureLoader, - convexOETHMetaVaultFixture, -} = require("../_fixture"); + convexOethEthAmoFixture, +} = require("../fixture/_fixture"); -const log = require("../../utils/logger")("test:fork:oeth:metapool"); +const log = require("../../utils/logger")("test:fork:oeth:amo:curve"); -describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { +describe("ForkTest: OETH AMO Curve Strategy", function () { this.timeout(0); // Retry up to 3 times on CI this.retries(isCI ? 3 : 0); @@ -20,7 +20,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { let fixture; describe("with mainnet data", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture); + const loadFixture = createFixtureLoader(convexOethEthAmoFixture); beforeEach(async () => { fixture = await loadFixture(); }); @@ -30,8 +30,6 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { expect(await convexEthMetaStrategy.MAX_SLIPPAGE()).to.equal( parseUnits("0.01", 18) ); - expect(await convexEthMetaStrategy.ETH_ADDRESS()).to.equal(addresses.ETH); - expect(await convexEthMetaStrategy.cvxDepositorAddress()).to.equal( addresses.mainnet.CVXBooster ); @@ -39,7 +37,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { addresses.mainnet.CVXETHRewardsPool ); expect(await convexEthMetaStrategy.cvxDepositorPTokenId()).to.equal( - oethPoolLpPID + convex_OETH_ETH_PID ); expect(await convexEthMetaStrategy.curvePool()).to.equal( addresses.mainnet.CurveOETHMetaPool @@ -54,6 +52,17 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { addresses.mainnet.WETH ); }); + it("Should have AMO strategy configured in the vault", async () => { + const { oethVault } = fixture; + + const config = await oethVault.getStrategyConfig( + addresses.mainnet.ConvexOETHAMOStrategy + ); + expect(config.isSupported).to.be.true; + expect(config.isAMO).to.be.true; + expect(config.mintForStrategy).to.eq(0); + expect(config.mintForStrategyThreshold).to.be.eq(parseUnits("500", 21)); + }); it("Should be able to check balance", async () => { const { weth, josh, convexEthMetaStrategy } = fixture; @@ -94,7 +103,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { ); await oethVault.connect(josh).rebase(); - await expect(wethDiff).to.be.gte(parseUnits("0.2")); + await expect(wethDiff).to.be.gte(parseUnits("0.15")); }); it("Only Governor can approve all tokens", async () => { const { @@ -105,7 +114,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { convexEthMetaStrategy, weth, oeth, - oethMetaPool, + curveOethEthPool, } = fixture; // Governor can approve all tokens @@ -114,7 +123,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { .safeApproveAllTokens(); await expect(tx).to.not.emit(weth, "Approval"); await expect(tx).to.emit(oeth, "Approval"); - await expect(tx).to.emit(oethMetaPool, "Approval"); + await expect(tx).to.emit(curveOethEthPool, "Approval"); for (const signer of [strategist, josh, oethVaultSigner]) { const tx = convexEthMetaStrategy.connect(signer).safeApproveAllTokens(); @@ -124,7 +133,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); describe("with some WETH in the vault", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: false, balancePool: true, @@ -136,7 +145,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const { convexEthMetaStrategy, oeth, - oethMetaPool, + curveOethEthPool, oethVaultSigner, weth, } = fixture; @@ -158,9 +167,9 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { output: false, }); + // prettier-ignore const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .deposit(weth.address, wethDepositAmount); + .connect(oethVaultSigner)["deposit(address,uint256)"](weth.address, wethDepositAmount); const receipt = await tx.wait(); @@ -174,13 +183,13 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { // Check emitted events await expect(tx) .to.emit(convexEthMetaStrategy, "Deposit") - .withArgs(weth.address, oethMetaPool.address, wethDepositAmount); + .withArgs(weth.address, curveOethEthPool.address, wethDepositAmount); await expect(tx) .to.emit(convexEthMetaStrategy, "Deposit") - .withArgs(oeth.address, oethMetaPool.address, oethMintAmount); + .withArgs(oeth.address, curveOethEthPool.address, oethMintAmount); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].add(wethDepositAmount), 0.01 // 0.01% or 1 basis point @@ -213,9 +222,9 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { .transfer(convexEthMetaStrategy.address, depositAmount); for (const signer of [strategist, timelock, josh]) { + // prettier-ignore const tx = convexEthMetaStrategy - .connect(signer) - .deposit(weth.address, depositAmount); + .connect(signer)["deposit(address,uint256)"](weth.address, depositAmount); await expect(tx).to.revertedWith("Caller is not the Vault"); } @@ -223,7 +232,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { it("Only vault can deposit all WETH to AMO strategy", async function () { const { convexEthMetaStrategy, - oethMetaPool, + curveOethEthPool, oethVaultSigner, strategist, timelock, @@ -245,14 +254,15 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const tx = await convexEthMetaStrategy .connect(oethVaultSigner) .depositAll(); - await expect(tx) - .to.emit(convexEthMetaStrategy, "Deposit") - .withNamedArgs({ _asset: weth.address, _pToken: oethMetaPool.address }); + await expect(tx).to.emit(convexEthMetaStrategy, "Deposit").withNamedArgs({ + _asset: weth.address, + _pToken: curveOethEthPool.address, + }); }); }); - describe("with the strategy having some OETH and ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + describe("with the strategy having some OETH and ETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: true, balancePool: true, @@ -263,7 +273,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { it("Vault should be able to withdraw all", async () => { const { convexEthMetaStrategy, - oethMetaPool, + curveOethEthPool, oeth, oethVaultSigner, weth, @@ -300,13 +310,13 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { // Check emitted events await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, ethWithdrawAmount); + .withArgs(weth.address, curveOethEthPool.address, ethWithdrawAmount); await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(oeth.address, oethMetaPool.address, oethBurnAmount); + .withArgs(oeth.address, curveOethEthPool.address, oethBurnAmount); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].sub(ethWithdrawAmount), 0.05 // 0.05% or 5 basis point @@ -327,7 +337,7 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { const { convexEthMetaStrategy, oeth, - oethMetaPool, + curveOethEthPool, oethVault, oethVaultSigner, weth, @@ -347,9 +357,13 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); // Now try to withdraw the WETH from the strategy + // prettier-ignore const tx = await convexEthMetaStrategy - .connect(oethVaultSigner) - .withdraw(oethVault.address, weth.address, withdrawAmount); + .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( + oethVault.address, + weth.address, + withdrawAmount + ); const receipt = await tx.wait(); @@ -363,13 +377,16 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { // Check emitted events await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, withdrawAmount); + .withArgs(weth.address, curveOethEthPool.address, withdrawAmount); await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withNamedArgs({ _asset: oeth.address, _pToken: oethMetaPool.address }); + .withNamedArgs({ + _asset: oeth.address, + _pToken: curveOethEthPool.address, + }); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].sub(withdrawAmount), 0.05 // 0.05% or 5 basis point @@ -402,9 +419,13 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { } = fixture; for (const signer of [strategist, timelock, josh]) { + // prettier-ignore const tx = convexEthMetaStrategy - .connect(signer) - .withdraw(oethVault.address, weth.address, parseUnits("50")); + .connect(signer)["withdraw(address,address,uint256)"]( + oethVault.address, + weth.address, + parseUnits("50") + ); await expect(tx).to.revertedWith("Caller is not the Vault"); } @@ -424,8 +445,8 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); }); - describe("with a lot more OETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + describe("with a lot more OETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: false, poolAddOethAmount: 4000, @@ -434,26 +455,26 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { beforeEach(async () => { fixture = await loadFixture(); }); - it("Strategist should remove a little OETH from the Metapool", async () => { + it("Strategist should remove a little OETH from the Curve pool", async () => { await assertRemoveAndBurn(parseUnits("3"), fixture); }); - it("Strategist should remove a lot of OETH from the Metapool", async () => { + it("Strategist should remove a lot of OETH from the Curve pool", async () => { await assertRemoveAndBurn(parseUnits("3500"), fixture); }); - it("Strategist should fail to add even more OETH to the Metapool", async () => { + it("Strategist should fail to add even more OETH to the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Mint and add OETH to the Metapool + // Mint and add OETH to the Curve pool const tx = convexEthMetaStrategy .connect(strategist) .mintAndAddOTokens(parseUnits("1")); await expect(tx).to.be.revertedWith("OTokens balance worse"); }); - it("Strategist should fail to remove the little ETH from the Metapool", async () => { + it("Strategist should fail to remove the little ETH from the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Remove ETH form the Metapool + // Remove ETH form the Curve pool const tx = convexEthMetaStrategy .connect(strategist) .removeOnlyAssets(parseUnits("1")); @@ -462,8 +483,8 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); }); - describe("with a lot more ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + describe("with a lot more ETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: false, poolAddEthAmount: 200000, @@ -472,17 +493,17 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { beforeEach(async () => { fixture = await loadFixture(); }); - it("Strategist should add a little OETH to the Metapool", async () => { + it("Strategist should add a little OETH to the Curve pool", async () => { const oethMintAmount = oethUnits("3"); await assertMintAndAddOTokens(oethMintAmount, fixture); }); - it("Strategist should add a lot of OETH to the Metapool", async () => { + it("Strategist should add a lot of OETH to the Curve pool", async () => { const oethMintAmount = oethUnits("150000"); await assertMintAndAddOTokens(oethMintAmount, fixture); }); - it("Strategist should add OETH to balance the Metapool", async () => { - const { oethMetaPool } = fixture; - const curveBalances = await oethMetaPool.get_balances(); + it("Strategist should add OETH to balance the Curve pool", async () => { + const { curveOethEthPool } = fixture; + const curveBalances = await curveOethEthPool.get_balances(); const oethMintAmount = curveBalances[0] .sub(curveBalances[1]) // reduce by 0.001% @@ -491,18 +512,18 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { await assertMintAndAddOTokens(oethMintAmount, fixture); }); - it("Strategist should remove a little ETH from the Metapool", async () => { + it("Strategist should remove a little ETH from the Curve pool", async () => { const lpAmount = parseUnits("2"); await assertRemoveOnlyAssets(lpAmount, fixture); }); - it("Strategist should remove a lot ETH from the Metapool", async () => { + it("Strategist should remove a lot ETH from the Curve pool", async () => { const lpAmount = parseUnits("5000"); await assertRemoveOnlyAssets(lpAmount, fixture); }); }); - describe("with a little more ETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + describe("with a little more ETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: false, poolAddEthAmount: 8000, @@ -511,9 +532,9 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { beforeEach(async () => { fixture = await loadFixture(); }); - it("Strategist should remove ETH to balance the Metapool", async () => { - const { oethMetaPool } = fixture; - const curveBalances = await oethMetaPool.get_balances(); + it("Strategist should remove ETH to balance the Curve pool", async () => { + const { curveOethEthPool } = fixture; + const curveBalances = await curveOethEthPool.get_balances(); const lpAmount = curveBalances[0] .sub(curveBalances[1]) // reduce by 1% @@ -523,30 +544,41 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { await assertRemoveOnlyAssets(lpAmount, fixture); }); - it("Strategist should fail to add too much OETH to the Metapool", async () => { + it("Strategist should fail to add too much OETH to the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Add OETH to the Metapool + const oethAmount = parseUnits("9000"); + log( + `Before mint of ${formatUnits( + oethAmount + )} OETH and add to the Curve pool` + ); + await run("amoStrat", { + pool: "OETH", + output: false, + }); + + // Add OETH to the Curve pool const tx = convexEthMetaStrategy .connect(strategist) - .mintAndAddOTokens(parseUnits("10000")); + .mintAndAddOTokens(oethAmount); await expect(tx).to.be.revertedWith("Assets overshot peg"); }); - it("Strategist should fail to remove too much ETH from the Metapool", async () => { + it("Strategist should fail to remove too much ETH from the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Remove ETH from the Metapool + // Remove ETH from the Curve pool const tx = convexEthMetaStrategy .connect(strategist) - .removeOnlyAssets(parseUnits("10000")); + .removeOnlyAssets(parseUnits("8000")); await expect(tx).to.be.revertedWith("Assets overshot peg"); }); - it("Strategist should fail to remove the little OETH from the Metapool", async () => { + it("Strategist should fail to remove the little OETH from the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Remove ETH from the Metapool + // Remove ETH from the Curve pool const tx = convexEthMetaStrategy .connect(strategist) .removeAndBurnOTokens(parseUnits("1")); @@ -555,8 +587,8 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); }); - describe("with a little more OETH in the Metapool", () => { - const loadFixture = createFixtureLoader(convexOETHMetaVaultFixture, { + describe("with a little more OETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexOethEthAmoFixture, { wethMintAmount: 5000, depositToStrategy: false, poolAddOethAmount: 500, @@ -565,10 +597,10 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { beforeEach(async () => { fixture = await loadFixture(); }); - it("Strategist should fail to remove too much OETH from the Metapool", async () => { + it("Strategist should fail to remove too much OETH from the Curve pool", async () => { const { convexEthMetaStrategy, strategist } = fixture; - // Remove OETH from the Metapool + // Remove OETH from the Curve pool const tx = convexEthMetaStrategy .connect(strategist) .removeAndBurnOTokens(parseUnits("4000")); @@ -579,28 +611,30 @@ describe("ForkTest: OETH AMO Curve Metapool Strategy", function () { }); async function assertRemoveAndBurn(lpAmount, fixture) { - const { convexEthMetaStrategy, oethMetaPool, oeth, strategist } = fixture; + const { convexEthMetaStrategy, curveOethEthPool, oeth, strategist } = fixture; const oethBurnAmount = await calcOethRemoveAmount(fixture, lpAmount); - const curveBalancesBefore = await oethMetaPool.get_balances(); + const curveBalancesBefore = await curveOethEthPool.get_balances(); const oethSupplyBefore = await oeth.totalSupply(); log( - `Before remove and burn of ${formatUnits(lpAmount)} OETH from the Metapool` + `Before remove and burn of ${formatUnits( + lpAmount + )} OETH from the Curve pool` ); await run("amoStrat", { pool: "OETH", output: false, }); - // Remove and burn OETH from the Metapool + // Remove and burn OETH from the Curve pool const tx = await convexEthMetaStrategy .connect(strategist) .removeAndBurnOTokens(lpAmount); const receipt = await tx.wait(); - log("After remove and burn of OETH from Metapool"); + log("After remove and burn of OETH from Curve pool"); await run("amoStrat", { pool: "OETH", output: false, @@ -610,10 +644,10 @@ async function assertRemoveAndBurn(lpAmount, fixture) { // Check emitted event await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(oeth.address, oethMetaPool.address, oethBurnAmount); + .withArgs(oeth.address, curveOethEthPool.address, oethBurnAmount); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.equal(curveBalancesBefore[0]); expect(curveBalancesAfter[1]).to.approxEqualTolerance( curveBalancesBefore[1].sub(oethBurnAmount), @@ -629,20 +663,20 @@ async function assertRemoveAndBurn(lpAmount, fixture) { } async function assertMintAndAddOTokens(oethMintAmount, fixture) { - const { convexEthMetaStrategy, oethMetaPool, oeth, strategist } = fixture; + const { convexEthMetaStrategy, curveOethEthPool, oeth, strategist } = fixture; - const curveBalancesBefore = await oethMetaPool.get_balances(); + const curveBalancesBefore = await curveOethEthPool.get_balances(); const oethSupplyBefore = await oeth.totalSupply(); log( - `Before mint and add ${formatUnits(oethMintAmount)} OETH to the Metapool` + `Before mint and add ${formatUnits(oethMintAmount)} OETH to the Curve pool` ); await run("amoStrat", { pool: "OETH", output: false, }); - // Mint and add OETH to the Metapool + // Mint and add OETH to the Curve pool const tx = await convexEthMetaStrategy .connect(strategist) .mintAndAddOTokens(oethMintAmount); @@ -652,17 +686,17 @@ async function assertMintAndAddOTokens(oethMintAmount, fixture) { // Check emitted event await expect(tx) .emit(convexEthMetaStrategy, "Deposit") - .withArgs(oeth.address, oethMetaPool.address, oethMintAmount); + .withArgs(oeth.address, curveOethEthPool.address, oethMintAmount); - log("After mint and add of OETH to the Metapool"); + log("After mint and add of OETH to the Curve pool"); await run("amoStrat", { pool: "OETH", output: false, fromBlock: receipt.blockNumber - 1, }); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0], 0.01 // 0.01% or 1 basis point @@ -683,41 +717,41 @@ async function assertMintAndAddOTokens(oethMintAmount, fixture) { async function assertRemoveOnlyAssets(lpAmount, fixture) { const { convexEthMetaStrategy, - cvxRewardPool, - oethMetaPool, + convexOethEthRewardsPool, + curveOethEthPool, oethVault, oeth, strategist, weth, } = fixture; - log(`Removing ${formatUnits(lpAmount)} ETH from the Metapool`); + log(`Removing ${formatUnits(lpAmount)} ETH from the Curve pool`); const ethRemoveAmount = await calcEthRemoveAmount(fixture, lpAmount); log("After calc ETH remove amount"); - const curveBalancesBefore = await oethMetaPool.get_balances(); + const curveBalancesBefore = await curveOethEthPool.get_balances(); const oethSupplyBefore = await oeth.totalSupply(); const vaultWethBalanceBefore = await weth.balanceOf(oethVault.address); - const strategyLpBalanceBefore = await cvxRewardPool.balanceOf( + const strategyLpBalanceBefore = await convexOethEthRewardsPool.balanceOf( convexEthMetaStrategy.address ); const vaultValueBefore = await oethVault.totalValue(); log( - `Before remove and burn of ${formatUnits(lpAmount)} ETH from the Metapool` + `Before remove and burn of ${formatUnits(lpAmount)} ETH from the Curve pool` ); await run("amoStrat", { pool: "OETH", output: false, }); - // Remove ETH from the Metapool and transfer to the Vault as WETH + // Remove ETH from the Curve pool and transfer to the Vault as WETH const tx = await convexEthMetaStrategy .connect(strategist) .removeOnlyAssets(lpAmount); const receipt = await tx.wait(); - log("After remove and burn of ETH from Metapool"); + log("After remove and burn of ETH from Curve pool"); await run("amoStrat", { pool: "OETH", output: false, @@ -727,10 +761,10 @@ async function assertRemoveOnlyAssets(lpAmount, fixture) { // Check emitted event await expect(tx) .to.emit(convexEthMetaStrategy, "Withdrawal") - .withArgs(weth.address, oethMetaPool.address, ethRemoveAmount); + .withArgs(weth.address, curveOethEthPool.address, ethRemoveAmount); - // Check the ETH and OETH balances in the Curve Metapool - const curveBalancesAfter = await oethMetaPool.get_balances(); + // Check the ETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveOethEthPool.get_balances(); expect(curveBalancesAfter[0]).to.approxEqualTolerance( curveBalancesBefore[0].sub(ethRemoveAmount), 0.01 // 0.01% or 1 basis point @@ -754,7 +788,7 @@ async function assertRemoveOnlyAssets(lpAmount, fixture) { expect(vaultValueAfter.sub(vaultValueBefore)).to.gt(parseUnits("-1")); // Check the strategy LP balance decreased - const strategyLpBalanceAfter = await cvxRewardPool.balanceOf( + const strategyLpBalanceAfter = await convexOethEthRewardsPool.balanceOf( convexEthMetaStrategy.address ); expect(strategyLpBalanceBefore.sub(strategyLpBalanceAfter)).to.eq(lpAmount); @@ -762,10 +796,10 @@ async function assertRemoveOnlyAssets(lpAmount, fixture) { // Calculate the minted OETH amount for a deposit async function calcOethMintAmount(fixture, wethDepositAmount) { - const { oethMetaPool } = fixture; + const { curveOethEthPool } = fixture; - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); + // Get the ETH and OETH balances in the Curve pool + const curveBalances = await curveOethEthPool.get_balances(); // ETH balance - OETH balance const balanceDiff = curveBalances[0].sub(curveBalances[1]); @@ -786,10 +820,10 @@ async function calcOethMintAmount(fixture, wethDepositAmount) { // Calculate the amount of OETH burnt from a withdraw async function calcOethWithdrawAmount(fixture, wethWithdrawAmount) { - const { oethMetaPool } = fixture; + const { curveOethEthPool } = fixture; - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); + // Get the ETH and OETH balances in the Curve pool + const curveBalances = await curveOethEthPool.get_balances(); // OETH to burn = WETH withdrawn * OETH pool balance / ETH pool balance const oethBurnAmount = wethWithdrawAmount @@ -803,14 +837,15 @@ async function calcOethWithdrawAmount(fixture, wethWithdrawAmount) { // Calculate the OETH and ETH amounts from a withdrawAll async function calcWithdrawAllAmounts(fixture) { - const { convexEthMetaStrategy, cvxRewardPool, oethMetaPool } = fixture; + const { convexEthMetaStrategy, convexOethEthRewardsPool, curveOethEthPool } = + fixture; - // Get the ETH and OETH balances in the Curve Metapool - const curveBalances = await oethMetaPool.get_balances(); - const strategyLpAmount = await cvxRewardPool.balanceOf( + // Get the ETH and OETH balances in the Curve pool + const curveBalances = await curveOethEthPool.get_balances(); + const strategyLpAmount = await convexOethEthRewardsPool.balanceOf( convexEthMetaStrategy.address ); - const totalLpSupply = await oethMetaPool.totalSupply(); + const totalLpSupply = await curveOethEthPool.totalSupply(); // OETH to burn = OETH pool balance * strategy LP amount / total pool LP amount const oethBurnAmount = curveBalances[1] @@ -829,11 +864,11 @@ async function calcWithdrawAllAmounts(fixture) { // Calculate the amount of OETH burned from a removeAndBurnOTokens async function calcOethRemoveAmount(fixture, lpAmount) { - const { oethGaugeSigner, oethMetaPool } = fixture; + const { curveOethEthGaugeSigner, curveOethEthPool } = fixture; - // Static call to get the OETH removed from the Metapool for a given amount of LP tokens - const oethBurnAmount = await oethMetaPool - .connect(oethGaugeSigner) + // Static call to get the OETH removed from the Curve pool for a given amount of LP tokens + const oethBurnAmount = await curveOethEthPool + .connect(curveOethEthGaugeSigner) .callStatic["remove_liquidity_one_coin(uint256,int128,uint256)"]( lpAmount, 1, @@ -847,10 +882,10 @@ async function calcOethRemoveAmount(fixture, lpAmount) { // Calculate the amount of ETH burned from a removeOnlyAssets async function calcEthRemoveAmount(fixture, lpAmount) { - const { oethMetaPool } = fixture; + const { curveOethEthPool } = fixture; - // Get the ETH removed from the Metapool for a given amount of LP tokens - const ethRemoveAmount = await oethMetaPool.calc_withdraw_one_coin( + // Get the ETH removed from the Curve pool for a given amount of LP tokens + const ethRemoveAmount = await curveOethEthPool.calc_withdraw_one_coin( lpAmount, 0 ); diff --git a/contracts/test/strategies/oeth-amo-curve-frx.fork-test.js b/contracts/test/strategies/oeth-amo-curve-frx.fork-test.js new file mode 100644 index 0000000000..7361c9e44f --- /dev/null +++ b/contracts/test/strategies/oeth-amo-curve-frx.fork-test.js @@ -0,0 +1,920 @@ +const { expect } = require("chai"); +const { formatUnits, parseUnits } = require("ethers/lib/utils"); +// const { run } = require("hardhat"); + +const addresses = require("../../utils/addresses"); +const { convex_frxETH_OETH_PID } = require("../../utils/constants"); +const { units, oethUnits, isCI } = require("../helpers"); +const { + createFixtureLoader, + convexFrxEthAmoFixture, + loadDefaultFixture, +} = require("../fixture/_fixture"); + +const log = require("../../utils/logger")("test:fork:oeth:amo:curve:frxETH"); + +describe("ForkTest: OETH AMO Curve frxETH/OETH Strategy", function () { + this.timeout(0); + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture; + + describe("with mainnet data", () => { + beforeEach(async () => { + fixture = await loadDefaultFixture(); + }); + it("Should have constants and immutables set", async () => { + const { convexFrxETHAMOStrategy } = fixture; + + expect(await convexFrxETHAMOStrategy.MAX_SLIPPAGE()).to.equal( + parseUnits("0.01", 18) + ); + expect(await convexFrxETHAMOStrategy.cvxDepositorAddress()).to.equal( + addresses.mainnet.CVXBooster + ); + expect(await convexFrxETHAMOStrategy.cvxRewardStaker()).to.equal( + addresses.mainnet.CVXFrxETHRewardsPool + ); + expect(await convexFrxETHAMOStrategy.cvxDepositorPTokenId()).to.equal( + convex_frxETH_OETH_PID + ); + expect(await convexFrxETHAMOStrategy.curvePool()).to.equal( + addresses.mainnet.CurveFrxETHOETHPool + ); + expect(await convexFrxETHAMOStrategy.lpToken()).to.equal( + addresses.mainnet.CurveFrxETHOETHPool + ); + expect(await convexFrxETHAMOStrategy.oToken()).to.equal( + addresses.mainnet.OETHProxy + ); + expect(await convexFrxETHAMOStrategy.vaultAsset()).to.equal( + addresses.mainnet.frxETH + ); + expect(await convexFrxETHAMOStrategy.poolAsset()).to.equal( + addresses.mainnet.frxETH + ); + }); + it("Should have AMO strategy configured in the vault", async () => { + const { oethVault, convexFrxETHAMOStrategy } = fixture; + + const config = await oethVault.getStrategyConfig( + convexFrxETHAMOStrategy.address + ); + expect(config.isSupported).to.be.true; + expect(config.isAMO).to.be.true; + expect(config.mintForStrategy).to.eq(0); + expect(config.mintForStrategyThreshold).to.be.eq(parseUnits("25", 21)); + }); + it("Should be able to check balance", async () => { + const { frxETH, josh, convexFrxETHAMOStrategy } = fixture; + + const balance = await convexFrxETHAMOStrategy.checkBalance( + frxETH.address + ); + log(`check balance ${balance}`); + + // This uses a transaction to call a view function so the gas usage can be reported. + const tx = await convexFrxETHAMOStrategy + .connect(josh) + .populateTransaction.checkBalance(frxETH.address); + await josh.sendTransaction(tx); + }); + it.skip("Should be able to harvest the rewards", async function () { + const { + josh, + weth, + oethHarvester, + oethDripper, + oethVault, + convexFrxETHAMOStrategy, + crv, + } = fixture; + + // send some CRV to the strategy to partly simulate reward harvesting + await crv + .connect(josh) + .transfer(convexFrxETHAMOStrategy.address, parseUnits("1000")); + + const wethBefore = await weth.balanceOf(oethDripper.address); + + // prettier-ignore + await oethHarvester + .connect(josh)["harvestAndSwap(address)"](convexFrxETHAMOStrategy.address); + + const wethDiff = (await weth.balanceOf(oethDripper.address)).sub( + wethBefore + ); + await oethVault.connect(josh).rebase(); + + await expect(wethDiff).to.be.gte(parseUnits("0.15")); + }); + }); + + describe("with frxETH/ETH AMO fixture", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Only Governor can approve all tokens", async () => { + const { + timelock, + strategist, + josh, + oethVaultSigner, + convexFrxETHAMOStrategy, + frxETH, + oeth, + weth, + curveFrxEthOethPool, + } = fixture; + + // Governor can approve all tokens + const tx = await convexFrxETHAMOStrategy + .connect(timelock) + .safeApproveAllTokens(); + await expect(tx).to.emit(frxETH, "Approval"); + await expect(tx).to.emit(oeth, "Approval"); + await expect(tx).to.not.emit(weth, "Approval"); + await expect(tx).to.emit(curveFrxEthOethPool, "Approval"); + + for (const signer of [strategist, josh, oethVaultSigner]) { + const tx = convexFrxETHAMOStrategy + .connect(signer) + .safeApproveAllTokens(); + await expect(tx).to.be.revertedWith("Caller is not the Governor"); + } + }); + }); + + describe("with some frxETH in the vault", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5001, + depositToStrategy: false, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Vault should deposit some frxETH to AMO strategy", async function () { + const { + convexFrxETHAMOStrategy, + oeth, + curveFrxEthOethPool, + oethVaultSigner, + frxETH, + } = fixture; + + const frxEthDepositAmount = await units("5000", frxETH); + + log( + `virtual price ${formatUnits( + await curveFrxEthOethPool.get_virtual_price(), + 18 + )}` + ); + + // Vault transfers frxETH to strategy + await frxETH + .connect(oethVaultSigner) + .transfer(convexFrxETHAMOStrategy.address, frxEthDepositAmount); + + const { oethMintAmount, curveBalances: curveBalancesBefore } = + await calcOethMintAmount(fixture, frxEthDepositAmount); + const oethSupplyBefore = await oeth.totalSupply(); + + // log("Before deposit to strategy"); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // prettier-ignore + const tx = await convexFrxETHAMOStrategy + .connect(oethVaultSigner)["deposit(address,uint256)"](frxETH.address, frxEthDepositAmount); + + // log("After deposit to strategy"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check emitted events + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Deposit") + .withArgs( + frxETH.address, + curveFrxEthOethPool.address, + frxEthDepositAmount + ); + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Deposit") + .withArgs(oeth.address, curveFrxEthOethPool.address, oethMintAmount); + + // Check the frxETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.approxEqualTolerance( + curveBalancesBefore[0].add(frxEthDepositAmount), + 0.01 // 0.01% or 1 basis point + ); + expect(curveBalancesAfter[1]).to.approxEqualTolerance( + curveBalancesBefore[1].add(oethMintAmount), + 0.01 // 0.01% or 1 basis point + ); + + // Check the OETH total supply increase + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore.add(oethMintAmount), + 0.01 // 0.01% or 1 basis point + ); + }); + it("Only vault can deposit some frxETH to AMO strategy", async function () { + const { + convexFrxETHAMOStrategy, + oethVaultSigner, + strategist, + timelock, + josh, + frxETH, + } = fixture; + + const depositAmount = parseUnits("50"); + await frxETH + .connect(oethVaultSigner) + .transfer(convexFrxETHAMOStrategy.address, depositAmount); + + for (const signer of [strategist, timelock, josh]) { + // prettier-ignore + const tx = convexFrxETHAMOStrategy + .connect(signer)["deposit(address,uint256)"](frxETH.address, depositAmount); + + await expect(tx).to.revertedWith("Caller is not the Vault"); + } + }); + it("Only vault can deposit all frxETH to AMO strategy", async function () { + const { + convexFrxETHAMOStrategy, + curveFrxEthOethPool, + oethVaultSigner, + strategist, + timelock, + josh, + frxETH, + } = fixture; + + const depositAmount = parseUnits("50"); + await frxETH + .connect(oethVaultSigner) + .transfer(convexFrxETHAMOStrategy.address, depositAmount); + + for (const signer of [strategist, timelock, josh]) { + const tx = convexFrxETHAMOStrategy.connect(signer).depositAll(); + + await expect(tx).to.revertedWith("Caller is not the Vault"); + } + + const tx = await convexFrxETHAMOStrategy + .connect(oethVaultSigner) + .depositAll(); + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Deposit") + .withNamedArgs({ + _asset: frxETH.address, + _pToken: curveFrxEthOethPool.address, + }); + }); + }); + + describe("with the strategy having some frxETH and OETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5000, + depositToStrategy: true, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Vault should be able to withdraw all", async () => { + const { + convexFrxETHAMOStrategy, + curveFrxEthOethPool, + oeth, + oethVaultSigner, + frxETH, + } = fixture; + + const { + oethBurnAmount, + frxEthWithdrawAmount, + curveBalances: curveBalancesBefore, + } = await calcWithdrawAllAmounts(fixture); + + const oethSupplyBefore = await oeth.totalSupply(); + + // log("Before withdraw all from strategy"); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // Now try to withdraw all the frxETH from the strategy + const tx = await convexFrxETHAMOStrategy + .connect(oethVaultSigner) + .withdrawAll(); + + // log("After withdraw all from strategy"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check emitted events + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withArgs( + frxETH.address, + curveFrxEthOethPool.address, + frxEthWithdrawAmount + ); + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withArgs(oeth.address, curveFrxEthOethPool.address, oethBurnAmount); + + // Check the frxETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.approxEqualTolerance( + curveBalancesBefore[0].sub(frxEthWithdrawAmount), + 0.01 // 0.01% or 1 basis point + ); + expect(curveBalancesAfter[1]).to.approxEqualTolerance( + curveBalancesBefore[1].sub(oethBurnAmount), + 0.01 // 0.01% + ); + + // Check the OETH total supply decrease + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore.sub(oethBurnAmount), + 0.01 // 0.01% or 1 basis point + ); + }); + it("Vault should be able to withdraw some", async () => { + const { + convexFrxETHAMOStrategy, + oeth, + curveFrxEthOethPool, + oethVault, + oethVaultSigner, + frxETH, + } = fixture; + + const withdrawAmount = oethUnits("1000"); + + const { oethBurnAmount, curveBalances: curveBalancesBefore } = + await calcOethWithdrawAmount(fixture, withdrawAmount); + const oethSupplyBefore = await oeth.totalSupply(); + const vaultFrxEthBalanceBefore = await frxETH.balanceOf( + oethVault.address + ); + + // log("Before withdraw from strategy"); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // Now try to withdraw the frxETH from the strategy + // prettier-ignore + const tx = await convexFrxETHAMOStrategy + .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( + oethVault.address, + frxETH.address, + withdrawAmount + ); + + // log("After withdraw from strategy"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check emitted events + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withArgs(frxETH.address, curveFrxEthOethPool.address, withdrawAmount); + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withNamedArgs({ + _asset: oeth.address, + _pToken: curveFrxEthOethPool.address, + }); + + // Check the frxETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.approxEqualTolerance( + curveBalancesBefore[0].sub(withdrawAmount), + 0.01 // 0.01% or 1 basis point + ); + expect(curveBalancesAfter[1]).to.approxEqualTolerance( + curveBalancesBefore[1].sub(oethBurnAmount), + 0.01 // 0.01% + ); + + // Check the OETH total supply decrease + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore.sub(oethBurnAmount), + 0.01 // 0.01% or 1 basis point + ); + + // Check the frxETH balance in the Vault + expect(await frxETH.balanceOf(oethVault.address)).to.equal( + vaultFrxEthBalanceBefore.add(withdrawAmount) + ); + }); + it("Only vault can withdraw some frxETH from AMO strategy", async function () { + const { + convexFrxETHAMOStrategy, + oethVault, + strategist, + timelock, + josh, + frxETH, + } = fixture; + + for (const signer of [strategist, timelock, josh]) { + // prettier-ignore + const tx = convexFrxETHAMOStrategy + .connect(signer)["withdraw(address,address,uint256)"]( + oethVault.address, + frxETH.address, + parseUnits("50") + ); + + await expect(tx).to.revertedWith("Caller is not the Vault"); + } + }); + it("Only vault and governor can withdraw all frxETH from AMO strategy", async function () { + const { convexFrxETHAMOStrategy, strategist, timelock, josh } = fixture; + + for (const signer of [strategist, josh]) { + const tx = convexFrxETHAMOStrategy.connect(signer).withdrawAll(); + + await expect(tx).to.revertedWith("Caller is not the Vault or Governor"); + } + + // Governor can withdraw all + const tx = convexFrxETHAMOStrategy.connect(timelock).withdrawAll(); + await expect(tx).to.emit(convexFrxETHAMOStrategy, "Withdrawal"); + }); + }); + + describe("with a lot more OETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5000, + depositToStrategy: true, + poolAddOethAmount: 4000, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Strategist should remove a little OETH from the Curve pool", async () => { + await assertRemoveAndBurn(parseUnits("3"), fixture); + }); + it("Strategist should remove a lot of OETH from the Curve pool", async () => { + await assertRemoveAndBurn(parseUnits("2000"), fixture); + }); + it("Strategist should fail to add even more OETH to the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Mint and add OETH to the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .mintAndAddOTokens(parseUnits("1")); + + await expect(tx).to.be.revertedWith("OTokens balance worse"); + }); + it("Strategist should fail to remove the little frxETH from the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Remove frxETH form the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .removeOnlyAssets(parseUnits("1")); + + await expect(tx).to.be.revertedWith("OTokens balance worse"); + }); + }); + + describe("with a lot more frxETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5000, + depositToStrategy: true, + poolAddFrxEthAmount: 8000, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Strategist should add a little OETH to the Curve pool", async () => { + const oethMintAmount = oethUnits("3"); + await assertMintAndAddOTokens(oethMintAmount, fixture); + }); + it("Strategist should add a lot of OETH to the Curve pool", async () => { + const oethMintAmount = oethUnits("7000"); + await assertMintAndAddOTokens(oethMintAmount, fixture); + }); + it("Strategist should add OETH to balance the Curve pool", async () => { + const { curveFrxEthOethPool } = fixture; + const curveBalances = await curveFrxEthOethPool.get_balances(); + // frxETH balance - OETH balance + const oethMintAmount = curveBalances[0] + .sub(curveBalances[1]) + // reduce by 0.0001% + .mul(999999) + .div(1000000); + log(`oethMintAmount ${formatUnits(oethMintAmount)}`); + + await assertMintAndAddOTokens(oethMintAmount, fixture); + }); + it("Strategist should remove a little frxETH from the Curve pool", async () => { + const lpAmount = parseUnits("2"); + await assertRemoveOnlyAssets(lpAmount, fixture); + }); + it("Strategist should remove a lot frxETH from the Curve pool", async () => { + const lpAmount = parseUnits("6000"); + await assertRemoveOnlyAssets(lpAmount, fixture); + }); + }); + + describe("with a little more frxETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5000, + depositToStrategy: true, + poolAddFrxEthAmount: 100, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Strategist should remove frxETH to balance the Curve pool", async () => { + const { curveFrxEthOethPool } = fixture; + const curveBalances = await curveFrxEthOethPool.get_balances(); + const lpAmount = curveBalances[0] + .sub(curveBalances[1]) + // reduce by 0.1% + .mul(999) + .div(1000); + expect(lpAmount).to.be.gt(0); + + await assertRemoveOnlyAssets(lpAmount, fixture); + }); + it("Strategist should fail to add too much OETH to the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Add OETH to the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .mintAndAddOTokens(parseUnits("500")); + + await expect(tx).to.be.revertedWith("Assets overshot peg"); + }); + it("Strategist should fail to remove too much frxETH from the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Remove frxETH from the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .removeOnlyAssets(parseUnits("500")); + + await expect(tx).to.be.revertedWith("Assets overshot peg"); + }); + it("Strategist should fail to remove the little OETH from the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Remove frxETH from the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .removeAndBurnOTokens(parseUnits("1")); + + await expect(tx).to.be.revertedWith("Assets balance worse"); + }); + }); + + describe("with a little more OETH in the Curve pool", () => { + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture, { + frxEthMintAmount: 5000, + depositToStrategy: true, + poolAddOethAmount: 100, + }); + beforeEach(async () => { + fixture = await loadFixture(); + }); + it("Strategist should fail to remove too much OETH from the Curve pool", async () => { + const { convexFrxETHAMOStrategy, strategist } = fixture; + + // Remove OETH from the Curve pool + const tx = convexFrxETHAMOStrategy + .connect(strategist) + .removeAndBurnOTokens(parseUnits("200")); + + await expect(tx).to.be.revertedWith("OTokens overshot peg"); + }); + }); +}); + +async function assertRemoveAndBurn(lpAmount, fixture) { + const { convexFrxETHAMOStrategy, curveFrxEthOethPool, oeth, strategist } = + fixture; + + const oethBurnAmount = await calcOethRemoveAmount(fixture, lpAmount); + const curveBalancesBefore = await curveFrxEthOethPool.get_balances(); + const oethSupplyBefore = await oeth.totalSupply(); + + // log( + // `Before remove and burn of ${formatUnits( + // lpAmount + // )} OETH from the Curve pool` + // ); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // Remove and burn OETH from the Curve pool + const tx = await convexFrxETHAMOStrategy + .connect(strategist) + .removeAndBurnOTokens(lpAmount); + + // log("After remove and burn of OETH from Curve pool"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check emitted event + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withArgs(oeth.address, curveFrxEthOethPool.address, oethBurnAmount); + + // Check the frxETH and OETH balances in the Curve Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.equal(curveBalancesBefore[0]); + expect(curveBalancesAfter[1]).to.approxEqualTolerance( + curveBalancesBefore[1].sub(oethBurnAmount), + 0.01 // 0.01% + ); + + // Check the OETH total supply decrease + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore.sub(oethBurnAmount), + 0.01 // 0.01% or 1 basis point + ); +} + +async function assertMintAndAddOTokens(oethMintAmount, fixture) { + const { convexFrxETHAMOStrategy, curveFrxEthOethPool, oeth, strategist } = + fixture; + + const curveBalancesBefore = await curveFrxEthOethPool.get_balances(); + const oethSupplyBefore = await oeth.totalSupply(); + + // log( + // `Before mint and add ${formatUnits(oethMintAmount)} OETH to the Curve pool` + // ); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // Mint and add OETH to the Curve pool + const tx = await convexFrxETHAMOStrategy + .connect(strategist) + .mintAndAddOTokens(oethMintAmount); + + // Check emitted event + await expect(tx) + .emit(convexFrxETHAMOStrategy, "Deposit") + .withArgs(oeth.address, curveFrxEthOethPool.address, oethMintAmount); + + // log("After mint and add of OETH to the Curve pool"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check the frxETH and OETH balances in the Curve Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.approxEqualTolerance( + curveBalancesBefore[0], + 0.1 // 0.1% or 10 basis point + ); + expect(curveBalancesAfter[1]).to.approxEqualTolerance( + curveBalancesBefore[1].add(oethMintAmount), + 0.1 // 0.1% or 10 basis point + ); + + // Check the OETH total supply decrease + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore.add(oethMintAmount), + 0.01 // 0.01% or 1 basis point + ); +} + +async function assertRemoveOnlyAssets(lpAmount, fixture) { + const { + convexFrxETHAMOStrategy, + convexFrxEthOethRewardsPool, + curveFrxEthOethPool, + oethVault, + oeth, + strategist, + frxETH, + } = fixture; + + log( + `Burning ${formatUnits( + lpAmount + )} LP tokens to remove frxETH from the Curve pool` + ); + const frxEthRemoveAmount = await calcEthRemoveAmount(fixture, lpAmount); + const curveBalancesBefore = await curveFrxEthOethPool.get_balances(); + const oethSupplyBefore = await oeth.totalSupply(); + const vaultFrxEthBalanceBefore = await frxETH.balanceOf(oethVault.address); + const strategyLpBalanceBefore = await convexFrxEthOethRewardsPool.balanceOf( + convexFrxETHAMOStrategy.address + ); + log( + `Strategy has ${formatUnits(strategyLpBalanceBefore)} Curve pool LP tokens` + ); + const vaultValueBefore = await oethVault.totalValue(); + + // log( + // `Before remove and burn of ${formatUnits(lpAmount)} frxETH from the Curve pool` + // ); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // }); + + // Remove frxETH from the Curve pool and transfer to the Vault + const tx = await convexFrxETHAMOStrategy + .connect(strategist) + .removeOnlyAssets(lpAmount); + + // log("After remove and burn of frxETH from Curve pool"); + // const receipt = await tx.wait(); + // await run("amoStrat", { + // pool: "OETH", + // output: false, + // fromBlock: receipt.blockNumber - 1, + // }); + + // Check emitted event + await expect(tx) + .to.emit(convexFrxETHAMOStrategy, "Withdrawal") + .withArgs(frxETH.address, curveFrxEthOethPool.address, frxEthRemoveAmount); + + // Check the frxETH and OETH balances in the Curve pool + const curveBalancesAfter = await curveFrxEthOethPool.get_balances(); + expect(curveBalancesAfter[0]).to.approxEqualTolerance( + curveBalancesBefore[0].sub(frxEthRemoveAmount), + 0.01 // 0.01% or 1 basis point + ); + expect(curveBalancesAfter[1]).to.equal(curveBalancesBefore[1]); + + // Check the OETH total supply is the same + const oethSupplyAfter = await oeth.totalSupply(); + expect(oethSupplyAfter).to.approxEqualTolerance( + oethSupplyBefore, + 0.01 // 0.01% or 1 basis point + ); + + // Check the frxETH balance in the Vault + expect(await frxETH.balanceOf(oethVault.address)).to.equal( + vaultFrxEthBalanceBefore.add(frxEthRemoveAmount) + ); + + // Check the vault made money + const vaultValueAfter = await oethVault.totalValue(); + expect(vaultValueAfter.sub(vaultValueBefore)).to.gt(parseUnits("-1")); + + // Check the strategy LP balance decreased + const strategyLpBalanceAfter = await convexFrxEthOethRewardsPool.balanceOf( + convexFrxETHAMOStrategy.address + ); + expect(strategyLpBalanceBefore.sub(strategyLpBalanceAfter)).to.eq(lpAmount); +} + +// Calculate the minted OETH amount for a deposit +async function calcOethMintAmount(fixture, frxEthDepositAmount) { + const { curveFrxEthOethPool } = fixture; + + // Get the frxETH and OETH balances in the Curve pool + const curveBalances = await curveFrxEthOethPool.get_balances(); + // frxETH balance - OETH balance + const balanceDiff = curveBalances[0].sub(curveBalances[1]); + + let oethMintAmount = balanceDiff.lte(0) + ? // If more OETH than frxETH then mint same amount of OETH as frxETH + frxEthDepositAmount + : // If less OETH than frxETH then mint the difference + balanceDiff.add(frxEthDepositAmount); + // Cap the minting to twice the frxETH deposit amount + const doubleFrxEthDepositAmount = frxEthDepositAmount.mul(2); + oethMintAmount = oethMintAmount.lte(doubleFrxEthDepositAmount) + ? oethMintAmount + : doubleFrxEthDepositAmount; + log(`OETH mint amount : ${formatUnits(oethMintAmount)}`); + + return { oethMintAmount, curveBalances }; +} + +// Calculate the amount of OETH burnt from a withdraw +async function calcOethWithdrawAmount(fixture, frxEthWithdrawAmount) { + const { curveFrxEthOethPool } = fixture; + + // Get the frxETH and OETH balances in the Curve pool + const curveBalances = await curveFrxEthOethPool.get_balances(); + + // OETH to burn = frxETH withdrawn * OETH pool balance / frxETH pool balance + const oethBurnAmount = frxEthWithdrawAmount + .mul(curveBalances[1]) + .div(curveBalances[0]); + + log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); + + return { oethBurnAmount, curveBalances }; +} + +// Calculate the OETH and frxETH amounts from a withdrawAll +async function calcWithdrawAllAmounts(fixture) { + const { + convexFrxETHAMOStrategy, + convexFrxEthOethRewardsPool, + curveFrxEthOethPool, + } = fixture; + + // Get the frxETH and OETH balances in the Curve pool + const curveBalances = await curveFrxEthOethPool.get_balances(); + const strategyLpAmount = await convexFrxEthOethRewardsPool.balanceOf( + convexFrxETHAMOStrategy.address + ); + const totalLpSupply = await curveFrxEthOethPool.totalSupply(); + + // OETH to burn = OETH pool balance * strategy LP amount / total pool LP amount + const oethBurnAmount = curveBalances[1] + .mul(strategyLpAmount) + .div(totalLpSupply); + // frxETH to withdraw = frxETH pool balance * strategy LP amount / total pool LP amount + const frxEthWithdrawAmount = curveBalances[0] + .mul(strategyLpAmount) + .div(totalLpSupply); + + log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); + log(`frxETH withdraw amount: ${formatUnits(frxEthWithdrawAmount)}`); + + return { oethBurnAmount, frxEthWithdrawAmount, curveBalances }; +} + +// Calculate the amount of OETH burned from a removeAndBurnOTokens +async function calcOethRemoveAmount(fixture, lpAmount) { + const { curveFrxETHOETHGaugeSigner, curveFrxEthOethPool } = fixture; + + // Static call to get the OETH removed from the Curve pool for a given amount of LP tokens + const oethBurnAmount = await curveFrxEthOethPool + .connect(curveFrxETHOETHGaugeSigner) + .callStatic["remove_liquidity_one_coin(uint256,int128,uint256)"]( + lpAmount, + 1, + 0 + ); + + log(`OETH burn amount : ${formatUnits(oethBurnAmount)}`); + + return oethBurnAmount; +} + +// Calculate the amount of frxETH burned from a removeOnlyAssets +async function calcEthRemoveAmount(fixture, lpAmount) { + const { curveFrxEthOethPool } = fixture; + + // Get the frxETH removed from the Curve pool for a given amount of LP tokens + const ethRemoveAmount = await curveFrxEthOethPool.calc_withdraw_one_coin( + lpAmount, + 0 + ); + + log(`frxETH burn amount : ${formatUnits(ethRemoveAmount)}`); + + return ethRemoveAmount; +} diff --git a/contracts/test/strategies/oeth-amo-curve-frxeth.js b/contracts/test/strategies/oeth-amo-curve-frxeth.js new file mode 100644 index 0000000000..b5f4923e77 --- /dev/null +++ b/contracts/test/strategies/oeth-amo-curve-frxeth.js @@ -0,0 +1,74 @@ +const { expect } = require("chai"); +const { parseUnits } = require("ethers/lib/utils"); + +const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); +const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); +const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); +const { shouldBehaveLikeAmo } = require("../behaviour/amo"); +const { + convexFrxEthAmoFixture, + createFixtureLoader, +} = require("../fixture/_fixture"); +const { isFork } = require("../helpers"); +const { convex_frxETH_OETH_PID } = require("../../utils/constants"); + +describe("Convex frxETH/OETH AMO Strategy", function () { + if (isFork) { + this.timeout(0); + } else { + this.timeout(600000); + } + + const loadFixture = createFixtureLoader(convexFrxEthAmoFixture); + let fixture; + beforeEach(async function () { + fixture = await loadFixture(); + }); + + shouldBehaveLikeGovernable(() => ({ + ...fixture, + strategy: fixture.convexFrxETHAMOStrategy, + })); + + shouldBehaveLikeHarvestable(() => ({ + ...fixture, + strategy: fixture.convexFrxETHAMOStrategy, + harvester: fixture.oethHarvester, + vault: fixture.oethVault, + dripAsset: fixture.weth, + rewards: [ + { asset: fixture.crv, expected: parseUnits("2") }, + { asset: fixture.cvx, expected: parseUnits("3") }, + ], + })); + + shouldBehaveLikeStrategy(() => ({ + ...fixture, + strategy: fixture.convexFrxETHAMOStrategy, + assets: [fixture.frxETH], + harvester: fixture.oethHarvester, + vault: fixture.oethVault, + })); + + shouldBehaveLikeAmo(() => ({ + ...fixture, + strategy: fixture.convexFrxETHAMOStrategy, + oToken: fixture.oeth, + vaultAsset: fixture.frxETH, + poolAssetAddress: fixture.frxETH.address, + assetIndex: 0, + curvePool: fixture.curveFrxEthOethPool, + vault: fixture.oethVault, + assetDivisor: 1, + convexPID: convex_frxETH_OETH_PID, + })); + + describe("Utilities", function () { + it("Should not initialize a second time", async () => { + const { convexFrxETHAMOStrategy, governor } = fixture; + await expect( + convexFrxETHAMOStrategy.connect(governor).initialize([], [], []) + ).to.revertedWith("Initializable: contract is already initialized"); + }); + }); +}); diff --git a/contracts/test/strategies/oeth-amo-curve-weth.js b/contracts/test/strategies/oeth-amo-curve-weth.js new file mode 100644 index 0000000000..d168ba9197 --- /dev/null +++ b/contracts/test/strategies/oeth-amo-curve-weth.js @@ -0,0 +1,75 @@ +const { expect } = require("chai"); +const { parseUnits } = require("ethers/lib/utils"); + +const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); +const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); +const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); +const { shouldBehaveLikeAmo } = require("../behaviour/amo"); +const { + convexOethEthAmoFixture, + createFixtureLoader, +} = require("../fixture/_fixture"); +const { isFork } = require("../helpers"); +const addresses = require("../../utils/addresses"); +const { convex_OETH_ETH_PID } = require("../../utils/constants"); + +describe("Convex OETH/WETH AMO Strategy", function () { + if (isFork) { + this.timeout(0); + } else { + this.timeout(600000); + } + + const loadFixture = createFixtureLoader(convexOethEthAmoFixture); + let fixture; + beforeEach(async function () { + fixture = await loadFixture(); + }); + + shouldBehaveLikeGovernable(() => ({ + ...fixture, + strategy: fixture.convexEthMetaStrategy, + })); + + shouldBehaveLikeHarvestable(() => ({ + ...fixture, + strategy: fixture.convexEthMetaStrategy, + harvester: fixture.oethHarvester, + vault: fixture.oethVault, + dripAsset: fixture.weth, + rewards: [ + { asset: fixture.crv, expected: parseUnits("2") }, + { asset: fixture.cvx, expected: parseUnits("3") }, + ], + })); + + shouldBehaveLikeStrategy(() => ({ + ...fixture, + strategy: fixture.convexEthMetaStrategy, + assets: [fixture.weth], + harvester: fixture.oethHarvester, + vault: fixture.oethVault, + })); + + shouldBehaveLikeAmo(() => ({ + ...fixture, + strategy: fixture.convexEthMetaStrategy, + oToken: fixture.oeth, + vaultAsset: fixture.weth, + poolAssetAddress: addresses.ETH, + assetIndex: 0, + curvePool: fixture.curveOethEthPool, + vault: fixture.oethVault, + assetDivisor: 1, + convexPID: convex_OETH_ETH_PID, + })); + + describe("Utilities", function () { + it("Should not initialize a second time", async () => { + const { convexEthMetaStrategy, governor } = fixture; + await expect( + convexEthMetaStrategy.connect(governor).initialize([], [], []) + ).to.revertedWith("Initializable: contract is already initialized"); + }); + }); +}); diff --git a/contracts/test/strategies/oeth-morpho-aave.fork-test.js b/contracts/test/strategies/oeth-morpho-aave.fork-test.js index 0ad076add8..90002ea029 100644 --- a/contracts/test/strategies/oeth-morpho-aave.fork-test.js +++ b/contracts/test/strategies/oeth-morpho-aave.fork-test.js @@ -7,7 +7,10 @@ const { advanceTime, isCI, } = require("../helpers"); -const { createFixtureLoader, oethMorphoAaveFixture } = require("../_fixture"); +const { + createFixtureLoader, + oethMorphoAaveFixture, +} = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); describe("ForkTest: Morpho Aave OETH Strategy", function () { diff --git a/contracts/test/strategies/ousd-amo-curve-3crv-tilted.fork-test.js b/contracts/test/strategies/ousd-amo-curve-3crv-tilted.fork-test.js new file mode 100644 index 0000000000..41d080bda6 --- /dev/null +++ b/contracts/test/strategies/ousd-amo-curve-3crv-tilted.fork-test.js @@ -0,0 +1,144 @@ +const { expect } = require("chai"); + +const { units, ousdUnits, isCI } = require("../helpers"); +const { createFixtureLoader } = require("../fixture/_fixture"); +const { + withCRV3TitledOUSDMetapool, +} = require("../fixture/_metastrategies-fixtures"); + +describe("ForkTest: Convex 3Pool/OUSD AMO Strategy - Titled to 3CRV", function () { + this.timeout(0); + + // Retry up to 3 times on CI + this.retries(isCI ? 3 : 0); + + let fixture; + const loadFixture = createFixtureLoader(withCRV3TitledOUSDMetapool); + beforeEach(async () => { + fixture = await loadFixture(); + }); + + describe("Mint", function () { + it("Should stake USDT in Curve gauge via metapool", async function () { + const { josh, usdt } = fixture; + await mintTest(fixture, josh, usdt, "200000"); + }); + + describe("Mint", function () { + it("Should stake USDT in Curve gauge via metapool", async function () { + const { josh, usdt } = fixture; + await mintTest(fixture, josh, usdt, "200000"); + }); + + it("Should stake USDC in Curve gauge via metapool", async function () { + const { matt, usdc } = fixture; + await mintTest(fixture, matt, usdc, "110000"); + }); + + it("Should stake DAI in Curve gauge via metapool", async function () { + const { anna, dai } = fixture; + await mintTest(fixture, anna, dai, "110000"); + }); + + it("Should stake DAI in Curve gauge via metapool", async function () { + const { anna, dai } = fixture; + await mintTest(fixture, anna, dai, "110000"); + }); + }); + + describe("Redeem", function () { + it("Should redeem", async () => { + const { vault, ousd, usdt, usdc, dai, anna, convexOusdAMOStrategy } = + fixture; + + await vault.connect(anna).allocate(); + + const supplyBeforeMint = await ousd.totalSupply(); + + const amount = "10000"; + + // Mint with all three assets + for (const asset of [usdt, usdc, dai]) { + await vault + .connect(anna) + .mint(asset.address, await units(amount, asset), 0); + } + + await vault.connect(anna).allocate(); + + // we multiply it by 3 because 1/3 of balance is represented by each of the assets + const strategyBalance = ( + await convexOusdAMOStrategy.checkBalance(dai.address) + ).mul(3); + + // min 1x 3crv + 1x printed OUSD: (10k + 10k) * (usdt + usdc) = 40k + await expect(strategyBalance).to.be.gte(ousdUnits("40000")); + + // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k + const currentSupply = await ousd.totalSupply(); + const supplyAdded = currentSupply.sub(supplyBeforeMint); + expect(supplyAdded).to.be.gte(ousdUnits("49999")); + + const currentBalance = await ousd.connect(anna).balanceOf(anna.address); + + // Now try to redeem the amount + await vault.connect(anna).redeem(ousdUnits("29900"), 0); + + // User balance should be down by 30k + const newBalance = await ousd.connect(anna).balanceOf(anna.address); + expect(newBalance).to.approxEqualTolerance( + currentBalance.sub(ousdUnits("29900")), + 1 + ); + + const newSupply = await ousd.totalSupply(); + const supplyDiff = currentSupply.sub(newSupply); + + expect(supplyDiff).to.be.gte(ousdUnits("29900")); + }); + }); + }); +}); + +async function mintTest(fixture, user, asset, amount = "30000") { + const { vault, ousd, convexOusdAMOStrategy, cvxRewardPool } = fixture; + + await vault.connect(user).allocate(); + await vault.connect(user).rebase(); + + const unitAmount = await units(amount, asset); + + const currentSupply = await ousd.totalSupply(); + const currentBalance = await ousd.connect(user).balanceOf(user.address); + const currentRewardPoolBalance = await cvxRewardPool + .connect(user) + .balanceOf(convexOusdAMOStrategy.address); + + // Mint OUSD w/ asset + await vault.connect(user).mint(asset.address, unitAmount, 0); + await vault.connect(user).allocate(); + + // Ensure user has correct balance (w/ 1% slippage tolerance) + const newBalance = await ousd.connect(user).balanceOf(user.address); + const balanceDiff = newBalance.sub(currentBalance); + expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); + + // Supply checks + const newSupply = await ousd.totalSupply(); + const supplyDiff = newSupply.sub(currentSupply); + const ousdUnitAmount = ousdUnits(amount); + + // The pool is titled to 3CRV by a million + // It should have added amount*3 supply + expect(supplyDiff).to.approxEqualTolerance(ousdUnitAmount.mul(3), 5); + + // Ensure some LP tokens got staked under convexOusdAMOStrategy address + const newRewardPoolBalance = await cvxRewardPool + .connect(user) + .balanceOf(convexOusdAMOStrategy.address); + const rewardPoolBalanceDiff = newRewardPoolBalance.sub( + currentRewardPoolBalance + ); + // Should have staked the LP tokens for USDT and USDC + expect(rewardPoolBalanceDiff).to.be.gte(ousdUnits(amount).mul(3).div(2)); +} diff --git a/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js b/contracts/test/strategies/ousd-amo-curve-balanced.fork-test.js similarity index 57% rename from contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js rename to contracts/test/strategies/ousd-amo-curve-balanced.fork-test.js index 1cfac51a8f..eca2834cf0 100644 --- a/contracts/test/strategies/ousd-metapool-balanced-pool.fork-test.js +++ b/contracts/test/strategies/ousd-amo-curve-balanced.fork-test.js @@ -2,12 +2,14 @@ const { expect } = require("chai"); const { run } = require("hardhat"); const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withBalancedOUSDMetaPool } = require("../_metastrategies-fixtures"); +const { createFixtureLoader } = require("../fixture/_fixture"); +const { + withBalancedOUSDMetaPool, +} = require("../fixture/_metastrategies-fixtures"); const log = require("../../utils/logger")("test:fork:ousd:metapool"); -describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", function () { +describe("ForkTest: Convex 3Pool/OUSD AMO Strategy - Balanced", function () { this.timeout(0); // Retry up to 3 times on CI @@ -30,15 +32,81 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi await mintTest(fixture, matt, usdc, "120000"); }); - it("Should stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); + describe("Redeem", function () { + it("Should redeem", async () => { + const { vault, ousd, usdt, usdc, dai, anna, convexOusdAMOStrategy } = + fixture; + + await vault.connect(anna).allocate(); + + const supplyBeforeMint = await ousd.totalSupply(); + + const amount = "10000"; + + const beforeMintBlock = await ethers.provider.getBlockNumber(); + + // Mint with all three assets + for (const asset of [usdt, usdc, dai]) { + await vault + .connect(anna) + .mint(asset.address, await units(amount, asset), 0); + } + + await vault.connect(anna).allocate(); + + log("After mints and allocate to strategy"); + await run("amoStrat", { + pool: "OUSD", + output: false, + fromBlock: beforeMintBlock, + }); + + const afterMintBlock = await ethers.provider.getBlockNumber(); + + // we multiply it by 3 because 1/3 of balance is represented by each of the assets + const strategyBalance = ( + await convexOusdAMOStrategy.checkBalance(dai.address) + ).mul(3); + + // 3x 10k assets + 3x 10k OUSD = 60k + await expect(strategyBalance).to.be.gte(ousdUnits("59990")); + + // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k + const currentSupply = await ousd.totalSupply(); + const supplyAdded = currentSupply.sub(supplyBeforeMint); + expect(supplyAdded).to.be.gte(ousdUnits("49995")); + + const currentBalance = await ousd.connect(anna).balanceOf(anna.address); + + // Now try to redeem the amount + const redeemAmount = ousdUnits("29990"); + await vault.connect(anna).redeem(redeemAmount, 0); + + log("After redeem"); + await run("amoStrat", { + pool: "OUSD", + output: false, + fromBlock: afterMintBlock, + }); + + // User balance should be down by 30k + const newBalance = await ousd.connect(anna).balanceOf(anna.address); + expect(newBalance).to.approxEqualTolerance( + currentBalance.sub(redeemAmount), + 1 + ); + + const newSupply = await ousd.totalSupply(); + const supplyDiff = currentSupply.sub(newSupply); + expect(supplyDiff).to.be.gte(redeemAmount); + }); }); }); describe("Redeem", function () { it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, dai, anna, OUSDmetaStrategy } = fixture; + const { vault, ousd, usdt, usdc, dai, anna, convexOusdAMOStrategy } = + fixture; await vault.connect(anna).allocate(); @@ -66,7 +134,7 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi // we multiply it by 3 because 1/3 of balance is represented by each of the assets const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(dai.address) + await convexOusdAMOStrategy.checkBalance(dai.address) ).mul(3); // 3x 10k assets + 3x 10k OUSD = 60k @@ -99,7 +167,7 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Balanced Metapool", functi }); async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; + const { vault, ousd, convexOusdAMOStrategy, cvxRewardPool } = fixture; await vault.connect(user).allocate(); await vault.connect(user).rebase(); @@ -110,7 +178,7 @@ async function mintTest(fixture, user, asset, amount = "30000") { const currentBalance = await ousd.connect(user).balanceOf(user.address); const currentRewardPoolBalance = await cvxRewardPool .connect(user) - .balanceOf(OUSDmetaStrategy.address); + .balanceOf(convexOusdAMOStrategy.address); // Mint OUSD w/ asset await vault.connect(user).mint(asset.address, unitAmount, 0); @@ -127,10 +195,10 @@ async function mintTest(fixture, user, asset, amount = "30000") { // Ensure 2x OUSD has been added to supply expect(supplyDiff).to.approxEqualTolerance(ousdUnits(amount).mul(2), 1); - // Ensure some LP tokens got staked under OUSDMetaStrategy address + // Ensure some LP tokens got staked under convexOusdAMOStrategy address const newRewardPoolBalance = await cvxRewardPool .connect(user) - .balanceOf(OUSDmetaStrategy.address); + .balanceOf(convexOusdAMOStrategy.address); const rewardPoolBalanceDiff = newRewardPoolBalance.sub( currentRewardPoolBalance ); diff --git a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js b/contracts/test/strategies/ousd-amo-curve-ousd-tilted.fork-test.js similarity index 83% rename from contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js rename to contracts/test/strategies/ousd-amo-curve-ousd-tilted.fork-test.js index ad51913ae8..d69650a59d 100644 --- a/contracts/test/strategies/ousd-metapool-ousd-tilted-pool.fork-test.js +++ b/contracts/test/strategies/ousd-amo-curve-ousd-tilted.fork-test.js @@ -1,10 +1,12 @@ const { expect } = require("chai"); const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withOUSDTitledMetapool } = require("../_metastrategies-fixtures"); +const { createFixtureLoader } = require("../fixture/_fixture"); +const { + withOUSDTitledMetapool, +} = require("../fixture/_metastrategies-fixtures"); -describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function () { +describe("ForkTest: Convex 3Pool/OUSD AMO Strategy - Titled to OUSD", function () { this.timeout(0); // Retry up to 3 times on CI @@ -35,13 +37,14 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function describe("Redeem", function () { it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, dai, anna, OUSDmetaStrategy } = fixture; + const { vault, ousd, usdt, usdc, dai, anna, convexOusdAMOStrategy } = + fixture; await vault.connect(anna).allocate(); const supplyBeforeMint = await ousd.totalSupply(); const strategyBalanceBeforeMint = ( - await OUSDmetaStrategy.checkBalance(dai.address) + await convexOusdAMOStrategy.checkBalance(dai.address) ).mul(3); const amount = "10000"; @@ -57,19 +60,19 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function // we multiply it by 3 because 1/3 of balance is represented by each of the assets const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(dai.address) + await convexOusdAMOStrategy.checkBalance(dai.address) ).mul(3); const strategyBalanceChange = strategyBalance.sub( strategyBalanceBeforeMint ); // min 1x 3crv + 1x printed OUSD: (10k + 10k + 10k) * (usdt + usdc + dai) = 60k - expect(strategyBalanceChange).to.be.gte(ousdUnits("59500")); + expect(strategyBalanceChange).to.be.gte(ousdUnits("59000")); // Total supply should be up by at least (10k x 2) + (10k x 2) + (10k x 2) = 60k const currentSupply = await ousd.totalSupply(); const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("59500")); + expect(supplyAdded).to.be.gte(ousdUnits("59000")); const currentBalance = await ousd.connect(anna).balanceOf(anna.address); @@ -93,7 +96,7 @@ describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to OUSD", function }); async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; + const { vault, ousd, convexOusdAMOStrategy, cvxRewardPool } = fixture; await vault.connect(user).allocate(); await vault.connect(user).rebase(); @@ -103,7 +106,7 @@ async function mintTest(fixture, user, asset, amount = "30000") { const currentBalance = await ousd.connect(user).balanceOf(user.address); const currentRewardPoolBalance = await cvxRewardPool .connect(user) - .balanceOf(OUSDmetaStrategy.address); + .balanceOf(convexOusdAMOStrategy.address); // Mint OUSD w/ asset await vault.connect(user).mint(asset.address, unitAmount, 0); @@ -123,10 +126,10 @@ async function mintTest(fixture, user, asset, amount = "30000") { // 1x for 3poolLp tokens and 1x for minimum amount of OUSD printed expect(supplyDiff).to.approxEqualTolerance(ousdUnits(amount).mul(2), 5); - // Ensure some LP tokens got staked under OUSDMetaStrategy address + // Ensure some LP tokens got staked under convexOusdAMOStrategy address const newRewardPoolBalance = await cvxRewardPool .connect(user) - .balanceOf(OUSDmetaStrategy.address); + .balanceOf(convexOusdAMOStrategy.address); const rewardPoolBalanceDiff = newRewardPoolBalance.sub( currentRewardPoolBalance ); diff --git a/contracts/test/strategies/ousd-amo-curve.js b/contracts/test/strategies/ousd-amo-curve.js new file mode 100644 index 0000000000..2c41ca47d2 --- /dev/null +++ b/contracts/test/strategies/ousd-amo-curve.js @@ -0,0 +1,254 @@ +const { expect } = require("chai"); +const { BigNumber } = require("ethers"); +const { parseUnits } = require("ethers/lib/utils"); + +const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); +const { shouldBehaveLikeHarvestable } = require("../behaviour/harvestable"); +const { shouldBehaveLikeStrategy } = require("../behaviour/strategy"); +const { shouldBehaveLikeAmo } = require("../behaviour/amo"); +const { + convexOusdAmoFixture, + createFixtureLoader, +} = require("../fixture/_fixture"); +const { + daiUnits, + ousdUnits, + units, + expectApproxSupply, + isFork, + usdtUnits, +} = require("../helpers"); +const { resolveAsset } = require("../../utils/assets"); +const { convex_OUSD_3CRV_PID } = require("../../utils/constants"); + +describe("Convex OUSD/3Pool AMO Strategy", function () { + if (isFork) { + this.timeout(0); + } else { + this.timeout(600000); + } + + const mint = async (amount, asset) => { + const { anna, vault } = fixture; + await asset.connect(anna).mint(await units(amount, asset)); + await asset + .connect(anna) + .approve(vault.address, await units(amount, asset)); + return await vault + .connect(anna) + .mint(asset.address, await units(amount, asset), 0); + }; + + const loadFixture = createFixtureLoader(convexOusdAmoFixture); + let fixture; + beforeEach(async function () { + fixture = await loadFixture(); + }); + + shouldBehaveLikeGovernable(() => ({ + ...fixture, + strategy: fixture.convexOusdAMOStrategy, + })); + + shouldBehaveLikeHarvestable(() => ({ + ...fixture, + strategy: fixture.convexOusdAMOStrategy, + dripAsset: fixture.usdt, + rewards: [ + { asset: fixture.crv, expected: parseUnits("2") }, + { asset: fixture.cvx, expected: parseUnits("3") }, + ], + })); + + shouldBehaveLikeStrategy(() => ({ + ...fixture, + strategy: fixture.convexOusdAMOStrategy, + assets: [fixture.dai, fixture.usdc, fixture.usdt], + harvester: fixture.harvester, + vault: fixture.vault, + })); + + shouldBehaveLikeAmo(() => ({ + ...fixture, + strategy: fixture.convexOusdAMOStrategy, + oToken: fixture.ousd, + vaultAsset: fixture.usdt, + poolAssetAddress: fixture.threePoolToken.address, + assetIndex: 1, + curvePool: fixture.curveOusd3CrvMetapool, + vault: fixture.vault, + assetDivisor: 3, + convexPID: convex_OUSD_3CRV_PID, + })); + + describe("Mint", function () { + ["DAI", "USDC", "USDT"].forEach((symbol) => { + it(`Should deposit ${symbol} to strategy`, async function () { + const { + anna, + cvxBooster, + ousd, + metapoolToken, + convexOusdAMOStrategy, + vaultSigner, + } = fixture; + await expectApproxSupply(ousd, ousdUnits("200")); + + const asset = await resolveAsset(symbol); + const depositAmount = await units("30000.00", asset); + await asset.connect(anna).mint(depositAmount); + await asset + .connect(anna) + .transfer(convexOusdAMOStrategy.address, depositAmount); + + // deposit asset to AMO + // prettier-ignore + const tx = await convexOusdAMOStrategy + .connect(vaultSigner)["deposit(address,uint256)"](asset.address, depositAmount); + // emit Deposit event for asset + await expect(tx) + .to.emit(convexOusdAMOStrategy, "Deposit") + .withArgs(asset.address, metapoolToken.address, depositAmount); + // emit Deposit event for OUSD + await expect(tx) + .to.emit(convexOusdAMOStrategy, "Deposit") + .withNamedArgs({ + _asset: ousd.address, + _pToken: metapoolToken.address, + // _amount: depositAmount, + }); + + await expect(cvxBooster).has.an.approxBalanceOf("60000", metapoolToken); + }); + }); + it(`Should deposit multiple assets to strategy`, async function () { + const { + anna, + cvxBooster, + dai, + ousd, + usdt, + metapoolToken, + convexOusdAMOStrategy, + vaultSigner, + } = fixture; + await expectApproxSupply(ousd, ousdUnits("200")); + + const daiAmount = await daiUnits("30000.00"); + await dai.connect(anna).mint(daiAmount); + await dai + .connect(anna) + .transfer(convexOusdAMOStrategy.address, daiAmount); + + const usdtAmount = await usdtUnits("30000.00"); + await usdt.connect(anna).mint(usdtAmount); + await usdt + .connect(anna) + .transfer(convexOusdAMOStrategy.address, usdtAmount); + + // deposit USDT to AMO + // prettier-ignore + const tx = await convexOusdAMOStrategy + .connect(vaultSigner)["deposit(address[],uint256[])"]( + [dai.address, usdt.address], + [daiAmount, usdtAmount] + ); + // emit Deposit event for DAI + await expect(tx) + .to.emit(convexOusdAMOStrategy, "Deposit") + .withArgs(dai.address, metapoolToken.address, daiAmount); + // emit Deposit event for USDT + await expect(tx) + .to.emit(convexOusdAMOStrategy, "Deposit") + .withArgs(usdt.address, metapoolToken.address, usdtAmount); + // emit Deposit event for OUSD + await expect(tx).to.emit(convexOusdAMOStrategy, "Deposit").withNamedArgs({ + _asset: ousd.address, + _pToken: metapoolToken.address, + // _amount: depositAmount, + }); + + await expect(cvxBooster).has.an.approxBalanceOf("120000", metapoolToken); + }); + + it("Should use a minimum LP token amount when depositing USDT into metapool", async function () { + const { usdt } = fixture; + await expect(mint("29000", usdt)).to.be.revertedWith( + "Slippage ruined your day" + ); + }); + + it("Should use a minimum LP token amount when depositing USDC into metapool", async function () { + const { usdc } = fixture; + await expect(mint("29000", usdc)).to.be.revertedWith( + "Slippage ruined your day" + ); + }); + }); + + describe("Redeem", function () { + it("Should be able to unstake from gauge and return USDT", async function () { + const { anna, dai, ousd, usdc, usdt, vault } = fixture; + await expectApproxSupply(ousd, ousdUnits("200")); + await mint("10000.00", dai); + await mint("10000.00", usdc); + await mint("10000.00", usdt); + await vault.connect(anna).redeem(ousdUnits("20000"), 0); + // DAI minted OUSD has not been deployed to OUSD AMO strategy for that reason the + // total supply of OUSD has not doubled + await expectApproxSupply(ousd, ousdUnits("10200")); + }); + }); + + describe("Utilities", function () { + const MAX_UINT16 = BigNumber.from( + "0x0000000000000000000000000000000000000000ffffffffffffffffffffffff" + ); + it("Should not initialize a second time", async () => { + const { convexOusdAMOStrategy, governor } = fixture; + await expect( + convexOusdAMOStrategy.connect(governor).initialize([], [], []) + ).to.revertedWith("Initializable: contract is already initialized"); + }); + + it("Should not allow too large mintForStrategy", async () => { + const { anna, governor, vault } = fixture; + await vault.connect(governor).setAMOStrategy(anna.address, true); + + await expect( + vault.connect(anna).mintForStrategy(MAX_UINT16) + ).to.be.revertedWith("value doesn't fit in 96 bits"); + + await expect( + vault.connect(anna).mintForStrategy(MAX_UINT16.div(2)) + ).to.be.revertedWith("OToken mint passes threshold"); + }); + + it("Should not allow too large burnForStrategy", async () => { + const { anna, governor, vault } = fixture; + await vault.connect(governor).setAMOStrategy(anna.address, true); + + await expect( + vault.connect(anna).burnForStrategy(MAX_UINT16) + ).to.be.revertedWith("value doesn't fit in 96 bits"); + + await expect( + vault.connect(anna).burnForStrategy(MAX_UINT16.div(2)) + ).to.be.revertedWith("OToken burn passes threshold"); + }); + + it("Should allow Governor to reset allowances", async () => { + const { convexOusdAMOStrategy, governor } = fixture; + await expect( + convexOusdAMOStrategy.connect(governor).safeApproveAllTokens() + ).to.not.be.reverted; + }); + + it("Should not allow non-Governor to reset allowances", async () => { + const { anna, convexOusdAMOStrategy } = fixture; + await expect( + convexOusdAMOStrategy.connect(anna).safeApproveAllTokens() + ).to.be.revertedWith("Caller is not the Governor"); + }); + }); +}); diff --git a/contracts/test/strategies/ousd-maker-dsr.fork-test.js b/contracts/test/strategies/ousd-maker-dsr.fork-test.js index 382025052c..eeb3af4eda 100644 --- a/contracts/test/strategies/ousd-maker-dsr.fork-test.js +++ b/contracts/test/strategies/ousd-maker-dsr.fork-test.js @@ -4,7 +4,7 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); const addresses = require("../../utils/addresses"); const { units, isCI } = require("../helpers"); -const { createFixtureLoader, makerDsrFixture } = require("../_fixture"); +const { createFixtureLoader, makerDsrFixture } = require("../fixture/_fixture"); const log = require("../../utils/logger")("test:fork:ousd:makerDSR"); diff --git a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js b/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js deleted file mode 100644 index 01c8de77e3..0000000000 --- a/contracts/test/strategies/ousd-metapool-3crv-tilted-pool.fork-test.js +++ /dev/null @@ -1,129 +0,0 @@ -const { expect } = require("chai"); - -const { units, ousdUnits, isCI } = require("../helpers"); -const { createFixtureLoader } = require("../_fixture"); -const { withCRV3TitledOUSDMetapool } = require("../_metastrategies-fixtures"); - -describe("ForkTest: Convex 3pool/OUSD Meta Strategy - Titled to 3CRV", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - const loadFixture = createFixtureLoader(withCRV3TitledOUSDMetapool); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - describe("Mint", function () { - it("Should stake USDT in Curve gauge via metapool", async function () { - const { josh, usdt } = fixture; - await mintTest(fixture, josh, usdt, "200000"); - }); - - it("Should stake USDC in Curve gauge via metapool", async function () { - const { matt, usdc } = fixture; - await mintTest(fixture, matt, usdc, "110000"); - }); - - it("Should stake DAI in Curve gauge via metapool", async function () { - const { anna, dai } = fixture; - await mintTest(fixture, anna, dai, "110000"); - }); - }); - - describe("Redeem", function () { - it("Should redeem", async () => { - const { vault, ousd, usdt, usdc, dai, anna, OUSDmetaStrategy } = fixture; - - await vault.connect(anna).allocate(); - - const supplyBeforeMint = await ousd.totalSupply(); - - const amount = "10000"; - - // Mint with all three assets - for (const asset of [usdt, usdc, dai]) { - await vault - .connect(anna) - .mint(asset.address, await units(amount, asset), 0); - } - - await vault.connect(anna).allocate(); - - // we multiply it by 3 because 1/3 of balance is represented by each of the assets - const strategyBalance = ( - await OUSDmetaStrategy.checkBalance(dai.address) - ).mul(3); - - // min 1x 3crv + 1x printed OUSD: (10k + 10k) * (usdt + usdc) = 40k - await expect(strategyBalance).to.be.gte(ousdUnits("40000")); - - // Total supply should be up by at least (10k x 2) + (10k x 2) + 10k = 50k - const currentSupply = await ousd.totalSupply(); - const supplyAdded = currentSupply.sub(supplyBeforeMint); - expect(supplyAdded).to.be.gte(ousdUnits("49999")); - - const currentBalance = await ousd.connect(anna).balanceOf(anna.address); - - // Now try to redeem the amount - await vault.connect(anna).redeem(ousdUnits("29900"), 0); - - // User balance should be down by 30k - const newBalance = await ousd.connect(anna).balanceOf(anna.address); - expect(newBalance).to.approxEqualTolerance( - currentBalance.sub(ousdUnits("29900")), - 1 - ); - - const newSupply = await ousd.totalSupply(); - const supplyDiff = currentSupply.sub(newSupply); - - expect(supplyDiff).to.be.gte(ousdUnits("29900")); - }); - }); -}); - -async function mintTest(fixture, user, asset, amount = "30000") { - const { vault, ousd, OUSDmetaStrategy, cvxRewardPool } = fixture; - - await vault.connect(user).allocate(); - await vault.connect(user).rebase(); - - const unitAmount = await units(amount, asset); - - const currentSupply = await ousd.totalSupply(); - const currentBalance = await ousd.connect(user).balanceOf(user.address); - const currentRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - - // Mint OUSD w/ asset - await vault.connect(user).mint(asset.address, unitAmount, 0); - await vault.connect(user).allocate(); - - // Ensure user has correct balance (w/ 1% slippage tolerance) - const newBalance = await ousd.connect(user).balanceOf(user.address); - const balanceDiff = newBalance.sub(currentBalance); - expect(balanceDiff).to.approxEqualTolerance(ousdUnits(amount), 2); - - // Supply checks - const newSupply = await ousd.totalSupply(); - const supplyDiff = newSupply.sub(currentSupply); - const ousdUnitAmount = ousdUnits(amount); - - // The pool is titled to 3CRV by a million - // It should have added amount*3 supply - expect(supplyDiff).to.approxEqualTolerance(ousdUnitAmount.mul(3), 5); - - // Ensure some LP tokens got staked under OUSDMetaStrategy address - const newRewardPoolBalance = await cvxRewardPool - .connect(user) - .balanceOf(OUSDmetaStrategy.address); - const rewardPoolBalanceDiff = newRewardPoolBalance.sub( - currentRewardPoolBalance - ); - // Should have staked the LP tokens for USDT and USDC - expect(rewardPoolBalanceDiff).to.be.gte(ousdUnits(amount).mul(3).div(2)); -} diff --git a/contracts/test/strategies/vault-value-checker.js b/contracts/test/strategies/vault-value-checker.js index 7a3ef4a6f4..430d3540f4 100644 --- a/contracts/test/strategies/vault-value-checker.js +++ b/contracts/test/strategies/vault-value-checker.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { impersonateAndFund } = require("../../utils/signers"); describe("Check vault value", () => { diff --git a/contracts/test/timelock.js b/contracts/test/timelock.js index 2d470c9e62..59f2f2ee13 100644 --- a/contracts/test/timelock.js +++ b/contracts/test/timelock.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("./_fixture"); +const { loadDefaultFixture } = require("./fixture/_fixture"); const { isFork, advanceTime, diff --git a/contracts/test/token/ousd.js b/contracts/test/token/ousd.js index a236f5f197..c5638913b3 100644 --- a/contracts/test/token/ousd.js +++ b/contracts/test/token/ousd.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { utils } = require("ethers"); const { daiUnits, ousdUnits, usdcUnits, isFork } = require("../helpers"); diff --git a/contracts/test/token/wousd.js b/contracts/test/token/wousd.js index 7e02b9a58a..6661c3e123 100644 --- a/contracts/test/token/wousd.js +++ b/contracts/test/token/wousd.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, daiUnits, isFork } = require("../helpers"); describe("WOUSD", function () { diff --git a/contracts/test/vault/collateral-swaps.fork-test.js b/contracts/test/vault/collateral-swaps.fork-test.js index 3be93f3758..0b3dcbefb7 100644 --- a/contracts/test/vault/collateral-swaps.fork-test.js +++ b/contracts/test/vault/collateral-swaps.fork-test.js @@ -7,7 +7,7 @@ const { oethDefaultFixture, oethCollateralSwapFixture, ousdCollateralSwapFixture, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const { getIInchSwapData, recodeSwapData } = require("../../utils/1Inch"); const { decimalsFor, isCI } = require("../helpers"); const { resolveAsset } = require("../../utils/assets"); diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index 898edf3f38..2200638922 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -1,7 +1,10 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { createFixtureLoader, compoundVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + compoundVaultFixture, +} = require("../fixture/_fixture"); const { advanceTime, @@ -26,11 +29,6 @@ describe("Vault with Compound strategy", function () { fixture = await loadFixture(); }); - it("Anyone can call safeApproveAllTokens", async () => { - const { matt, compoundStrategy } = fixture; - await compoundStrategy.connect(matt).safeApproveAllTokens(); - }); - it("Governor can call removePToken", async () => { const { governor, compoundStrategy } = fixture; diff --git a/contracts/test/vault/deposit.js b/contracts/test/vault/deposit.js index 20c868be39..0795ee188c 100644 --- a/contracts/test/vault/deposit.js +++ b/contracts/test/vault/deposit.js @@ -1,6 +1,9 @@ const { expect } = require("chai"); -const { createFixtureLoader, compoundVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + compoundVaultFixture, +} = require("../fixture/_fixture"); const { usdcUnits, isFork } = require("../helpers"); describe("Vault deposit pausing", function () { diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js index 56bae820ed..40031f5fa1 100644 --- a/contracts/test/vault/exchangeRate.js +++ b/contracts/test/vault/exchangeRate.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, daiUnits, diff --git a/contracts/test/vault/harvester.fork-test.js b/contracts/test/vault/harvester.fork-test.js index bdf28ca0ff..e89ac8441a 100644 --- a/contracts/test/vault/harvester.fork-test.js +++ b/contracts/test/vault/harvester.fork-test.js @@ -1,8 +1,11 @@ const { expect } = require("chai"); const { utils, BigNumber } = require("ethers"); -const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); -const { isCI, oethUnits } = require("./../helpers"); +const { + createFixtureLoader, + harvesterFixture, +} = require("../fixture/_fixture"); +const { isCI, oethUnits } = require("../helpers"); const { hotDeployOption } = require("../_hot-deploy"); const addresses = require("../../utils/addresses"); const { setERC20TokenBalance } = require("../_fund"); diff --git a/contracts/test/vault/harvester.js b/contracts/test/vault/harvester.js index aecda3bb4d..cb3f42d2f4 100644 --- a/contracts/test/vault/harvester.js +++ b/contracts/test/vault/harvester.js @@ -1,4 +1,7 @@ -const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); +const { + createFixtureLoader, + harvesterFixture, +} = require("../fixture/_fixture"); const { shouldBehaveLikeHarvester } = require("../behaviour/harvester"); const loadFixture = createFixtureLoader(harvesterFixture); diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 816674198b..bf1c8bcc00 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const hre = require("hardhat"); const { utils } = require("ethers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, daiUnits, @@ -587,55 +587,51 @@ describe("Vault", function () { ).to.be.revertedWith("Caller is not the Strategist or Governor"); }); - it("Should only allow metastrategy to mint oTokens and revert when threshold is reached.", async () => { + it("Should only allow AMO to mint oTokens and revert when threshold is reached.", async () => { const { vault, ousd, governor, anna, josh } = fixture; + // Approve anna address as an address allowed to mint OUSD without backing + await vault.connect(governor).setAMOStrategy(anna.address, true); await vault .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); + .setMintForStrategyThreshold(anna.address, ousdUnits("10")); await expect( vault.connect(anna).mintForStrategy(ousdUnits("11")) - ).to.be.revertedWith( - "Minted ousd surpassed netOusdMintForStrategyThreshold." - ); + ).to.be.revertedWith("OToken mint passes threshold"); await expect( vault.connect(josh).mintForStrategy(ousdUnits("9")) - ).to.be.revertedWith("Caller is not the OUSD meta strategy"); + ).to.be.revertedWith("Caller is not an AMO strategy"); await vault.connect(anna).mintForStrategy(ousdUnits("9")); await expect(await ousd.balanceOf(anna.address)).to.equal(ousdUnits("9")); }); - it("Should reset netOusdMintedForStrategy when new threshold is set", async () => { + it("Should reset mintForStrategy when new threshold is set", async () => { const { vault, governor, anna } = fixture; + // Approve anna address as an address allowed to mint OUSD without backing + await vault.connect(governor).setAMOStrategy(anna.address, true); await vault .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); + .setMintForStrategyThreshold(anna.address, ousdUnits("10")); await vault.connect(anna).mintForStrategy(ousdUnits("9")); - // netOusdMintedForStrategy should be equal to amount minted - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("9") - ); + // mintForStrategy should be equal to amount minted + let strategyConfig = await vault.getStrategyConfig(anna.address); + await expect(strategyConfig.mintForStrategy).to.equal(ousdUnits("9")); await vault .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); + .setMintForStrategyThreshold(anna.address, ousdUnits("10")); - // netOusdMintedForStrategy should be reset back to 0 - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("0") - ); + // mintForStrategy should be reset back to 0 + strategyConfig = await vault.getStrategyConfig(anna.address); + await expect(strategyConfig.mintForStrategy).to.equal(ousdUnits("0")); }); + it("Should re-cache decimals", async () => { const { vault, governor, usdc } = fixture; diff --git a/contracts/test/vault/oeth-vault.fork-test.js b/contracts/test/vault/oeth-vault.fork-test.js index 6f7571dd90..b99f99eafc 100644 --- a/contracts/test/vault/oeth-vault.fork-test.js +++ b/contracts/test/vault/oeth-vault.fork-test.js @@ -2,7 +2,10 @@ const { expect } = require("chai"); const { formatUnits, parseUnits } = require("ethers/lib/utils"); const addresses = require("../../utils/addresses"); -const { createFixtureLoader, oethDefaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + oethDefaultFixture, +} = require("../fixture/_fixture"); const { isCI, oethUnits } = require("../helpers"); const { impersonateAndFund } = require("../../utils/signers"); const { diff --git a/contracts/test/vault/oneinch-swapper.js b/contracts/test/vault/oneinch-swapper.js index d1b7a483f7..09bb88fdaf 100644 --- a/contracts/test/vault/oneinch-swapper.js +++ b/contracts/test/vault/oneinch-swapper.js @@ -7,7 +7,7 @@ const { oethCollateralSwapFixture, ousdCollateralSwapFixture, oeth1InchSwapperFixture, -} = require("../_fixture"); +} = require("../fixture/_fixture"); const { SWAP_SELECTOR, UNISWAP_SELECTOR, diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 373fffcb9e..8f8c753823 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, daiUnits, diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index 78e44366bf..a10c32e049 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { BigNumber } = require("ethers"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, diff --git a/contracts/test/vault/upgrade.js b/contracts/test/vault/upgrade.js index 16a43f992b..3e13bf5e27 100644 --- a/contracts/test/vault/upgrade.js +++ b/contracts/test/vault/upgrade.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const { loadDefaultFixture } = require("../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); describe("VaultAdmin Upgrades", async function () { let ousd, vault, vaultStorage, governor; diff --git a/contracts/test/vault/vault.fork-test.js b/contracts/test/vault/vault.fork-test.js index 581df18ca1..eaca034e4e 100644 --- a/contracts/test/vault/vault.fork-test.js +++ b/contracts/test/vault/vault.fork-test.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const { utils } = require("ethers"); const addresses = require("../../utils/addresses"); -const { loadDefaultFixture } = require("./../_fixture"); +const { loadDefaultFixture } = require("../fixture/_fixture"); const { ousdUnits, usdtUnits, @@ -77,11 +77,17 @@ describe("ForkTest: Vault", function () { ); }); - it("Should have the correct OUSD MetaStrategy address set", async () => { + it("Should have AMO strategy configured in the vault", async () => { const { vault } = fixture; - expect(await vault.ousdMetaStrategy()).to.equal( + const config = await vault.getStrategyConfig( addresses.mainnet.ConvexOUSDAMOStrategy ); + expect(config.isSupported).to.be.true; + expect(config.isAMO).to.be.true; + expect(config.mintForStrategy).to.eq(0); + expect(config.mintForStrategyThreshold).to.be.eq( + utils.parseUnits("50", 24) + ); }); it("Should have supported assets", async () => { diff --git a/contracts/test/vault/z_mockvault.js b/contracts/test/vault/z_mockvault.js index 7a95bf5583..93df82a1da 100644 --- a/contracts/test/vault/z_mockvault.js +++ b/contracts/test/vault/z_mockvault.js @@ -1,7 +1,10 @@ const { expect } = require("chai"); const { utils } = require("ethers"); -const { createFixtureLoader, mockVaultFixture } = require("../_fixture"); +const { + createFixtureLoader, + mockVaultFixture, +} = require("../fixture/_fixture"); describe("Vault mock with rebase", async () => { let mockVault, matt, ousd, josh, governor; diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index acab5b4184..673831cf9c 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -57,6 +57,8 @@ addresses.mainnet.ThreePoolGauge = "0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A"; // CVX addresses.mainnet.CVX = "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B"; addresses.mainnet.CVXBooster = "0xF403C135812408BFbE8713b5A23a04b3D48AAE31"; +addresses.mainnet.ConvexPoolManager = + "0xc461E1CE3795Ee30bA2EC59843d5fAe14d5782D5"; addresses.mainnet.CVXRewardsPool = "0x7D536a737C13561e0D2Decf1152a653B4e615158"; addresses.mainnet.CVXLocker = "0x72a19342e8F1838460eBFCCEf09F6585e32db86E"; @@ -134,6 +136,10 @@ addresses.mainnet.CompoundStrategyProxy = "0x12115A32a19e4994C2BA4A5437C22CEf5ABb59C3"; addresses.mainnet.CompoundStrategy = "0xFaf23Bd848126521064184282e8AD344490BA6f0"; + +// Curve +addresses.mainnet.CurveGaugeController = + "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB"; addresses.mainnet.CurveUSDCStrategyProxy = "0x67023c56548BA15aD3542E65493311F19aDFdd6d"; addresses.mainnet.CurveUSDCStrategy = @@ -142,15 +148,18 @@ addresses.mainnet.CurveUSDTStrategyProxy = "0xe40e09cD6725E542001FcB900d9dfeA447B529C0"; addresses.mainnet.CurveUSDTStrategy = "0x75Bc09f72db1663Ed35925B89De2b5212b9b6Cb3"; -addresses.mainnet.CurveOUSDMetaPool = - "0x87650D7bbfC3A9F10587d7778206671719d9910D"; addresses.mainnet.CurveLUSDMetaPool = "0x7A192DD9Cc4Ea9bdEdeC9992df74F1DA55e60a19"; + +// OUSD AMO for Curve OUSD/3CRV pool +addresses.mainnet.CurveOUSDMetaPool = + "0x87650D7bbfC3A9F10587d7778206671719d9910D"; addresses.mainnet.ConvexOUSDAMOStrategy = "0x89Eb88fEdc50FC77ae8a18aAD1cA0ac27f777a90"; addresses.mainnet.CurveOUSDGauge = "0x25f0cE4E2F8dbA112D9b115710AC297F816087CD"; +addresses.mainnet.CVXRewardsPool = "0x7D536a737C13561e0D2Decf1152a653B4e615158"; -// Curve OETH/ETH pool +// OETH AMO for Curve OETH/ETH pool addresses.mainnet.ConvexOETHAMOStrategy = "0x1827F9eA98E0bf96550b2FC20F7233277FcD7E63"; addresses.mainnet.CurveOETHMetaPool = @@ -159,6 +168,16 @@ addresses.mainnet.CurveOETHGauge = "0xd03BE91b1932715709e18021734fcB91BB431715"; addresses.mainnet.CVXETHRewardsPool = "0x24b65DC1cf053A8D96872c323d29e86ec43eB33A"; +// OETH AMO for Curve frxETH/OETH pool +addresses.mainnet.ConvexFraxAMOStrategy = + "0xD2001BaF4E726dAfd6589D5c7bf8C74d59E48438"; // TODO set once deployed +addresses.mainnet.CurveFrxETHOETHPool = + "0xfa0BBB0A5815F6648241C9221027b70914dd8949"; +addresses.mainnet.CurveFrxETHOETHGauge = + "0x8584d7F7bf9803d45e8df75Afe824f1Ae83C6256"; +addresses.mainnet.CVXFrxETHRewardsPool = + "0x105fb5f119117853af8D7D57581a13dDeCa01e21"; + // Curve frxETH/WETH pool addresses.mainnet.ConvexFrxEthWethStrategy = "0x1ce298Ec5FE0B1E4B04fb78d275Da6280f6e82A3"; // TODO set after deployment diff --git a/contracts/utils/constants.js b/contracts/utils/constants.js index 7ea0813d2a..a6452beb28 100644 --- a/contracts/utils/constants.js +++ b/contracts/utils/constants.js @@ -3,11 +3,12 @@ const { BigNumber } = require("ethers"); const MAX_UINT256 = BigNumber.from( "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ); -const threeCRVPid = 9; -const metapoolLPCRVPid = 56; -const lusdMetapoolLPCRVPid = 33; -const oethPoolLpPID = 174; -const frxEthWethPoolLpPID = 219; +const convex_3CRV_PID = 9; +const convex_OUSD_3CRV_PID = 56; +const convex_LUSD_3CRV_PID = 33; +const convex_OETH_ETH_PID = 174; +const convex_frxETH_OETH_PID = 237; +const convex_frxETH_WETH_PID = 219; // stETH/WETH const aura_stETH_WETH_PID = 115; @@ -25,11 +26,12 @@ const balancer_rETH_WETH_PID = "0x1e19cf2d73a72ef1332c882f20534b6519be0276000200000000000000000112"; module.exports = { - threeCRVPid, - metapoolLPCRVPid, - lusdMetapoolLPCRVPid, - oethPoolLpPID, - frxEthWethPoolLpPID, + convex_3CRV_PID, + convex_OUSD_3CRV_PID, + convex_LUSD_3CRV_PID, + convex_OETH_ETH_PID, + convex_frxETH_OETH_PID, + convex_frxETH_WETH_PID, MAX_UINT256, aura_stETH_WETH_PID, balancer_stETH_WETH_PID, diff --git a/contracts/utils/signers.js b/contracts/utils/signers.js index dd16417d4b..911be49b47 100644 --- a/contracts/utils/signers.js +++ b/contracts/utils/signers.js @@ -88,9 +88,13 @@ const getDefenderSigner = async () => { async function impersonateAccount(account) { log(`Impersonating account ${account}`); - await hhHelpers.impersonateAccount(account); + try { + await hhHelpers.impersonateAccount(account); - return await ethers.provider.getSigner(account); + return await ethers.provider.getSigner(account); + } catch (err) { + throw Error(`Failed to impersonate ${account}: ${err}`, { cause: err }); + } } /** diff --git a/contracts/utils/temporaryFork.js b/contracts/utils/temporaryFork.js index 792454e095..cc1350d2cd 100644 --- a/contracts/utils/temporaryFork.js +++ b/contracts/utils/temporaryFork.js @@ -1,5 +1,4 @@ -const hre = require("hardhat"); -const { nodeSnapshot, nodeRevert } = require("../test/_fixture"); +const { nodeSnapshot, nodeRevert } = require("../test/fixture/_fixture"); /* Executes a (test) function in a temporary fork that is after the function executes reverted. * Useful for when preview of actions need to be executed and changes in oToken supply and vault * observed.