From 4d93f783dc280fa34f0c03f8b0d9b1f132c90332 Mon Sep 17 00:00:00 2001 From: doncesarts Date: Wed, 15 Feb 2023 14:43:11 +0000 Subject: [PATCH 1/2] feat: shutdown metavaults contracts --- contracts/vault/LightAbstractVault.sol | 11 +- .../convex/Convex3CrvLiquidatorVault.sol | 43 +- .../curve/Curve3CrvAbstractMetaVault.sol | 50 +- .../curve/Curve3CrvBasicMetaVault.sol | 179 ++++ .../PeriodicAllocationPerfFeeMetaVault.sol | 38 +- package.json | 2 +- tasks/convex3CrvMetaVault.ts | 37 +- tasks/convex3CrvVault.ts | 33 +- tasks/curve3CrvVault.ts | 16 +- tasks/deployment/convex3CrvVaults-config.ts | 13 +- test-fork/vault/savePlusShutdown.spec.ts | 940 ++++++++++++++++++ test/vault/liquidator/Liquidator.spec.ts | 5 +- .../curve/Curve3CrvBasicMetaVault.spec.ts | 5 +- 13 files changed, 1294 insertions(+), 78 deletions(-) create mode 100644 test-fork/vault/savePlusShutdown.spec.ts diff --git a/contracts/vault/LightAbstractVault.sol b/contracts/vault/LightAbstractVault.sol index 1ee61ab..206ed36 100644 --- a/contracts/vault/LightAbstractVault.sol +++ b/contracts/vault/LightAbstractVault.sol @@ -50,6 +50,12 @@ abstract contract LightAbstractVault is IERC4626Vault, InitializableToken, Vault * @return maxAssets The maximum amount of underlying assets the caller can deposit. */ function maxDeposit(address caller) external view override returns (uint256 maxAssets) { + maxAssets = _maxDeposit(caller); + } + + function _maxDeposit( + address /** caller */ + ) internal view virtual returns (uint256 maxAssets) { if (paused()) { return 0; } @@ -63,10 +69,13 @@ abstract contract LightAbstractVault is IERC4626Vault, InitializableToken, Vault * @return maxShares The maximum amount of vault shares the caller can mint. */ function maxMint(address caller) external view override returns (uint256 maxShares) { + maxShares = _maxMint(caller); + } + + function _maxMint(address) internal view virtual returns (uint256 maxShares) { if (paused()) { return 0; } - maxShares = type(uint256).max; } diff --git a/contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol b/contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol index a598377..a2e33a5 100644 --- a/contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol +++ b/contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol @@ -191,7 +191,10 @@ contract Convex3CrvLiquidatorVault is // Get vault's asset (3Crv) balance after adding token to Curve's 3Pool. assets_ = _asset.balanceOf(address(this)); // Add asset (3Crv) to metapool with slippage protection. - uint256 metapoolTokens = ICurveMetapool(metapool).add_liquidity([0, assets_], minMetapoolTokens); + uint256 metapoolTokens = ICurveMetapool(metapool).add_liquidity( + [0, assets_], + minMetapoolTokens + ); // Calculate share value of the new assets before depositing the metapool tokens to the Convex pool. shares_ = _getSharesFromMetapoolTokens( @@ -304,7 +307,7 @@ contract Convex3CrvLiquidatorVault is override(AbstractVault, Convex3CrvAbstractVault) returns (uint256 shares) { - shares = Convex3CrvAbstractVault._previewDeposit(assets); + // return 0 } /// @dev use Convex3CrvAbstractVault implementation. @@ -315,7 +318,7 @@ contract Convex3CrvLiquidatorVault is override(AbstractVault, Convex3CrvAbstractVault) returns (uint256 assets) { - assets = Convex3CrvAbstractVault._previewMint(shares); + // return 0 } /// @dev use Convex3CrvAbstractVault implementation. @@ -345,23 +348,45 @@ contract Convex3CrvLiquidatorVault is ****************************************/ /// @dev use Convex3CrvAbstractVault implementation. - function _deposit(uint256 assets, address receiver) + function _deposit( + uint256, /** assets */ + address /** receiver */ + ) internal virtual override(AbstractVault, Convex3CrvAbstractVault) - returns (uint256 shares) + returns ( + uint256 /** shares */ + ) { - shares = Convex3CrvAbstractVault._deposit(assets, receiver); + revert("Vault shutdown"); + } + + function _maxDeposit( + address /** caller */ + ) internal view virtual override returns (uint256 maxAssets) { + // return 0 } /// @dev use Convex3CrvAbstractVault implementation. - function _mint(uint256 shares, address receiver) + function _mint( + uint256, /** shares */ + address /** receiver */ + ) internal virtual override(AbstractVault, Convex3CrvAbstractVault) - returns (uint256 assets) + returns ( + uint256 /** assets*/ + ) { - assets = Convex3CrvAbstractVault._mint(shares, receiver); + revert("Vault shutdown"); + } + + function _maxMint( + address /** caller */ + ) internal view virtual override returns (uint256 maxShares) { + // return 0 } /// @dev use Convex3CrvAbstractVault implementation. diff --git a/contracts/vault/liquidity/curve/Curve3CrvAbstractMetaVault.sol b/contracts/vault/liquidity/curve/Curve3CrvAbstractMetaVault.sol index 1292423..e894671 100644 --- a/contracts/vault/liquidity/curve/Curve3CrvAbstractMetaVault.sol +++ b/contracts/vault/liquidity/curve/Curve3CrvAbstractMetaVault.sol @@ -90,7 +90,7 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV * Meta Vault shares -> Meta Vault assets (3Crv) -> vault assets (DAI, USDC or USDT) * @return totalManagedAssets Amount of assets managed by the vault. */ - function totalAssets() public view override returns (uint256 totalManagedAssets) { + function totalAssets() public view virtual override returns (uint256 totalManagedAssets) { // Get the amount of underying meta vault shares held by this vault. uint256 totalMetaVaultShares = metaVault.balanceOf(address(this)); if (totalMetaVaultShares > 0) { @@ -191,6 +191,10 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV override returns (uint256 shares) { + shares = _previewDeposit(assets); + } + + function _previewDeposit(uint256 assets) internal view virtual returns (uint256 shares) { if (assets > 0) { // Calculate Meta Vault assets (3Crv) for this vault's asset (DAI, USDC, USDT) (uint256 threeCrvTokens, , ) = Curve3PoolCalculatorLibrary.calcDeposit( @@ -232,6 +236,10 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV whenNotPaused returns (uint256 assets) { + assets = _mint(shares, receiver); + } + + function _mint(uint256 shares, address receiver) internal virtual returns (uint256 assets) { // Get the total underlying Meta Vault shares held by this vault. uint256 totalMetaVaultShares = metaVault.balanceOf(address(this)); // Convert this vault's required shares to required underlying meta vault shares. @@ -282,6 +290,10 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV * @dev Vault shares -> Meta Vault shares -> Meta Vault assets (3Crv) -> vault assets (eg DAI) */ function previewMint(uint256 shares) external view virtual override returns (uint256 assets) { + assets = _previewMint(shares); + } + + function _previewMint(uint256 shares) internal view virtual returns (uint256 assets) { if (shares > 0) { // Get the total underlying Meta Vault shares held by this vault. uint256 totalMetaVaultShares = metaVault.balanceOf(address(this)); @@ -321,6 +333,14 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV address receiver, address owner ) external virtual override whenNotPaused returns (uint256 shares) { + shares = _withdraw(assets, receiver, owner); + } + + function _withdraw( + uint256 assets, + address receiver, + address owner + ) internal virtual returns (uint256 shares) { if (assets > 0) { // Get the total underlying Meta Vault shares held by this vault. uint256 totalMetaVaultSharesBefore = metaVault.balanceOf(address(this)); @@ -399,6 +419,10 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV override returns (uint256 shares) { + shares = _previewWithdraw(assets); + } + + function _previewWithdraw(uint256 assets) internal view virtual returns (uint256 shares) { if (assets > 0) { // Calculate 3Pool LP tokens (3Crv) for this vault's asset (DAI, USDC or USDT). (uint256 threeCrvTokens, , ) = Curve3PoolCalculatorLibrary.calcWithdraw( @@ -742,14 +766,7 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV /*************************************** Emergency Functions ****************************************/ - - /** - * @notice Governor liquidates all the vault's assets and send to the governor. - * Only to be used in an emergency. eg whitehat protection against a hack. - * @param minAssets Minimum amount of asset tokens to receive from removing liquidity from the Curve 3Pool. - * This provides sandwich attack protection. - */ - function liquidateVault(uint256 minAssets) external onlyGovernor { + function _liquidateVault(uint256 minAssets, bool transferToGovernor) internal { uint256 totalMetaVaultShares = metaVault.balanceOf(address(this)); metaVault.redeem(totalMetaVaultShares, address(this), address(this)); @@ -759,8 +776,19 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV int128(uint128(assetPoolIndex)), minAssets ); + if (transferToGovernor) { + _asset.safeTransfer(_governor(), _asset.balanceOf(address(this))); + } + } - _asset.safeTransfer(_governor(), _asset.balanceOf(address(this))); + /** + * @notice Governor liquidates all the vault's assets and send to the governor. + * Only to be used in an emergency. eg whitehat protection against a hack. + * @param minAssets Minimum amount of asset tokens to receive from removing liquidity from the Curve 3Pool. + * This provides sandwich attack protection. + */ + function liquidateVault(uint256 minAssets) external virtual onlyGovernor { + _liquidateVault(minAssets, true); } /*************************************** @@ -769,7 +797,7 @@ abstract contract Curve3CrvAbstractMetaVault is AbstractSlippage, LightAbstractV /// @notice Approves Curve's 3Pool contract to transfer assets (DAI, USDC or USDT) from this vault. /// Also approves the underlying Meta Vault to transfer 3Crv from this vault. - function resetAllowances() external onlyGovernor { + function resetAllowances() external virtual onlyGovernor { _resetAllowances(); } diff --git a/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol b/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol index 0467699..1ae3de3 100644 --- a/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol +++ b/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity 0.8.17; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { AbstractSlippage } from "../AbstractSlippage.sol"; @@ -11,11 +13,14 @@ import { InitializableToken } from "../../../tokens/InitializableToken.sol"; /** * @title Basic 3Pool ERC-4626 vault that takes in one underlying asset to deposit in 3Pool and put the 3Crv in underlying metaVault. + * @notice Disables permanently mints and deposits, allows to liquidate underlying vaults. * @author mStable * @dev VERSION: 1.0 * DATE: 2022-05-11 */ contract Curve3CrvBasicMetaVault is Curve3CrvAbstractMetaVault, Initializable { + using SafeERC20 for IERC20; + /// @param _nexus Address of the Nexus contract that resolves protocol modules and roles.. /// @param _asset Address of the vault's asset which is one of the 3Pool tokens DAI, USDC or USDT. /// @param _metaVault Address of the vault's underlying meta vault that implements ERC-4626. @@ -46,4 +51,178 @@ contract Curve3CrvBasicMetaVault is Curve3CrvAbstractMetaVault, Initializable { AbstractSlippage._initialize(_slippageData); Curve3CrvAbstractMetaVault._initialize(); } + + /// @dev Overrides Curve3CrvAbstractMetaVault.totalAssets() + function totalAssets() public view override returns (uint256 totalManagedAssets) { + totalManagedAssets = + Curve3CrvAbstractMetaVault.totalAssets() + + _asset.balanceOf(address(this)); + } + + /*/////////////////////////////////////////////////////////////// + DEPOSIT/MINT + //////////////////////////////////////////////////////////////*/ + /// @dev disables Curve3CrvAbstractMetaVault implementation. + function _depositInternal( + uint256, /** assets */ + address, /** receiver */ + uint256 /** _slippage */ + ) + internal + virtual + override(Curve3CrvAbstractMetaVault) + returns ( + uint256 /** shares */ + ) + { + revert("Vault shutdown"); + } + + function _previewDeposit( + uint256 /** assets*/ + ) internal view virtual override returns (uint256 shares) { + // return 0 + } + + function _maxDeposit( + address /** caller */ + ) internal view virtual override returns (uint256 maxAssets) { + // return 0 + } + + /// @dev disables Curve3CrvAbstractMetaVault implementation. + function _mint( + uint256, /** shares */ + address /** receiver */ + ) + internal + virtual + override(Curve3CrvAbstractMetaVault) + returns ( + uint256 /** assets */ + ) + { + revert("Vault shutdown"); + } + + function _previewMint(uint256 shares) internal view virtual override returns (uint256 assets) { + // return 0 + } + + function _maxMint( + address /** caller */ + ) internal view virtual override returns (uint256 maxShares) { + // return 0 + } + + /*/////////////////////////////////////////////////////////////// + WITHDRAW/REDEEM + //////////////////////////////////////////////////////////////*/ + /// @dev Overrides Curve3CrvAbstractMetaVault._withdraw() + function _withdraw( + uint256 assets, + address receiver, + address owner + ) internal virtual override returns (uint256 shares) { + shares = _previewWithdraw(assets); + + _burnTransfer(assets, shares, receiver, owner, false); + } + + function _previewWithdraw(uint256 assets) + internal + view + virtual + override + returns (uint256 shares) + { + shares = _convertToShares(assets); + } + + /// @dev Overrides Curve3CrvAbstractMetaVault._redeemInternal() + function _redeemInternal( + uint256 shares, + address receiver, + address owner, + uint256 /** _slippage **/ + ) internal virtual override returns (uint256 assets) { + assets = _previewRedeem(shares); + _burnTransfer(assets, shares, receiver, owner, true); + } + + function _previewRedeem(uint256 shares) + internal + view + virtual + override + returns (uint256 assets) + { + assets = _convertToAssets(shares); + } + + /*/////////////////////////////////////////////////////////////// + INTERNAL WITHDRAW/REDEEM + //////////////////////////////////////////////////////////////*/ + + function _burnTransfer( + uint256 assets, + uint256 shares, + address receiver, + address owner, + bool /** fromRedeem */ + ) internal virtual { + // If caller is not the owner of the shares + uint256 allowed = allowance(owner, msg.sender); + if (msg.sender != owner && allowed != type(uint256).max) { + require(shares <= allowed, "Amount exceeds allowance"); + _approve(owner, msg.sender, allowed - shares); + } + + _burn(owner, shares); + + _asset.safeTransfer(receiver, assets); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + } + + /*/////////////////////////////////////////////////////////////// + CONVERTIONS + //////////////////////////////////////////////////////////////*/ + function _convertToShares(uint256 assets) internal view virtual returns (uint256 shares) { + uint256 totalShares = totalSupply(); + + if (totalShares == 0) { + shares = assets; // 1:1 value of shares and assets + } else { + shares = (assets * totalShares) / totalAssets(); + } + } + + function _convertToAssets(uint256 shares) internal view virtual returns (uint256 assets) { + uint256 totalShares = totalSupply(); + + if (totalShares == 0) { + assets = shares; // 1:1 value of shares and assets + } else { + assets = (shares * totalAssets()) / totalShares; + } + } + + /** + * @notice disables Curve3CrvAbstractMetaVault.liquidateVault(), it does nothing. + */ + function liquidateVault( + uint256 /** minAssets */ + ) external view override onlyGovernor { + revert("Vault shutdown"); + } + + /** + * @notice Governor liquidates all underlying vaults assets. + * @param minAssets Minimum amount of asset tokens to receive from removing liquidity from the Curve 3Pool. + * This provides sandwich attack protection. + */ + function liquidateUnderlyingVault(uint256 minAssets) external onlyGovernor { + _liquidateVault(minAssets, false); + } } diff --git a/contracts/vault/meta/PeriodicAllocationPerfFeeMetaVault.sol b/contracts/vault/meta/PeriodicAllocationPerfFeeMetaVault.sol index 3c531d2..7a62f6e 100644 --- a/contracts/vault/meta/PeriodicAllocationPerfFeeMetaVault.sol +++ b/contracts/vault/meta/PeriodicAllocationPerfFeeMetaVault.sol @@ -74,13 +74,18 @@ contract PeriodicAllocationPerfFeeMetaVault is //////////////////////////////////////////////////////////////*/ /// @dev use PeriodicAllocationAbstractVault implementation. - function _deposit(uint256 assets, address receiver) + function _deposit( + uint256, /** assets */ + address /** receiver */ + ) internal virtual override(AbstractVault, PeriodicAllocationAbstractVault) - returns (uint256 shares) + returns ( + uint256 /** shares */ + ) { - return PeriodicAllocationAbstractVault._deposit(assets, receiver); + revert("Vault shutdown"); } /// @dev use PeriodicAllocationAbstractVault implementation. @@ -91,17 +96,28 @@ contract PeriodicAllocationPerfFeeMetaVault is override(AbstractVault, PeriodicAllocationAbstractVault) returns (uint256 shares) { - return PeriodicAllocationAbstractVault._previewDeposit(assets); + // return 0 + } + + function _maxDeposit( + address /** caller */ + ) internal view virtual override returns (uint256 maxAssets) { + // return 0 } /// @dev use PeriodicAllocationAbstractVault implementation. - function _mint(uint256 shares, address receiver) + function _mint( + uint256, /** shares */ + address /** receiver */ + ) internal virtual override(AbstractVault, PeriodicAllocationAbstractVault) - returns (uint256 assets) + returns ( + uint256 /** assets*/ + ) { - return PeriodicAllocationAbstractVault._mint(shares, receiver); + revert("Vault shutdown"); } /// @dev use PeriodicAllocationAbstractVault implementation. @@ -112,7 +128,13 @@ contract PeriodicAllocationPerfFeeMetaVault is override(AbstractVault, PeriodicAllocationAbstractVault) returns (uint256 assets) { - return PeriodicAllocationAbstractVault._previewMint(shares); + // return 0 + } + + function _maxMint( + address /** caller */ + ) internal view virtual override returns (uint256 maxShares) { + // return 0 } /*/////////////////////////////////////////////////////////////// diff --git a/package.json b/package.json index 43c4226..7299163 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "task:fork:polly": "yarn hardhat --config tasks-fork-polygon.config.ts --typecheck", "test": "yarn hardhat test --typecheck", "test:fork": "yarn hardhat --config hardhat-fork.config.ts test ./test-fork/**/*.spec.ts --typecheck", - "test:fork:ci": "yarn hardhat --config hardhat-fork.config.ts test ./test-fork/vault/savePlus.spec.ts --typecheck", + "test:fork:ci": "yarn hardhat --config hardhat-fork.config.ts test ./test-fork/vault/savePlusShutdown.spec.ts --typecheck", "test:file:fork": "yarn hardhat --config hardhat-fork.config.ts test --typecheck", "test:file": "yarn hardhat test --typecheck", "slither": "slither .", diff --git a/tasks/convex3CrvMetaVault.ts b/tasks/convex3CrvMetaVault.ts index d297bcb..48aa048 100644 --- a/tasks/convex3CrvMetaVault.ts +++ b/tasks/convex3CrvMetaVault.ts @@ -24,7 +24,12 @@ interface AssetSourcingParams { singleSourceVaultIndex: number } -interface PeriodicAllocationPerfFeeMetaVaultParams { +interface PeriodicAllocationPerfFeeMetaVaultUpgrade { + nexus: string + asset: string + proxy: boolean +} +interface PeriodicAllocationPerfFeeMetaVaultDeploy { nexus: string asset: string name: string @@ -38,6 +43,7 @@ interface PeriodicAllocationPerfFeeMetaVaultParams { assetPerShareUpdateThreshold: BN proxy: boolean } +type PeriodicAllocationPerfFeeMetaVaultParams = PeriodicAllocationPerfFeeMetaVaultDeploy | PeriodicAllocationPerfFeeMetaVaultUpgrade export async function deployPeriodicAllocationPerfFeeMetaVaults( hre: HardhatRuntimeEnvironment, @@ -71,20 +77,7 @@ export const deployPeriodicAllocationPerfFeeMetaVault = async ( signer: Signer, params: PeriodicAllocationPerfFeeMetaVaultParams, ) => { - const { - nexus, - asset, - name, - symbol, - vaultManager, - proxyAdmin, - performanceFee, - feeReceiver, - underlyingVaults, - sourceParams, - assetPerShareUpdateThreshold, - proxy, - } = params + const { nexus, asset, proxy } = params const constructorArguments = [nexus, asset] const vaultImpl = await deployContract( new PeriodicAllocationPerfFeeMetaVault__factory(signer), @@ -102,6 +95,18 @@ export const deployPeriodicAllocationPerfFeeMetaVault = async ( if (!proxy) { return { proxy: undefined, impl: vaultImpl } } + const { + name, + symbol, + vaultManager, + proxyAdmin, + performanceFee, + feeReceiver, + underlyingVaults, + sourceParams, + assetPerShareUpdateThreshold, + } = params as PeriodicAllocationPerfFeeMetaVaultDeploy + const data = vaultImpl.interface.encodeFunctionData("initialize", [ name, symbol, @@ -126,7 +131,7 @@ subtask("convex-3crv-mv-deploy", "Deploys Convex 3Crv Meta Vault") undefined, types.string, ) - .addOptionalParam("name", "Vault name", "3CRV Convex Meta Vault", types.string) + .addOptionalParam("name", "Vault name", "Convex 3CRV Meta Vault", types.string) .addOptionalParam("symbol", "Vault symbol", "mv3CRV-CVX", types.string) .addOptionalParam("asset", "Token address or symbol of the vault's asset", "3Crv", types.string) .addOptionalParam("admin", "Instant or delayed proxy admin: InstantProxyAdmin | DelayedProxyAdmin", "InstantProxyAdmin", types.string) diff --git a/tasks/convex3CrvVault.ts b/tasks/convex3CrvVault.ts index 4ece1e3..992700d 100644 --- a/tasks/convex3CrvVault.ts +++ b/tasks/convex3CrvVault.ts @@ -51,10 +51,21 @@ interface Convex3CrvBasicVaultParams { feeReceiver: string } -interface Convex3CrvLiquidatorVaultParams extends Convex3CrvBasicVaultParams { +interface Convex3CrvLiquidatorVaultUpgrade { + calculatorLibrary: string + nexus: string + asset: string + constructorData: Convex3CrvConstructorData + name: string + symbol: string + streamDuration: number + proxy: boolean +} +interface Convex3CrvLiquidatorVaultDeploy extends Convex3CrvBasicVaultParams { streamDuration: number proxy: boolean } +type Convex3CrvLiquidatorVaultParams = Convex3CrvLiquidatorVaultUpgrade | Convex3CrvLiquidatorVaultDeploy export async function deployCurve3CrvMetapoolCalculatorLibrary(hre: HardhatRuntimeEnvironment, signer: Signer) { const calculatorLibrary = await deployContract( @@ -127,23 +138,7 @@ export async function deployConvex3CrvLiquidatorVault( signer: Signer, params: Convex3CrvLiquidatorVaultParams, ) { - const { - calculatorLibrary, - nexus, - asset, - constructorData, - slippageData, - streamDuration, - name, - symbol, - vaultManager, - proxyAdmin, - rewardTokens, - donateToken, - donationFee, - feeReceiver, - proxy, - } = params + const { calculatorLibrary, nexus, asset, constructorData, name, symbol, streamDuration, proxy } = params const linkAddresses = getMetapoolLinkAddresses(calculatorLibrary) @@ -165,6 +160,8 @@ export async function deployConvex3CrvLiquidatorVault( if (!proxy) { return { proxy: undefined, impl: vaultImpl } } + const { slippageData, vaultManager, proxyAdmin, rewardTokens, donateToken, donationFee, feeReceiver } = + params as Convex3CrvLiquidatorVaultDeploy const data = vaultImpl.interface.encodeFunctionData("initialize", [ name, symbol, diff --git a/tasks/curve3CrvVault.ts b/tasks/curve3CrvVault.ts index da7cb0e..6c053fc 100644 --- a/tasks/curve3CrvVault.ts +++ b/tasks/curve3CrvVault.ts @@ -18,7 +18,14 @@ type SlippageData = { withdraw: number mint: number } -interface Curve3CrvBasicMetaVaultParams { +interface Curve3CrvBasicMetaVaultUpgrade { + calculatorLibrary: string + nexus: string + asset: string + metaVault: string + proxy: boolean +} +interface Curve3CrvBasicMetaVaultDeploy { calculatorLibrary: string nexus: string asset: string @@ -30,6 +37,9 @@ interface Curve3CrvBasicMetaVaultParams { proxyAdmin: string proxy: boolean } + +type Curve3CrvBasicMetaVaultParams = Curve3CrvBasicMetaVaultDeploy | Curve3CrvBasicMetaVaultUpgrade + interface Curve3CrvMetaVaultDeployed { proxy: AssetProxy impl: Curve3CrvBasicMetaVault @@ -55,7 +65,7 @@ export async function deployCurve3PoolCalculatorLibrary(hre: HardhatRuntimeEnvir } export const deployCurve3CrvMetaVault = async (hre: HardhatRuntimeEnvironment, signer: Signer, params: Curve3CrvBasicMetaVaultParams) => { - const { calculatorLibrary, nexus, asset, metaVault, slippageData, name, symbol, vaultManager, proxyAdmin, proxy } = params + const { calculatorLibrary, nexus, asset, metaVault, proxy } = params const libraryAddresses = { "contracts/peripheral/Curve/Curve3PoolCalculatorLibrary.sol:Curve3PoolCalculatorLibrary": calculatorLibrary } @@ -76,6 +86,8 @@ export const deployCurve3CrvMetaVault = async (hre: HardhatRuntimeEnvironment, s if (!proxy) { return { proxy: undefined, impl: vaultImpl } } + const { slippageData, name, symbol, vaultManager, proxyAdmin } = params as Curve3CrvBasicMetaVaultDeploy + const data = vaultImpl.interface.encodeFunctionData("initialize", [name, symbol, vaultManager, slippageData]) const proxyConstructorArguments = [vaultImpl.address, proxyAdmin, data] const proxyContract = await deployContract(new AssetProxy__factory(signer), "AssetProxy", proxyConstructorArguments) diff --git a/tasks/deployment/convex3CrvVaults-config.ts b/tasks/deployment/convex3CrvVaults-config.ts index 659bfb5..b5004d2 100644 --- a/tasks/deployment/convex3CrvVaults-config.ts +++ b/tasks/deployment/convex3CrvVaults-config.ts @@ -31,8 +31,8 @@ const usdcCurve3CrvMetaVault: Curve3CrvPool = { // constructor asset: USDC.address, // initialize - name: "USDC Convex Meta Vault", - symbol: "mvUSDC-CX1", + name: "USDC 3Pool Convex Meta Vault", + symbol: "mvUSDC-3PCV", decimals: USDC.decimals, slippageData, } @@ -198,17 +198,16 @@ export const config = { periodicAllocationPerfFeeMetaVault: { asset: ThreeCRV.address, - name: "Convex 3CRV Meta Vault", - symbol: "m3CRV-CX1", - performanceFee: 50000, //5 + name: "3CRV Convex Meta Vault", + symbol: "mv3CRV-CVX", + performanceFee: 40000, //5 feeReceiver, - // underlyingVaults: Array after deployment, sourceParams: { // TODO - TBD singleVaultSharesThreshold: 1000, // 10% singleSourceVaultIndex: 0, }, - assetPerShareUpdateThreshold: simpleToExactAmount(1000000), //1M + assetPerShareUpdateThreshold: simpleToExactAmount(100000), }, curve3CrvMetaVault: { dai: daiCurve3CrvMetaVault, usdc: usdcCurve3CrvMetaVault, usdt: usdtCurve3CrvMetaVault }, } diff --git a/test-fork/vault/savePlusShutdown.spec.ts b/test-fork/vault/savePlusShutdown.spec.ts new file mode 100644 index 0000000..b420f45 --- /dev/null +++ b/test-fork/vault/savePlusShutdown.spec.ts @@ -0,0 +1,940 @@ +import { config } from "@tasks/deployment/convex3CrvVaults-config" +import { logger } from "@tasks/utils/logger" +import { resolveAddress } from "@tasks/utils/networkAddressFactory" +import { assertBNClose } from "@utils/assertions" +import { DEAD_ADDRESS, ONE_HOUR, ONE_WEEK, ZERO } from "@utils/constants" +import { impersonateAccount, loadOrExecFixture, setBalancesToAccount } from "@utils/fork" +import { StandardAccounts } from "@utils/machines" +import { BN, simpleToExactAmount } from "@utils/math" +import { increaseTime } from "@utils/time" +import { expect } from "chai" +import { keccak256, toUtf8Bytes } from "ethers/lib/utils" +import * as hre from "hardhat" +import { ethers } from "hardhat" +import { + BasicDexSwap__factory, + Convex3CrvLiquidatorVault__factory, + CowSwapDex__factory, + Curve3CrvBasicMetaVault__factory, + DataEmitter__factory, + IERC20__factory, + IERC20Metadata__factory, + InstantProxyAdmin__factory, + Liquidator__factory, + MockGPv2Settlement__factory, + MockGPv2VaultRelayer__factory, + Nexus__factory, + PeriodicAllocationPerfFeeMetaVault__factory, +} from "types/generated" + +import { CRV, CVX, DAI, logTxDetails, ThreeCRV, USDC, usdFormatter, USDT } from "../../tasks/utils" + +import type { BigNumber, Signer } from "ethers" +import type { + Convex3CrvLiquidatorVault, + Convex3CrvPool, + CowSwapDex, + Curve3CrvBasicMetaVault, + Curve3CrvPool, + DataEmitter, + Liquidator, + Nexus, +} from "types" +import type { Account, AnyVault } from "types/common" +import type { ERC20, IERC20Metadata, InstantProxyAdmin, PeriodicAllocationPerfFeeMetaVault } from "types/generated" +import { deployCurve3CrvMetaVault } from "@tasks/curve3CrvVault" +import { deployPeriodicAllocationPerfFeeMetaVault } from "@tasks/convex3CrvMetaVault" +import { deployConvex3CrvLiquidatorVault } from "@tasks/convex3CrvVault" + +const log = logger("test:savePlus") + +const governorAddress = resolveAddress("Governor") +const feeReceiver = resolveAddress("mStableDAO") +const curveThreePoolAddress = resolveAddress("CurveThreePool") +const convexBoosterAddress = resolveAddress("ConvexBooster") +const usdtWhaleAddress = "0xD6216fC19DB775Df9774a6E33526131dA7D19a2c" // KuCoin 6 +const daiWhaleAddress = "0xD6216fC19DB775Df9774a6E33526131dA7D19a2c" // KuCoin 6 +const usdcWhaleAddress = "0x3dd46846eed8D147841AE162C8425c08BD8E1b41" // mStableDAO +const threeCrvWhale1Address = "0x064c60c99C392c96d5733AE48d83fE7Ea3C75CAf" +const threeCrvWhale2Address = "0xEcd5e75AFb02eFa118AF914515D6521aaBd189F1" +// CRV and CVX rewards +const rewardsWhaleAddress = "0x2faf487a4414fe77e2327f0bf4ae2a264a776ad2" // FTX Exchange + +interface Convex3CrvLiquidatorVaults { + musd: Convex3CrvLiquidatorVault + frax: Convex3CrvLiquidatorVault + busd: Convex3CrvLiquidatorVault +} +interface Curve3CrvBasicMetaVaults { + usdc: Curve3CrvBasicMetaVault +} +async function deployMockSyncSwapper(deployer: Signer, nexus: Nexus) { + const exchanges = [ + { from: CRV.address, to: DAI.address, rate: simpleToExactAmount(62, 16) }, + { from: CVX.address, to: DAI.address, rate: simpleToExactAmount(57, 16) }, + { from: CRV.address, to: USDC.address, rate: simpleToExactAmount(61, 4) }, + { from: CVX.address, to: USDC.address, rate: simpleToExactAmount(56, 4) }, + { from: CRV.address, to: USDT.address, rate: simpleToExactAmount(63, 4) }, + { from: CVX.address, to: USDT.address, rate: simpleToExactAmount(58, 4) }, + ] + const swapper = await new BasicDexSwap__factory(deployer).deploy(nexus.address) + await swapper.initialize(exchanges) + return swapper +} +async function deployMockAsyncSwapper(deployer: Signer, nexus: Nexus) { + const gpv2Settlement = await new MockGPv2Settlement__factory(deployer).deploy() + const relayer = await new MockGPv2VaultRelayer__factory(deployer).deploy(DEAD_ADDRESS) + await relayer.initialize([ + { from: CRV.address, to: DAI.address, rate: simpleToExactAmount(62, 16) }, + { from: CVX.address, to: DAI.address, rate: simpleToExactAmount(57, 16) }, + { from: CRV.address, to: USDC.address, rate: simpleToExactAmount(61, 4) }, + { from: CVX.address, to: USDC.address, rate: simpleToExactAmount(56, 4) }, + { from: CRV.address, to: USDT.address, rate: simpleToExactAmount(63, 4) }, + { from: CVX.address, to: USDT.address, rate: simpleToExactAmount(58, 4) }, + ]) + const swapper = await new CowSwapDex__factory(deployer).deploy(nexus.address, relayer.address, gpv2Settlement.address) + return { relayer, swapper } +} + +const assertVaultDeposit = async (staker: Account, asset: IERC20Metadata, vault: AnyVault, depositAmount: BigNumber) => { + await increaseTime(ONE_HOUR) + const assetsBefore = await asset.balanceOf(staker.address) + const sharesBefore = await vault.balanceOf(staker.address) + const totalAssetsBefore = await vault.totalAssets() + + const sharesPreviewed = await vault.connect(staker.signer).previewDeposit(depositAmount) + + await expect(vault.connect(staker.signer)["deposit(uint256,address)"](depositAmount, staker.address), "deposit").to.be.revertedWith( + "Vault shutdown", + ) + const sharesAfter = await vault.balanceOf(staker.address) + const assetsAfter = await asset.balanceOf(staker.address) + const sharesMinted = sharesAfter.sub(sharesBefore) + + expect(sharesMinted, "sharesMinted").to.be.eq(ZERO) + expect(sharesPreviewed, "previewDeposit").to.be.eq(ZERO) + expect(assetsAfter, `staker ${await asset.symbol()} assets after`).eq(assetsBefore) + expect(await vault.balanceOf(staker.address), `staker ${await vault.symbol()} shares after`).eq(sharesBefore) + expect(await vault.totalAssets(), "totalAssets").to.be.eq(totalAssetsBefore) +} + +const assertVaultMint = async ( + staker: Account, + asset: IERC20Metadata, + vault: AnyVault, + dataEmitter: DataEmitter, + mintAmount: BigNumber, +) => { + const variance = BN.from(10) + await increaseTime(ONE_HOUR) + const assetsBefore = await asset.balanceOf(staker.address) + const sharesBefore = await vault.balanceOf(staker.address) + const totalSharesBefore = await vault.totalSupply() + const assetsPreviewed = await vault.connect(staker.signer).previewMint(mintAmount) + log(`Assets deposited from mint of 70,000 shares ${usdFormatter(assetsPreviewed, 18, 14, 18)}`) + + await expect(vault.connect(staker.signer).mint(mintAmount, staker.address), "mint").to.be.revertedWith("Vault shutdown") + + const assetsAfter = await asset.balanceOf(staker.address) + const assetsUsedForMint = assetsBefore.sub(assetsAfter) + + expect(assetsUsedForMint, "assetsUsedForMint").to.be.eq(ZERO) + expect(assetsPreviewed, "assetsPreviewed").to.be.eq(ZERO) + expect(assetsAfter, `staker ${await asset.symbol()} assets after`).eq(assetsBefore) + expect(await vault.balanceOf(staker.address), `staker ${await vault.symbol()} shares after`).eq(sharesBefore) + expect(await vault.totalSupply(), "vault supply after").eq(totalSharesBefore) +} +const assertVaultWithdraw = async (staker: Account, asset: IERC20Metadata, vault: AnyVault, _withdrawAmount?: BigNumber) => { + const variance = BN.from(10) + await increaseTime(ONE_HOUR) + const withdrawAmount = _withdrawAmount ? _withdrawAmount : await vault.convertToAssets(await vault.balanceOf(staker.address)) + const assetsBefore = await asset.balanceOf(staker.address) + const sharesBefore = await vault.balanceOf(staker.address) + const sharesPreviewed = await vault.connect(staker.signer).previewWithdraw(withdrawAmount) + + const tx = await vault.connect(staker.signer).withdraw(withdrawAmount, staker.address, staker.address) + + await ethers.provider.send("evm_mine", []) + + await logTxDetails(tx, `withdraw ${withdrawAmount} assets`) + + const assetsAfter = await asset.balanceOf(staker.address) + const sharesAfter = await vault.balanceOf(staker.address) + const sharesBurned = sharesBefore.sub(sharesAfter) + + assertBNClose(sharesBurned, sharesPreviewed, variance, "expected shares burned") + expect(assetsAfter, `staker ${await asset.symbol()} assets after`).eq(assetsBefore.add(withdrawAmount)) + expect(await vault.balanceOf(staker.address), `staker ${await vault.symbol()} shares after`).lt(sharesBefore) +} +const assertVaultRedeem = async ( + staker: Account, + asset: IERC20Metadata, + vault: AnyVault, + dataEmitter: DataEmitter, + _redeemAmount?: BigNumber, +) => { + const variance = BN.from(10) + // Do a full redeem if no redeemAmount passed + const redeemAmount = _redeemAmount ? _redeemAmount : await vault.balanceOf(staker.address) + await increaseTime(ONE_HOUR) + const assetsBefore = await asset.balanceOf(staker.address) + const sharesBefore = await vault.balanceOf(staker.address) + const assetsPreviewed = await vault.connect(staker.signer).previewRedeem(redeemAmount) + + // Need to get the totalSupply before the mint tx but in the same block + const tx1 = await dataEmitter.emitStaticCall(vault.address, vault.interface.encodeFunctionData("totalSupply")) + + const tx2 = await vault.connect(staker.signer)["redeem(uint256,address,address)"](redeemAmount, staker.address, staker.address) + + await ethers.provider.send("evm_mine", []) + const tx1Receipt = await tx1.wait() + const totalSharesBefore = vault.interface.decodeFunctionResult("totalSupply", tx1Receipt.events[0].args[0])[0] + + await logTxDetails(tx2, `redeem ${usdFormatter(redeemAmount)} shares`) + + const assetsAfter = await asset.balanceOf(staker.address) + const sharesAfter = await vault.balanceOf(staker.address) + const assetsRedeemed = assetsAfter.sub(assetsBefore) + log( + `assertVaultRedeem redeemAmount ${redeemAmount.toString()} assetsBefore ${assetsBefore.toString()}, assetsRedeemed ${assetsRedeemed.toString()}, assetsAfter ${assetsAfter.toString()}`, + ) + assertBNClose(assetsRedeemed, assetsPreviewed, variance, "expected assets redeemed") + expect(assetsAfter, `staker ${await asset.symbol()} assets after`).gt(assetsBefore) + expect(sharesAfter, `staker ${await vault.symbol()} shares after`).eq(sharesBefore.sub(redeemAmount)) + expect(await vault.totalSupply(), "vault supply after").eq(totalSharesBefore.sub(redeemAmount)) +} + +const snapConvex3CrvLiquidatorVaults = async (vaults: Convex3CrvLiquidatorVaults, account: Account, metaVaultAddress: string) => { + // reward tokens + const crvToken = IERC20__factory.connect(CRV.address, account.signer) + const cvxToken = IERC20__factory.connect(CVX.address, account.signer) + + const snapVault = async (vault: Convex3CrvLiquidatorVault) => ({ + totalAssets: await vault.totalAssets(), + totalSupply: await vault.totalSupply(), + metaVaultBalance: await vault.balanceOf(metaVaultAddress), + feeReceiverBalance: await vault.balanceOf(await vault.feeReceiver()), + // rewards + crvBalance: await crvToken.balanceOf(vault.address), + cvxBalance: await cvxToken.balanceOf(vault.address), + // fees + STREAM_DURATION: await vault.STREAM_DURATION(), + STREAM_PER_SECOND_SCALE: await vault.STREAM_PER_SECOND_SCALE(), + shareStream: await vault.shareStream(), + feeReceiver: await vault.feeReceiver(), + }) + const vaultsData = { + musd: await snapVault(vaults.musd), + frax: await snapVault(vaults.frax), + busd: await snapVault(vaults.busd), + } + log(` + musd: { totalAssets: ${vaultsData.musd.totalAssets.toString()}, totalSupply: ${vaultsData.musd.totalSupply.toString()} , metaVaultBalance: ${vaultsData.musd.metaVaultBalance.toString()} , + feeReceiverBalance: ${vaultsData.musd.feeReceiverBalance.toString()} , crvBalance: ${vaultsData.musd.crvBalance.toString()} , cvxBalance: ${vaultsData.musd.cvxBalance.toString()} , + STREAM_DURATION: ${vaultsData.musd.STREAM_DURATION.toString()} , STREAM_PER_SECOND_SCALE: ${vaultsData.musd.STREAM_PER_SECOND_SCALE.toString()} + shareStream: ${vaultsData.musd.shareStream.toString()} , feeReceiver: ${vaultsData.musd.feeReceiver.toString()} + } + frax: { totalAssets: ${vaultsData.frax.totalAssets.toString()}, totalSupply: ${vaultsData.frax.totalSupply.toString()} , metaVaultBalance: ${vaultsData.frax.metaVaultBalance.toString()} , + feeReceiverBalance: ${vaultsData.frax.feeReceiverBalance.toString()} , crvBalance: ${vaultsData.frax.crvBalance.toString()} , cvxBalance: ${vaultsData.frax.cvxBalance.toString()} + STREAM_DURATION: ${vaultsData.frax.STREAM_DURATION.toString()} , STREAM_PER_SECOND_SCALE: ${vaultsData.frax.STREAM_PER_SECOND_SCALE.toString()} + shareStream: ${vaultsData.frax.shareStream.toString()} , feeReceiver: ${vaultsData.frax.feeReceiver.toString()} + } + busd: { totalAssets: ${vaultsData.busd.totalAssets.toString()}, totalSupply: ${vaultsData.busd.totalSupply.toString()} , metaVaultBalance: ${vaultsData.busd.metaVaultBalance.toString()} , + feeReceiverBalance: ${vaultsData.busd.feeReceiverBalance.toString()} , crvBalance: ${vaultsData.busd.crvBalance.toString()} , cvxBalance: ${vaultsData.busd.cvxBalance.toString()} + STREAM_DURATION: ${vaultsData.busd.STREAM_DURATION.toString()} , STREAM_PER_SECOND_SCALE: ${vaultsData.busd.STREAM_PER_SECOND_SCALE.toString()} + shareStream: ${vaultsData.busd.shareStream.toString()} , feeReceiver: ${vaultsData.busd.feeReceiver.toString()} + } + `) + return vaultsData +} +const snapPeriodicAllocationPerfFeeMetaVault = async ( + vault: PeriodicAllocationPerfFeeMetaVault, + account: Account, + curve3CrvBasicMetaVaults: Curve3CrvBasicMetaVaults, + // users: { user1: string; user2: string }, +) => { + const assetToken = IERC20__factory.connect(await vault.asset(), account.signer) + + const vaultData = { + totalSupply: await vault.totalSupply(), + totalAssets: await vault.totalAssets(), + assetsPerShare: await vault.assetsPerShare(), + internalBalance: await assetToken.balanceOf(vault.address), + } + const usersData = { + user1Balance: await vault.balanceOf(account.address), + } + let curve3CrvBasicMetaVaultsData = undefined + if (curve3CrvBasicMetaVaults) { + curve3CrvBasicMetaVaultsData = { + usdcVaultBalance: await vault.balanceOf(curve3CrvBasicMetaVaults.usdc.address), + } + } + // users: {user1Balance: ${usersData.user1Balance.toString()}, user2Balance:${usersData.user2Balance.toString()}} + log(` + vault: { totalAssets: ${vaultData.totalAssets.toString()}, totalSupply: ${vaultData.totalSupply.toString()} , assetsPerShare: ${vaultData.assetsPerShare.toString()}, internalBalance: ${vaultData.internalBalance.toString()}} + users: { user1Balance: ${usersData.user1Balance.toString()} } + `) + if (curve3CrvBasicMetaVaultsData) { + log(`curve3CrvBasicMetaVaults: { usdcVaultBalance: ${curve3CrvBasicMetaVaultsData.usdcVaultBalance.toString()} } + `) + } + return { + vault: vaultData, + users: usersData, + curve3CrvBasicMetaVaults: curve3CrvBasicMetaVaultsData, + } +} +const snapCurve3CrvBasicMetaVaults = async (vaults: Curve3CrvBasicMetaVaults, accountAddress: string) => { + const snapVault = async (vault: Curve3CrvBasicMetaVault) => ({ + totalAssets: await vault.totalAssets(), + totalSupply: await vault.totalSupply(), + accountBalance: await vault.balanceOf(accountAddress), + }) + const vaultsData = { + usdc: await snapVault(vaults.usdc), + } + log(` + usdc: {totalAssets: ${vaultsData.usdc.totalAssets.toString()}, totalSupply: ${vaultsData.usdc.totalSupply.toString()} , accountBalance: ${vaultsData.usdc.accountBalance.toString()} } + `) + return vaultsData +} + +const snapshotVaults = async ( + convex3CrvLiquidatorVaults: Convex3CrvLiquidatorVaults, + periodicAllocationPerfFeeMetaVault: PeriodicAllocationPerfFeeMetaVault, + curve3CrvBasicMetaVaults: Curve3CrvBasicMetaVaults, + account: Account, +) => { + const accountAddress = account.address + return { + convex3CrvLiquidatorVaults: await snapConvex3CrvLiquidatorVaults( + convex3CrvLiquidatorVaults, + account, + periodicAllocationPerfFeeMetaVault.address, + ), + periodicAllocationPerfFeeMetaVault: await snapPeriodicAllocationPerfFeeMetaVault( + periodicAllocationPerfFeeMetaVault, + account, + curve3CrvBasicMetaVaults, + ), + curve3CrvBasicMetaVaults: await snapCurve3CrvBasicMetaVaults(curve3CrvBasicMetaVaults, accountAddress), + } +} + +describe("Save+ Basic and Meta Vaults - Shutdown", async () => { + let sa: StandardAccounts + let deployer: Signer + let governor: Account + let rewardsWhale: Account + let vaultManager: Account + let keeper: Account + let usdtWhale: Account + let usdcWhale: Account + let daiWhale: Account + let threeCrvWhale1: Account + let threeCrvWhale2: Account + // core smart contracts + let nexus: Nexus + let proxyAdmin: InstantProxyAdmin + // common smart contracts + let swapper: CowSwapDex + let liquidator: Liquidator + // external smart contracts + let threeCrvToken: IERC20Metadata + let cvxToken: IERC20Metadata + let crvToken: IERC20Metadata + let daiToken: IERC20Metadata + let usdcToken: IERC20Metadata + let usdtToken: IERC20Metadata + // mstable underlying vaults <= => convex + let musdConvexVault: Convex3CrvLiquidatorVault + let fraxConvexVault: Convex3CrvLiquidatorVault + let busdConvexVault: Convex3CrvLiquidatorVault + // meta vault <= => mstable underlying vaults + let periodicAllocationPerfFeeMetaVault: PeriodicAllocationPerfFeeMetaVault + // 4626 vaults <= => meta vault + let usdcMetaVault: Curve3CrvBasicMetaVault + + // custom types to ease unit testing + let curve3CrvBasicMetaVaults: Curve3CrvBasicMetaVaults + let convex3CrvLiquidatorVaults: Convex3CrvLiquidatorVaults + + let dataEmitter: DataEmitter + const { network } = hre + + const resetNetwork = async (blockNumber?: number) => { + // Only reset if using the in memory hardhat chain + // No need to reset if using a local fork node + if (network.name === "hardhat") { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env.NODE_URL, + blockNumber, + }, + }, + ], + }) + } + } + const setup = async () => { + await resetNetwork(16580000) + const accounts = await ethers.getSigners() + sa = await new StandardAccounts().initAccounts(accounts) + governor = await impersonateAccount(resolveAddress("Governor")) + sa.governor = governor + + threeCrvWhale1 = await impersonateAccount(threeCrvWhale1Address) + threeCrvWhale2 = await impersonateAccount(threeCrvWhale2Address) + sa.alice = threeCrvWhale1 + sa.bob = threeCrvWhale2 + + rewardsWhale = await impersonateAccount(rewardsWhaleAddress) + vaultManager = await impersonateAccount(resolveAddress("VaultManager")) + sa.vaultManager = vaultManager + keeper = await impersonateAccount(resolveAddress("OperationsSigner")) + sa.keeper = keeper + deployer = keeper.signer + + daiWhale = await impersonateAccount(daiWhaleAddress) + usdcWhale = await impersonateAccount(usdcWhaleAddress) + usdtWhale = await impersonateAccount(usdtWhaleAddress) + + const nexusAddress = resolveAddress("Nexus") + nexus = Nexus__factory.connect(nexusAddress, governor.signer) + const proxyAdminAddress = resolveAddress("InstantProxyAdmin") + + proxyAdmin = InstantProxyAdmin__factory.connect(proxyAdminAddress, governor.signer) + + // Deploy mocked contracts + ;({ swapper } = await deployMockAsyncSwapper(deployer, nexus)) + await swapper.connect(governor.signer).approveToken(CRV.address) + await swapper.connect(governor.signer).approveToken(CVX.address) + // swapper = CowSwapDex__factory.connect(resolveAddress("CowSwapDex"), deployer) + const syncSwapper = await deployMockSyncSwapper(deployer, nexus) + + // Deploy common / utilities contracts + liquidator = Liquidator__factory.connect(resolveAddress("LiquidatorV2"), keeper.signer) + + const convex3CrvVaults = { + musd: { address: resolveAddress("vcx3CRV-mUSD") }, + frax: { address: resolveAddress("vcx3CRV-FRAX") }, + busd: { address: resolveAddress("vcx3CRV-BUSD") }, + } + const curve3CrvMetaVaults = { + usdc: { address: resolveAddress("mvUSDC-3PCV") }, + } + + const savePlusConfig = { + periodicAllocationPerfFeeMetaVault: { address: resolveAddress("mv3CRV-CVX") }, + convex3CrvVaults, + curve3CrvMetaVaults, + } + + // 1.- underlying meta vaults capable of liquidate rewards + musdConvexVault = Convex3CrvLiquidatorVault__factory.connect(convex3CrvVaults.musd.address, deployer) + fraxConvexVault = Convex3CrvLiquidatorVault__factory.connect(convex3CrvVaults.frax.address, deployer) + busdConvexVault = Convex3CrvLiquidatorVault__factory.connect(convex3CrvVaults.busd.address, deployer) + + // 2.- save plus meta vault + periodicAllocationPerfFeeMetaVault = PeriodicAllocationPerfFeeMetaVault__factory.connect( + savePlusConfig.periodicAllocationPerfFeeMetaVault.address, + deployer, + ) + // 3.- 4626 Wrappers of the save plus meta vault + usdcMetaVault = Curve3CrvBasicMetaVault__factory.connect(curve3CrvMetaVaults.usdc.address, deployer) + + // Deploy mocked contracts + dataEmitter = await new DataEmitter__factory(deployer).deploy() + + threeCrvToken = IERC20Metadata__factory.connect(ThreeCRV.address, threeCrvWhale1.signer) + cvxToken = IERC20Metadata__factory.connect(CVX.address, rewardsWhale.signer) + crvToken = IERC20Metadata__factory.connect(CRV.address, rewardsWhale.signer) + daiToken = IERC20Metadata__factory.connect(DAI.address, daiWhale.signer) + usdcToken = IERC20Metadata__factory.connect(USDC.address, usdcWhale.signer) + usdtToken = IERC20Metadata__factory.connect(USDT.address, usdtWhale.signer) + + // Mock Balances on our lovely users + const musdTokenAddress = resolveAddress("mUSD") + const daiTokenAddress = DAI.address + const usdcTokenAddress = USDC.address + const usdtTokenAddress = USDT.address + const tokensToMockBalance = { musdTokenAddress, usdcTokenAddress, daiTokenAddress, usdtTokenAddress } + + await setBalancesToAccount(threeCrvWhale1, [] as ERC20[], tokensToMockBalance, 10000000000) + await setBalancesToAccount(threeCrvWhale2, [] as ERC20[], tokensToMockBalance, 10000000000) + + // Mock balances on swappers to simulate swaps + cvxToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) + crvToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) + daiToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) + usdcToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) + usdtToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) + + cvxToken.transfer(swapper.address, simpleToExactAmount(10000)) + crvToken.transfer(swapper.address, simpleToExactAmount(10000)) + daiToken.transfer(swapper.address, simpleToExactAmount(10000)) + usdcToken.transfer(swapper.address, simpleToExactAmount(10000)) + usdtToken.transfer(swapper.address, simpleToExactAmount(10000)) + + // Stakers approve vaults to take their tokens + await threeCrvToken.connect(threeCrvWhale1.signer).approve(periodicAllocationPerfFeeMetaVault.address, ethers.constants.MaxUint256) + await threeCrvToken.connect(threeCrvWhale2.signer).approve(periodicAllocationPerfFeeMetaVault.address, ethers.constants.MaxUint256) + + await usdcToken.connect(usdcWhale.signer).approve(usdcMetaVault.address, ethers.constants.MaxUint256) + await usdcToken.connect(threeCrvWhale2.signer).approve(usdcMetaVault.address, ethers.constants.MaxUint256) + + await usdtToken.connect(usdtWhale.signer).transfer(threeCrvWhale1.address, simpleToExactAmount(10000000, USDT.decimals)) + await usdtToken.connect(usdtWhale.signer).transfer(threeCrvWhale2.address, simpleToExactAmount(10000000, USDT.decimals)) + + // custom types to ease unit testing + convex3CrvLiquidatorVaults = { + musd: musdConvexVault, + frax: fraxConvexVault, + busd: busdConvexVault, + } + curve3CrvBasicMetaVaults = { + usdc: usdcMetaVault, + } + } + + const assertConvex3CrvVaultConfiguration = async (convex3CrvVault: Convex3CrvLiquidatorVault, convex3CrvPool: Convex3CrvPool) => { + const rewardTokens = await convex3CrvVault.rewardTokens() + expect(await convex3CrvVault.nexus(), "nexus").eq(nexus.address) + expect(await convex3CrvVault.metapool(), "curve Metapool").to.equal(convex3CrvPool.curveMetapool) + expect(await convex3CrvVault.metapoolToken(), "metapool token").to.equal(convex3CrvPool.curveMetapoolToken) + expect(await convex3CrvVault.basePool(), "3Pool pool").to.equal(curveThreePoolAddress) + expect(await convex3CrvVault.booster(), "booster").to.equal(convexBoosterAddress) + expect(await convex3CrvVault.convexPoolId(), "convex Pool Id").to.equal(convex3CrvPool.convexPoolId) + expect(await convex3CrvVault.baseRewardPool(), "base reward pool").to.equal(convex3CrvPool.convexRewardPool) + expect(rewardTokens[0], "reward tokens").to.equal(convex3CrvPool.rewardTokens[0]) + expect(rewardTokens[1], "reward tokens").to.equal(convex3CrvPool.rewardTokens[1]) + } + const assertCurve3CrvVaultConfiguration = async (curve3CrvVault: Curve3CrvBasicMetaVault, curve3CrvPool: Curve3CrvPool) => { + // check a minimum set of configurations + expect(await curve3CrvVault.nexus(), "nexus").eq(nexus.address) + expect(await curve3CrvVault.metaVault(), "underlying metaVault").to.equal(periodicAllocationPerfFeeMetaVault.address) + expect(await curve3CrvVault.asset(), "asset").to.equal(curve3CrvPool.asset) + expect(await curve3CrvVault.name(), "name").to.equal(curve3CrvPool.name) + expect(await curve3CrvVault.symbol(), "symbol").to.equal(curve3CrvPool.symbol) + expect(await curve3CrvVault.decimals(), "decimals").to.equal(18) + } + before("reset block number", async () => { + await loadOrExecFixture(setup) + }) + context("deployment check", async () => { + describe("proxy instant admin", async () => { + it("owner is the multisig governor", async () => { + expect(await proxyAdmin.owner(), "owner must be governor").to.be.eq(governorAddress) + }) + it("is the admin of all vaults proxies", async () => { + expect(await proxyAdmin.getProxyAdmin(musdConvexVault.address), "musd vault proxy admin").to.be.eq(proxyAdmin.address) + expect(await proxyAdmin.getProxyAdmin(fraxConvexVault.address), "frax vault proxy admin").to.be.eq(proxyAdmin.address) + expect(await proxyAdmin.getProxyAdmin(busdConvexVault.address), "busd vault proxy admin").to.be.eq(proxyAdmin.address) + }) + }) + describe("Convex 3Crv Liquidator Vaults", async () => { + it("musd should properly store valid arguments", async () => { + await assertConvex3CrvVaultConfiguration(musdConvexVault, config.convex3CrvPools.musd) + }) + it("busd should properly store valid arguments", async () => { + await assertConvex3CrvVaultConfiguration(busdConvexVault, config.convex3CrvPools.busd) + }) + it("frax should properly store valid arguments", async () => { + await assertConvex3CrvVaultConfiguration(fraxConvexVault, config.convex3CrvPools.frax) + }) + }) + describe("Curve 3CRV Convex Meta Vault", async () => { + it("constructor data", async () => { + expect(await periodicAllocationPerfFeeMetaVault.nexus(), "nexus").eq(nexus.address) + expect(await periodicAllocationPerfFeeMetaVault.asset(), "asset").to.equal(config.periodicAllocationPerfFeeMetaVault.asset) + }) + + it("initialize data", async () => { + // initialize + expect(await periodicAllocationPerfFeeMetaVault.name(), "name").to.equal(config.periodicAllocationPerfFeeMetaVault.name) + expect(await periodicAllocationPerfFeeMetaVault.symbol(), "symbol").to.equal( + config.periodicAllocationPerfFeeMetaVault.symbol, + ) + expect(await periodicAllocationPerfFeeMetaVault.decimals(), "decimals").to.equal(18) + expect(await periodicAllocationPerfFeeMetaVault.vaultManager(), "vaultManager").to.equal(vaultManager.address) + expect(await periodicAllocationPerfFeeMetaVault.performanceFee(), "performanceFee").to.equal( + config.periodicAllocationPerfFeeMetaVault.performanceFee, + ) + expect(await periodicAllocationPerfFeeMetaVault.feeReceiver(), "feeReceiver").to.equal(feeReceiver) + + expect(await periodicAllocationPerfFeeMetaVault.resolveVaultIndex(0), "underlying vault 0").to.equal( + fraxConvexVault.address, + ) + expect(await periodicAllocationPerfFeeMetaVault.resolveVaultIndex(1), "underlying vault 1").to.equal( + musdConvexVault.address, + ) + expect(await periodicAllocationPerfFeeMetaVault.resolveVaultIndex(2), "underlying vault 2").to.equal( + busdConvexVault.address, + ) + expect(await periodicAllocationPerfFeeMetaVault.assetPerShareUpdateThreshold(), "assetPerShareUpdateThreshold").to.equal( + config.periodicAllocationPerfFeeMetaVault.assetPerShareUpdateThreshold, + ) + const sourceParams = await periodicAllocationPerfFeeMetaVault.sourceParams() + expect(sourceParams.singleSourceVaultIndex, "singleSourceVaultIndex").to.equal( + config.periodicAllocationPerfFeeMetaVault.sourceParams.singleSourceVaultIndex, + ) + expect(sourceParams.singleVaultSharesThreshold, "singleVaultSharesThreshold").to.equal( + config.periodicAllocationPerfFeeMetaVault.sourceParams.singleVaultSharesThreshold, + ) + }) + }) + describe("Curve 3Crv Meta Vaults", async () => { + // 4626 Wrappers that facilitate deposit / withdraw USDC | DAI| USDT + it("usdc should properly store valid arguments", async () => { + await assertCurve3CrvVaultConfiguration(usdcMetaVault, config.curve3CrvMetaVault.usdc) + }) + }) + }) + context("upgrade vaults", async () => { + // mstable underlying vaults <= => convex + let musdConvexVaultImpl: Convex3CrvLiquidatorVault + let fraxConvexVaultImpl: Convex3CrvLiquidatorVault + let busdConvexVaultImpl: Convex3CrvLiquidatorVault + // meta vault <= => mstable underlying vaults + let periodicAllocationPerfFeeMetaVaultImpl: PeriodicAllocationPerfFeeMetaVault + // 4626 vaults <= => meta vault + let usdcMetaVaultImpl: Curve3CrvBasicMetaVault + + describe("propose / accepts upgrades", async () => { + it("deploys new vaults", async () => { + // 1.- underlying meta vaults capable of liquidate rewards + const deployConvex3CrvLiquidatorVaultImpl = async (reference: Convex3CrvLiquidatorVault, calculatorLibrary: string) => { + return await deployConvex3CrvLiquidatorVault(hre, deployer, { + calculatorLibrary, + nexus: nexus.address, + asset: await reference.asset(), + constructorData: { + metapool: await reference.metapool(), + booster: await reference.booster(), + convexPoolId: await reference.convexPoolId(), + }, + name: await reference.name(), + symbol: await reference.symbol(), + streamDuration: (await reference.STREAM_DURATION()).toNumber(), + proxy: false, + }) + } + fraxConvexVaultImpl = ( + await deployConvex3CrvLiquidatorVaultImpl(fraxConvexVault, resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary")) + ).impl + musdConvexVaultImpl = ( + await deployConvex3CrvLiquidatorVaultImpl(musdConvexVault, resolveAddress("Curve3CrvMetapoolCalculatorLibrary")) + ).impl + busdConvexVaultImpl = ( + await deployConvex3CrvLiquidatorVaultImpl(busdConvexVault, resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary")) + ).impl + + // 2.- save plus meta vault + const periodicAllocationPerfFeeMetaVaultUpgrade = await deployPeriodicAllocationPerfFeeMetaVault(hre, deployer, { + nexus: nexus.address, + asset: await periodicAllocationPerfFeeMetaVault.asset(), + proxy: false, + }) + periodicAllocationPerfFeeMetaVaultImpl = periodicAllocationPerfFeeMetaVaultUpgrade.impl + + // 3.- 4626 Wrappers of the save plus meta vault + const usdcMetaVaultUpgrade = await deployCurve3CrvMetaVault(hre, deployer, { + calculatorLibrary: resolveAddress("Curve3CrvCalculatorLibrary"), + nexus: nexus.address, + asset: usdcToken.address, + metaVault: periodicAllocationPerfFeeMetaVault.address, + proxy: false, + }) + usdcMetaVaultImpl = usdcMetaVaultUpgrade.impl + }) + it("pause all vaults", async () => { + await musdConvexVault.connect(governor.signer).pause() + await fraxConvexVault.connect(governor.signer).pause() + await busdConvexVault.connect(governor.signer).pause() + + expect(await musdConvexVault.paused(), " musd convex vault paused").to.be.eq(true) + expect(await fraxConvexVault.paused(), " frax convex vault paused").to.be.eq(true) + expect(await busdConvexVault.paused(), " busd convex vault paused").to.be.eq(true) + + await periodicAllocationPerfFeeMetaVault.connect(governor.signer).pause() + expect(await periodicAllocationPerfFeeMetaVault.paused(), " periodic allocation vault paused").to.be.eq(true) + + await usdcMetaVault.connect(governor.signer).pause() + expect(await usdcMetaVault.paused(), " usdc curve vault paused").to.be.eq(true) + }) + it("upgrade contracts", async () => { + await proxyAdmin.upgrade(musdConvexVault.address, musdConvexVaultImpl.address) + await proxyAdmin.upgrade(fraxConvexVault.address, fraxConvexVaultImpl.address) + await proxyAdmin.upgrade(busdConvexVault.address, busdConvexVaultImpl.address) + + expect(await proxyAdmin.getProxyImplementation(musdConvexVault.address), "musd proxy convex vault updated").to.be.eq( + musdConvexVaultImpl.address, + ) + expect(await proxyAdmin.getProxyImplementation(fraxConvexVault.address), "frax proxy convex vault updated").to.be.eq( + fraxConvexVaultImpl.address, + ) + expect(await proxyAdmin.getProxyImplementation(busdConvexVault.address), "busd proxy convex vault updated").to.be.eq( + busdConvexVaultImpl.address, + ) + + await proxyAdmin.upgrade(periodicAllocationPerfFeeMetaVault.address, periodicAllocationPerfFeeMetaVaultImpl.address) + expect( + await proxyAdmin.getProxyImplementation(periodicAllocationPerfFeeMetaVault.address), + "periodic allocation proxy convex vault updated", + ).to.be.eq(periodicAllocationPerfFeeMetaVaultImpl.address) + + await proxyAdmin.upgrade(usdcMetaVault.address, usdcMetaVaultImpl.address) + expect(await proxyAdmin.getProxyImplementation(usdcMetaVault.address), "usdc proxy convex vault updated").to.be.eq( + usdcMetaVaultImpl.address, + ) + }) + it("unpause all vaults", async () => { + await musdConvexVault.connect(governor.signer).unpause() + await fraxConvexVault.connect(governor.signer).unpause() + await busdConvexVault.connect(governor.signer).unpause() + + expect(await musdConvexVault.paused(), " musd convex vault paused").to.be.eq(false) + expect(await fraxConvexVault.paused(), " frax convex vault paused").to.be.eq(false) + expect(await busdConvexVault.paused(), " busd convex vault paused").to.be.eq(false) + + await periodicAllocationPerfFeeMetaVault.connect(governor.signer).unpause() + expect(await periodicAllocationPerfFeeMetaVault.paused(), " periodic allocation vault paused").to.be.eq(false) + + await usdcMetaVault.connect(governor.signer).unpause() + expect(await usdcMetaVault.paused(), " usdc curve vault paused").to.be.eq(false) + }) + }) + }) + context("remove liquidity from yield sources", async () => { + it("PeriodicAllocationPerfFeeMetaVault remove underlying vaults", async () => { + const totalSupplyBefore = await periodicAllocationPerfFeeMetaVault.totalSupply() + const totalAssetsBefore = await periodicAllocationPerfFeeMetaVault.totalAssets() + + await periodicAllocationPerfFeeMetaVault.connect(governor.signer).removeVault(2) //busd + await periodicAllocationPerfFeeMetaVault.connect(governor.signer).removeVault(1) //musd + await periodicAllocationPerfFeeMetaVault.connect(governor.signer).removeVault(0) //Frax + + const totalAssetsAfter = await periodicAllocationPerfFeeMetaVault.totalAssets() + const totalSupplyAfter = await periodicAllocationPerfFeeMetaVault.totalSupply() + + expect(totalAssetsBefore, "total assets").to.be.eq(totalAssetsAfter) + expect(totalSupplyBefore, "total supply").to.be.eq(totalSupplyAfter) + }) + it("Curve3CrvBasicMetaVault withdraw from convex", async () => { + const totalAssetsBefore = await usdcMetaVault.totalAssets() + const totalSupplyBefore = await usdcMetaVault.totalSupply() + + await usdcMetaVault.connect(governor.signer).liquidateUnderlyingVault(ZERO) + + const totalAssetsAfter = await usdcMetaVault.totalAssets() + const totalSupplyAfter = await usdcMetaVault.totalSupply() + expect(totalSupplyBefore, "total supply").to.be.eq(totalSupplyAfter) + assertBNClose(totalAssetsBefore, totalAssetsAfter, simpleToExactAmount(3, 5), "total assets") + + const usdcMetavaultBalance = await periodicAllocationPerfFeeMetaVault.balanceOf(usdcMetaVault.address) + expect(usdcMetavaultBalance, "usdc metavault balance on underlying vault").to.be.eq(0) + }) + }) + // ------------------------------------------------------------------// + // ------------------------ VERIFY BEHAVIORS ------------------------// + // ------------------------------------------------------------------// + ;["vcx3CRV-mUSD", "vcx3CRV-FRAX", "vcx3CRV-BUSD"].forEach((vaultSymbol) => { + context(`Convex3CrvLiquidatorVault ${vaultSymbol}`, async () => { + let holder: Account + let convex3CrvLiquidatorVault: Convex3CrvLiquidatorVault + before("", async () => { + holder = await impersonateAccount(feeReceiver) + convex3CrvLiquidatorVault = Convex3CrvLiquidatorVault__factory.connect(resolveAddress(vaultSymbol), deployer) + }) + describe("liquidate assets", () => { + before(async () => { + await threeCrvToken.connect(holder.signer).approve(convex3CrvLiquidatorVault.address, ethers.constants.MaxUint256) + }) + it("whale should fail to liquidate vault", async () => { + const tx = convex3CrvLiquidatorVault.connect(holder.signer).liquidateVault(0) + await expect(tx).to.be.revertedWith("Only governor can execute") + }) + it("vault manager should fail to liquidate vault", async () => { + const tx = convex3CrvLiquidatorVault.connect(vaultManager.signer).liquidateVault(0) + await expect(tx).to.be.revertedWith("Only governor can execute") + }) + }) + it("reset allowances", async () => { + await convex3CrvLiquidatorVault.connect(governor.signer).resetAllowances() + }) + describe("basic flow", () => { + it("deposit 3Crv reverted", async () => { + await assertVaultDeposit( + threeCrvWhale1, + threeCrvToken, + convex3CrvLiquidatorVault, + simpleToExactAmount(50000, ThreeCRV.decimals), + ) + }) + it("mint shares reverted", async () => { + await assertVaultMint( + threeCrvWhale1, + threeCrvToken, + convex3CrvLiquidatorVault, + dataEmitter, + simpleToExactAmount(70000, ThreeCRV.decimals), + ) + }) + it("partial withdraw", async () => { + await assertVaultWithdraw(holder, threeCrvToken, convex3CrvLiquidatorVault, simpleToExactAmount(50, ThreeCRV.decimals)) + }) + it("partial redeem", async () => { + await assertVaultRedeem( + holder, + threeCrvToken, + convex3CrvLiquidatorVault, + dataEmitter, + simpleToExactAmount(50, ThreeCRV.decimals), + ) + }) + it("total redeem", async () => { + await assertVaultRedeem(holder, threeCrvToken, convex3CrvLiquidatorVault, dataEmitter) + }) + }) + }) + }) + context("PeriodicAllocationPerfFeeMetaVault", async () => { + let holder: Account + before("", async () => { + holder = await impersonateAccount(feeReceiver) + }) + describe("basic flow", () => { + it("deposit 3Crv reverted", async () => { + await assertVaultDeposit( + threeCrvWhale1, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + simpleToExactAmount(50000, ThreeCRV.decimals), + ) + }) + it("mint shares reverted", async () => { + await assertVaultMint( + threeCrvWhale1, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + dataEmitter, + simpleToExactAmount(70000, ThreeCRV.decimals), + ) + }) + it("partial withdraw", async () => { + await assertVaultWithdraw( + holder, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + simpleToExactAmount(100, ThreeCRV.decimals), + ) + }) + it("partial redeem", async () => { + await assertVaultRedeem( + holder, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + dataEmitter, + simpleToExactAmount(100, ThreeCRV.decimals), + ) + }) + it("total redeem", async () => { + await assertVaultRedeem(holder, threeCrvToken, periodicAllocationPerfFeeMetaVault, dataEmitter) + const vaultsDataAfter = await snapshotVaults( + convex3CrvLiquidatorVaults, + periodicAllocationPerfFeeMetaVault, + curve3CrvBasicMetaVaults, + holder, + ) + // Expect all liquidity to be removed + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.users.user1Balance, "user balance").to.be.eq(0) + }) + }) + }) + context("Curve3CrvBasicMetaVault", async () => { + let vaultsDataBefore + beforeEach("snap data", async () => { + vaultsDataBefore = await snapshotVaults( + convex3CrvLiquidatorVaults, + periodicAllocationPerfFeeMetaVault, + curve3CrvBasicMetaVaults, + threeCrvWhale1, + ) + }) + describe("basic flow", () => { + it("deposit erc20Token", async () => { + // When deposit via 4626MetaVault + await assertVaultDeposit(usdcWhale, usdcToken, usdcMetaVault, simpleToExactAmount(50000, USDC.decimals)) + + const vaultsDataAfter = await snapshotVaults( + convex3CrvLiquidatorVaults, + periodicAllocationPerfFeeMetaVault, + curve3CrvBasicMetaVaults, + threeCrvWhale1, + ) + // Then periodicAllocationPerfFeeMetaVault supply eq + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.totalAssets, "meta vault totalAssets").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.totalAssets, + ) + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.totalSupply, "meta vault totalSupply").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.totalSupply, + ) + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.internalBalance, "meta vault internalBalance").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.internalBalance, + ) + + // The 4626MetaVault's shares on the meta vault eq + const { curve3CrvBasicMetaVaults: dataBefore } = vaultsDataBefore.periodicAllocationPerfFeeMetaVault + const { curve3CrvBasicMetaVaults: dataAfter } = vaultsDataAfter.periodicAllocationPerfFeeMetaVault + expect(dataAfter.usdcVaultBalance, "meta vault usdc vault balance").to.be.eq(dataBefore.usdcVaultBalance) + // no change on underlying vaults + }) + it("mint shares", async () => { + // When mint via 4626MetaVault + await assertVaultMint(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, simpleToExactAmount(70000, ThreeCRV.decimals)) + + const vaultsDataAfter = await snapshotVaults( + convex3CrvLiquidatorVaults, + periodicAllocationPerfFeeMetaVault, + curve3CrvBasicMetaVaults, + threeCrvWhale1, + ) + // Then periodicAllocationPerfFeeMetaVault supply eq + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.totalAssets, "meta vault totalAssets").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.totalAssets, + ) + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.totalSupply, "meta vault totalSupply").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.totalSupply, + ) + expect(vaultsDataAfter.periodicAllocationPerfFeeMetaVault.vault.internalBalance, "meta vault internalBalance").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.vault.internalBalance, + ) + + // The 4626MetaVault's shares on the meta vault eq + const { curve3CrvBasicMetaVaults: dataAfter } = vaultsDataAfter.periodicAllocationPerfFeeMetaVault + expect(dataAfter.usdcVaultBalance, "meta vault usdc vault balance").to.be.eq( + vaultsDataBefore.periodicAllocationPerfFeeMetaVault.curve3CrvBasicMetaVaults.usdcVaultBalance, + ) + // no change on underlying vaults + }) + it("partial withdraw", async () => { + await assertVaultWithdraw(usdcWhale, usdcToken, usdcMetaVault, simpleToExactAmount(60000, USDC.decimals)) + // no change on underlying vaults + }) + it("partial redeem", async () => { + await assertVaultRedeem(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, simpleToExactAmount(7000, ThreeCRV.decimals)) + // no change on underlying vaults + }) + it("total redeem", async () => { + await assertVaultRedeem(usdcWhale, usdcToken, usdcMetaVault, dataEmitter) + + // 4626 + expect(await usdcMetaVault.balanceOf(daiWhale.address), "usdc vault user balance").to.be.eq(0) + }) + }) + }) +}) diff --git a/test/vault/liquidator/Liquidator.spec.ts b/test/vault/liquidator/Liquidator.spec.ts index f0798de..a9c476f 100644 --- a/test/vault/liquidator/Liquidator.spec.ts +++ b/test/vault/liquidator/Liquidator.spec.ts @@ -11,11 +11,9 @@ import { Liquidator__factory, LiquidatorBasicVault__factory, MockERC20__factory, - MockNexus__factory, MockLiquidatorMaliciousVault__factory, - MockLiquidatorMaliciousVault, MockMaliciousDexSwap__factory, - MockMaliciousDexSwap, + MockNexus__factory, } from "types/generated" import { buildDonateTokensInput } from "../../../tasks/utils/liquidatorUtil" @@ -31,6 +29,7 @@ import type { MockGPv2VaultRelayer, MockNexus, } from "types" +import type { MockLiquidatorMaliciousVault, MockMaliciousDexSwap } from "types/generated" const ERROR = { ALREADY_INITIALIZED: "Initializable: contract is already initialized", diff --git a/test/vault/liquidity/curve/Curve3CrvBasicMetaVault.spec.ts b/test/vault/liquidity/curve/Curve3CrvBasicMetaVault.spec.ts index b023874..e33a5b8 100644 --- a/test/vault/liquidity/curve/Curve3CrvBasicMetaVault.spec.ts +++ b/test/vault/liquidity/curve/Curve3CrvBasicMetaVault.spec.ts @@ -3,8 +3,9 @@ import { ContractMocks, StandardAccounts } from "@utils/machines" import { expect } from "chai" import { ethers } from "hardhat" import { Curve3CrvBasicMetaVault__factory, Curve3PoolCalculatorLibrary__factory } from "types/generated" -import type { MockERC20, MockNexus, Curve3CrvBasicMetaVault } from "types/generated" -import { Curve3CrvBasicMetaVaultLibraryAddresses } from "types/generated/factories/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault__factory" + +import type { Curve3CrvBasicMetaVault, MockERC20, MockNexus } from "types/generated" +import type { Curve3CrvBasicMetaVaultLibraryAddresses } from "types/generated/factories/contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault__factory" describe("Curve3CrvBasicMetaVault", () => { /* -- Declare shared variables -- */ From 6296f37f40298a85ad6bfcd541cb43604840b592 Mon Sep 17 00:00:00 2001 From: doncesarts Date: Mon, 10 Apr 2023 11:15:09 +0100 Subject: [PATCH 2/2] updates hh task to shutdown the metavault --- tasks/convex3CrvMetaVault.ts | 62 +++--- tasks/convex3CrvVault.ts | 46 ++++ tasks/curve3CrvVault.ts | 54 +++-- test-fork/vault/savePlusShutdown.spec.ts | 254 ++++++++--------------- 4 files changed, 205 insertions(+), 211 deletions(-) diff --git a/tasks/convex3CrvMetaVault.ts b/tasks/convex3CrvMetaVault.ts index 48aa048..4689666 100644 --- a/tasks/convex3CrvMetaVault.ts +++ b/tasks/convex3CrvMetaVault.ts @@ -18,7 +18,6 @@ import type { AssetProxy, PeriodicAllocationPerfFeeMetaVault } from "types/gener import type { Convex3CrvVaultsDeployed } from "./deployment/convex3CrvVaults" -// deployPeriodicAllocationPerfFeeMetaVault interface AssetSourcingParams { singleVaultSharesThreshold: number singleSourceVaultIndex: number @@ -124,8 +123,8 @@ export const deployPeriodicAllocationPerfFeeMetaVault = async ( } subtask("convex-3crv-mv-deploy", "Deploys Convex 3Crv Meta Vault") - .addParam("vaults", "Comma separated symbols or addresses of the underlying convex vaults", undefined, types.string) - .addParam( + .addOptionalParam("vaults", "Comma separated symbols or addresses of the underlying convex vaults", undefined, types.string) + .addOptionalParam( "singleSource", "Token symbol or address of the vault that smaller withdraws should be sourced from.", undefined, @@ -172,32 +171,43 @@ subtask("convex-3crv-mv-deploy", "Deploys Convex 3Crv Meta Vault") const proxyAdminAddress = resolveAddress(admin, chain) const vaultManagerAddress = resolveAddress(vaultManager, chain) - const underlyings = vaults.split(",") - const underlyingAddresses = underlyings.map((underlying) => resolveAddress(underlying, chain)) - const singleSourceAddress = resolveAddress(singleSource, chain) - const singleSourceVaultIndex = underlyingAddresses.indexOf(singleSourceAddress) + if (proxy) { + const underlyings = vaults.split(",") + const underlyingAddresses = underlyings.map((underlying) => resolveAddress(underlying, chain)) + const singleSourceAddress = resolveAddress(singleSource, chain) + const singleSourceVaultIndex = underlyingAddresses.indexOf(singleSourceAddress) - const feeReceiverAddress = resolveAddress(feeReceiver, chain) + const feeReceiverAddress = resolveAddress(feeReceiver, chain) - const { proxy: proxyContract, impl } = await deployPeriodicAllocationPerfFeeMetaVault(hre, signer, { - nexus: nexusAddress, - asset: assetToken.address, - name, - symbol, - vaultManager: vaultManagerAddress, - proxyAdmin: proxyAdminAddress, - feeReceiver: feeReceiverAddress, - performanceFee: fee, - underlyingVaults: underlyingAddresses, - sourceParams: { - singleVaultSharesThreshold: singleThreshold, - singleSourceVaultIndex, - }, - assetPerShareUpdateThreshold: simpleToExactAmount(updateThreshold, assetToken.decimals), - proxy, - }) + const { proxy: proxyContract, impl } = await deployPeriodicAllocationPerfFeeMetaVault(hre, signer, { + nexus: nexusAddress, + asset: assetToken.address, + name, + symbol, + vaultManager: vaultManagerAddress, + proxyAdmin: proxyAdminAddress, + feeReceiver: feeReceiverAddress, + performanceFee: fee, + underlyingVaults: underlyingAddresses, + sourceParams: { + singleVaultSharesThreshold: singleThreshold, + singleSourceVaultIndex, + }, + assetPerShareUpdateThreshold: simpleToExactAmount(updateThreshold, assetToken.decimals), + proxy, + }) + + return { proxy: proxyContract, impl } + } else { + const { proxy: proxyContract, impl } = await deployPeriodicAllocationPerfFeeMetaVault(hre, signer, { + nexus: nexusAddress, + asset: assetToken.address, + proxy, + }) + console.log(`New ${asset} vault implementation deployed at ${impl.address}`) - return { proxy: proxyContract, impl } + return { proxy: proxyContract, impl } + } }) task("convex-3crv-mv-deploy").setAction(async (_, __, runSuper) => { return runSuper() diff --git a/tasks/convex3CrvVault.ts b/tasks/convex3CrvVault.ts index 992700d..87ef940 100644 --- a/tasks/convex3CrvVault.ts +++ b/tasks/convex3CrvVault.ts @@ -154,6 +154,7 @@ export async function deployConvex3CrvLiquidatorVault( address: vaultImpl.address, contract: "contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol:Convex3CrvLiquidatorVault", constructorArguments: constructorArguments, + libraries: linkAddresses, }) // Proxy @@ -178,6 +179,28 @@ export async function deployConvex3CrvLiquidatorVault( return { proxy: proxyContract, impl: vaultImpl } } +export async function deployConvex3CrvLiquidatorVaultCopy( + hre: HardhatRuntimeEnvironment, + signer: Signer, + reference: Convex3CrvLiquidatorVault, + calculatorLibrary: string, +) { + return deployConvex3CrvLiquidatorVault(hre, signer, { + calculatorLibrary, + nexus: await reference.nexus(), + asset: await reference.asset(), + constructorData: { + metapool: await reference.metapool(), + booster: await reference.booster(), + convexPoolId: await reference.convexPoolId(), + }, + name: await reference.name(), + symbol: await reference.symbol(), + streamDuration: (await reference.STREAM_DURATION()).toNumber(), + proxy: false, + }) +} + subtask("convex-3crv-lib-deploy", "Deploys a Curve Metapool calculator library") .addOptionalParam("factory", "Is the Curve Metapool a factory pool", false, types.boolean) .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) @@ -384,3 +407,26 @@ subtask("convex-3crv-snap", "Logs Convex 3Crv Vault details") task("convex-3crv-snap").setAction(async (_, __, runSuper) => { return runSuper() }) + +subtask("convex-3crv-vault-deploy-copy", "Deploys a new Convex 3Crv Liquidator Vault ") + .addParam("vault", "Vault symbol or address", undefined, types.string) + .addParam("calculatorLibrary", "Name or address of the Curve calculator library", undefined, types.string) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const { calculatorLibrary, vault, speed } = taskArgs + + const signer = await getSigner(hre, speed) + const chain = getChain(hre) + + const calculatorLibraryAddress = resolveAddress(calculatorLibrary, chain) + const vaultToken = await resolveAssetToken(signer, chain, vault) + const vaultContract = Convex3CrvLiquidatorVault__factory.connect(vaultToken.address, signer) + + const result = await deployConvex3CrvLiquidatorVaultCopy(hre, signer, vaultContract, calculatorLibraryAddress) + console.log(`New ${vault} implementation deployed at ${result.impl.address}`) + + return result + }) +task("convex-3crv-vault-deploy-copy").setAction(async (_, __, runSuper) => { + return runSuper() +}) diff --git a/tasks/curve3CrvVault.ts b/tasks/curve3CrvVault.ts index 6c053fc..ccf5b0e 100644 --- a/tasks/curve3CrvVault.ts +++ b/tasks/curve3CrvVault.ts @@ -80,6 +80,7 @@ export const deployCurve3CrvMetaVault = async (hre: HardhatRuntimeEnvironment, s address: vaultImpl.address, contract: "contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol:Curve3CrvBasicMetaVault", constructorArguments: constructorArguments, + libraries: libraryAddresses, }) // Proxy @@ -144,9 +145,9 @@ task("curve-3crv-lib-deploy").setAction(async (_, __, runSuper) => { }) subtask("curve-3crv-meta-vault-deploy", "Deploys Curve 3Pool Meta Vault") - .addParam("name", "Meta Vault name", undefined, types.string) - .addParam("symbol", "Meta Vault symbol", undefined, types.string) - .addParam("asset", "Token address or symbol of the vault's asset. eg DAI, USDC or USDT", undefined, types.string) + .addOptionalParam("name", "Meta Vault name", undefined, types.string) + .addOptionalParam("symbol", "Meta Vault symbol", undefined, types.string) + .addOptionalParam("asset", "Token address or symbol of the vault's asset. eg DAI, USDC or USDT", undefined, types.string) .addOptionalParam("metaVault", "Underlying Meta Vault override", "mv3CRV-CVX", types.string) .addOptionalParam("admin", "Instant or delayed proxy admin: InstantProxyAdmin | DelayedProxyAdmin", "InstantProxyAdmin", types.string) .addOptionalParam("calculatorLibrary", "Name or address of the Curve calculator library.", "Curve3CrvCalculatorLibrary", types.string) @@ -162,25 +163,38 @@ subtask("curve-3crv-meta-vault-deploy", "Deploys Curve 3Pool Meta Vault") const nexusAddress = resolveAddress("Nexus", chain) const assetToken = await resolveAssetToken(signer, chain, asset) - const proxyAdminAddress = resolveAddress(admin, chain) - const vaultManagerAddress = resolveAddress(vaultManager, chain) const metaVaultAddress = resolveAddress(metaVault, chain) const calculatorLibraryAddress = resolveAddress(calculatorLibrary, chain) - - const { proxy: proxyContract, impl } = await deployCurve3CrvMetaVault(hre, signer, { - nexus: nexusAddress, - asset: assetToken.address, - name, - symbol, - metaVault: metaVaultAddress, - vaultManager: vaultManagerAddress, - proxyAdmin: proxyAdminAddress, - slippageData: { mint: slippage, deposit: slippage, redeem: slippage, withdraw: slippage }, - calculatorLibrary: calculatorLibraryAddress, - proxy, - }) - - return { proxyContract, impl } + if (proxy) { + if (!(!!name && !!symbol && !!asset)) throw new Error("When proxy is true, name, symbol and asset are mandatory") + const proxyAdminAddress = resolveAddress(admin, chain) + const vaultManagerAddress = resolveAddress(vaultManager, chain) + + const { proxy: proxyContract, impl } = await deployCurve3CrvMetaVault(hre, signer, { + nexus: nexusAddress, + asset: assetToken.address, + name, + symbol, + metaVault: metaVaultAddress, + vaultManager: vaultManagerAddress, + proxyAdmin: proxyAdminAddress, + slippageData: { mint: slippage, deposit: slippage, redeem: slippage, withdraw: slippage }, + calculatorLibrary: calculatorLibraryAddress, + proxy, + }) + + return { proxyContract, impl } + } else { + const { proxy: proxyContract, impl } = await deployCurve3CrvMetaVault(hre, signer, { + nexus: nexusAddress, + asset: assetToken.address, + metaVault: metaVaultAddress, + calculatorLibrary: calculatorLibraryAddress, + proxy, + }) + console.log(`New ${asset} vault implementation deployed at ${impl.address}`) + return { proxyContract, impl } + } }) task("curve-3crv-meta-vault-deploy").setAction(async (_, __, runSuper) => { return runSuper() diff --git a/test-fork/vault/savePlusShutdown.spec.ts b/test-fork/vault/savePlusShutdown.spec.ts index b420f45..cf80e8c 100644 --- a/test-fork/vault/savePlusShutdown.spec.ts +++ b/test-fork/vault/savePlusShutdown.spec.ts @@ -1,28 +1,25 @@ +import { deployPeriodicAllocationPerfFeeMetaVault } from "@tasks/convex3CrvMetaVault" +import { deployConvex3CrvLiquidatorVaultCopy } from "@tasks/convex3CrvVault" +import { deployCurve3CrvMetaVault } from "@tasks/curve3CrvVault" import { config } from "@tasks/deployment/convex3CrvVaults-config" import { logger } from "@tasks/utils/logger" import { resolveAddress } from "@tasks/utils/networkAddressFactory" -import { assertBNClose } from "@utils/assertions" -import { DEAD_ADDRESS, ONE_HOUR, ONE_WEEK, ZERO } from "@utils/constants" +import { assertBNClose, assertBNClosePercent } from "@utils/assertions" +import { ONE_HOUR, ZERO } from "@utils/constants" import { impersonateAccount, loadOrExecFixture, setBalancesToAccount } from "@utils/fork" import { StandardAccounts } from "@utils/machines" -import { BN, simpleToExactAmount } from "@utils/math" +import { simpleToExactAmount } from "@utils/math" import { increaseTime } from "@utils/time" import { expect } from "chai" -import { keccak256, toUtf8Bytes } from "ethers/lib/utils" import * as hre from "hardhat" import { ethers } from "hardhat" import { - BasicDexSwap__factory, Convex3CrvLiquidatorVault__factory, - CowSwapDex__factory, Curve3CrvBasicMetaVault__factory, DataEmitter__factory, IERC20__factory, IERC20Metadata__factory, InstantProxyAdmin__factory, - Liquidator__factory, - MockGPv2Settlement__factory, - MockGPv2VaultRelayer__factory, Nexus__factory, PeriodicAllocationPerfFeeMetaVault__factory, } from "types/generated" @@ -30,21 +27,9 @@ import { import { CRV, CVX, DAI, logTxDetails, ThreeCRV, USDC, usdFormatter, USDT } from "../../tasks/utils" import type { BigNumber, Signer } from "ethers" -import type { - Convex3CrvLiquidatorVault, - Convex3CrvPool, - CowSwapDex, - Curve3CrvBasicMetaVault, - Curve3CrvPool, - DataEmitter, - Liquidator, - Nexus, -} from "types" +import type { Convex3CrvLiquidatorVault, Convex3CrvPool, Curve3CrvBasicMetaVault, Curve3CrvPool, DataEmitter, Nexus } from "types" import type { Account, AnyVault } from "types/common" import type { ERC20, IERC20Metadata, InstantProxyAdmin, PeriodicAllocationPerfFeeMetaVault } from "types/generated" -import { deployCurve3CrvMetaVault } from "@tasks/curve3CrvVault" -import { deployPeriodicAllocationPerfFeeMetaVault } from "@tasks/convex3CrvMetaVault" -import { deployConvex3CrvLiquidatorVault } from "@tasks/convex3CrvVault" const log = logger("test:savePlus") @@ -68,35 +53,7 @@ interface Convex3CrvLiquidatorVaults { interface Curve3CrvBasicMetaVaults { usdc: Curve3CrvBasicMetaVault } -async function deployMockSyncSwapper(deployer: Signer, nexus: Nexus) { - const exchanges = [ - { from: CRV.address, to: DAI.address, rate: simpleToExactAmount(62, 16) }, - { from: CVX.address, to: DAI.address, rate: simpleToExactAmount(57, 16) }, - { from: CRV.address, to: USDC.address, rate: simpleToExactAmount(61, 4) }, - { from: CVX.address, to: USDC.address, rate: simpleToExactAmount(56, 4) }, - { from: CRV.address, to: USDT.address, rate: simpleToExactAmount(63, 4) }, - { from: CVX.address, to: USDT.address, rate: simpleToExactAmount(58, 4) }, - ] - const swapper = await new BasicDexSwap__factory(deployer).deploy(nexus.address) - await swapper.initialize(exchanges) - return swapper -} -async function deployMockAsyncSwapper(deployer: Signer, nexus: Nexus) { - const gpv2Settlement = await new MockGPv2Settlement__factory(deployer).deploy() - const relayer = await new MockGPv2VaultRelayer__factory(deployer).deploy(DEAD_ADDRESS) - await relayer.initialize([ - { from: CRV.address, to: DAI.address, rate: simpleToExactAmount(62, 16) }, - { from: CVX.address, to: DAI.address, rate: simpleToExactAmount(57, 16) }, - { from: CRV.address, to: USDC.address, rate: simpleToExactAmount(61, 4) }, - { from: CVX.address, to: USDC.address, rate: simpleToExactAmount(56, 4) }, - { from: CRV.address, to: USDT.address, rate: simpleToExactAmount(63, 4) }, - { from: CVX.address, to: USDT.address, rate: simpleToExactAmount(58, 4) }, - ]) - const swapper = await new CowSwapDex__factory(deployer).deploy(nexus.address, relayer.address, gpv2Settlement.address) - return { relayer, swapper } -} - -const assertVaultDeposit = async (staker: Account, asset: IERC20Metadata, vault: AnyVault, depositAmount: BigNumber) => { +const assertVaultDepositRevert = async (staker: Account, asset: IERC20Metadata, vault: AnyVault, depositAmount: BigNumber) => { await increaseTime(ONE_HOUR) const assetsBefore = await asset.balanceOf(staker.address) const sharesBefore = await vault.balanceOf(staker.address) @@ -117,21 +74,19 @@ const assertVaultDeposit = async (staker: Account, asset: IERC20Metadata, vault: expect(await vault.balanceOf(staker.address), `staker ${await vault.symbol()} shares after`).eq(sharesBefore) expect(await vault.totalAssets(), "totalAssets").to.be.eq(totalAssetsBefore) } - -const assertVaultMint = async ( +const assertVaultMintRevert = async ( staker: Account, asset: IERC20Metadata, vault: AnyVault, dataEmitter: DataEmitter, mintAmount: BigNumber, ) => { - const variance = BN.from(10) await increaseTime(ONE_HOUR) const assetsBefore = await asset.balanceOf(staker.address) const sharesBefore = await vault.balanceOf(staker.address) const totalSharesBefore = await vault.totalSupply() const assetsPreviewed = await vault.connect(staker.signer).previewMint(mintAmount) - log(`Assets deposited from mint of 70,000 shares ${usdFormatter(assetsPreviewed, 18, 14, 18)}`) + log(`Assets deposited from mint of 70,000 shares ${usdFormatter(assetsPreviewed)}`) await expect(vault.connect(staker.signer).mint(mintAmount, staker.address), "mint").to.be.revertedWith("Vault shutdown") @@ -145,7 +100,7 @@ const assertVaultMint = async ( expect(await vault.totalSupply(), "vault supply after").eq(totalSharesBefore) } const assertVaultWithdraw = async (staker: Account, asset: IERC20Metadata, vault: AnyVault, _withdrawAmount?: BigNumber) => { - const variance = BN.from(10) + const variance = simpleToExactAmount(3, 15) await increaseTime(ONE_HOUR) const withdrawAmount = _withdrawAmount ? _withdrawAmount : await vault.convertToAssets(await vault.balanceOf(staker.address)) const assetsBefore = await asset.balanceOf(staker.address) @@ -173,7 +128,8 @@ const assertVaultRedeem = async ( dataEmitter: DataEmitter, _redeemAmount?: BigNumber, ) => { - const variance = BN.from(10) + const variance = simpleToExactAmount(4, 15) + // Do a full redeem if no redeemAmount passed const redeemAmount = _redeemAmount ? _redeemAmount : await vault.balanceOf(staker.address) await increaseTime(ONE_HOUR) @@ -201,7 +157,7 @@ const assertVaultRedeem = async ( assertBNClose(assetsRedeemed, assetsPreviewed, variance, "expected assets redeemed") expect(assetsAfter, `staker ${await asset.symbol()} assets after`).gt(assetsBefore) expect(sharesAfter, `staker ${await vault.symbol()} shares after`).eq(sharesBefore.sub(redeemAmount)) - expect(await vault.totalSupply(), "vault supply after").eq(totalSharesBefore.sub(redeemAmount)) + assertBNClose(await vault.totalSupply(), totalSharesBefore.sub(redeemAmount), simpleToExactAmount(5, 16), "vault supply after") } const snapConvex3CrvLiquidatorVaults = async (vaults: Convex3CrvLiquidatorVaults, account: Account, metaVaultAddress: string) => { @@ -326,7 +282,6 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { let sa: StandardAccounts let deployer: Signer let governor: Account - let rewardsWhale: Account let vaultManager: Account let keeper: Account let usdtWhale: Account @@ -338,13 +293,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { let nexus: Nexus let proxyAdmin: InstantProxyAdmin // common smart contracts - let swapper: CowSwapDex - let liquidator: Liquidator - // external smart contracts let threeCrvToken: IERC20Metadata - let cvxToken: IERC20Metadata - let crvToken: IERC20Metadata - let daiToken: IERC20Metadata let usdcToken: IERC20Metadata let usdtToken: IERC20Metadata // mstable underlying vaults <= => convex @@ -381,7 +330,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { } } const setup = async () => { - await resetNetwork(16580000) + await resetNetwork(16989760) const accounts = await ethers.getSigners() sa = await new StandardAccounts().initAccounts(accounts) governor = await impersonateAccount(resolveAddress("Governor")) @@ -392,7 +341,6 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { sa.alice = threeCrvWhale1 sa.bob = threeCrvWhale2 - rewardsWhale = await impersonateAccount(rewardsWhaleAddress) vaultManager = await impersonateAccount(resolveAddress("VaultManager")) sa.vaultManager = vaultManager keeper = await impersonateAccount(resolveAddress("OperationsSigner")) @@ -409,16 +357,6 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { proxyAdmin = InstantProxyAdmin__factory.connect(proxyAdminAddress, governor.signer) - // Deploy mocked contracts - ;({ swapper } = await deployMockAsyncSwapper(deployer, nexus)) - await swapper.connect(governor.signer).approveToken(CRV.address) - await swapper.connect(governor.signer).approveToken(CVX.address) - // swapper = CowSwapDex__factory.connect(resolveAddress("CowSwapDex"), deployer) - const syncSwapper = await deployMockSyncSwapper(deployer, nexus) - - // Deploy common / utilities contracts - liquidator = Liquidator__factory.connect(resolveAddress("LiquidatorV2"), keeper.signer) - const convex3CrvVaults = { musd: { address: resolveAddress("vcx3CRV-mUSD") }, frax: { address: resolveAddress("vcx3CRV-FRAX") }, @@ -451,9 +389,6 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { dataEmitter = await new DataEmitter__factory(deployer).deploy() threeCrvToken = IERC20Metadata__factory.connect(ThreeCRV.address, threeCrvWhale1.signer) - cvxToken = IERC20Metadata__factory.connect(CVX.address, rewardsWhale.signer) - crvToken = IERC20Metadata__factory.connect(CRV.address, rewardsWhale.signer) - daiToken = IERC20Metadata__factory.connect(DAI.address, daiWhale.signer) usdcToken = IERC20Metadata__factory.connect(USDC.address, usdcWhale.signer) usdtToken = IERC20Metadata__factory.connect(USDT.address, usdtWhale.signer) @@ -467,19 +402,6 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { await setBalancesToAccount(threeCrvWhale1, [] as ERC20[], tokensToMockBalance, 10000000000) await setBalancesToAccount(threeCrvWhale2, [] as ERC20[], tokensToMockBalance, 10000000000) - // Mock balances on swappers to simulate swaps - cvxToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) - crvToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) - daiToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) - usdcToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) - usdtToken.transfer(syncSwapper.address, simpleToExactAmount(10000)) - - cvxToken.transfer(swapper.address, simpleToExactAmount(10000)) - crvToken.transfer(swapper.address, simpleToExactAmount(10000)) - daiToken.transfer(swapper.address, simpleToExactAmount(10000)) - usdcToken.transfer(swapper.address, simpleToExactAmount(10000)) - usdtToken.transfer(swapper.address, simpleToExactAmount(10000)) - // Stakers approve vaults to take their tokens await threeCrvToken.connect(threeCrvWhale1.signer).approve(periodicAllocationPerfFeeMetaVault.address, ethers.constants.MaxUint256) await threeCrvToken.connect(threeCrvWhale2.signer).approve(periodicAllocationPerfFeeMetaVault.address, ethers.constants.MaxUint256) @@ -594,7 +516,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { }) }) }) - context("upgrade vaults", async () => { + context.skip("upgrade vaults", async () => { // mstable underlying vaults <= => convex let musdConvexVaultImpl: Convex3CrvLiquidatorVault let fraxConvexVaultImpl: Convex3CrvLiquidatorVault @@ -607,49 +529,56 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { describe("propose / accepts upgrades", async () => { it("deploys new vaults", async () => { // 1.- underlying meta vaults capable of liquidate rewards - const deployConvex3CrvLiquidatorVaultImpl = async (reference: Convex3CrvLiquidatorVault, calculatorLibrary: string) => { - return await deployConvex3CrvLiquidatorVault(hre, deployer, { - calculatorLibrary, - nexus: nexus.address, - asset: await reference.asset(), - constructorData: { - metapool: await reference.metapool(), - booster: await reference.booster(), - convexPoolId: await reference.convexPoolId(), - }, - name: await reference.name(), - symbol: await reference.symbol(), - streamDuration: (await reference.STREAM_DURATION()).toNumber(), - proxy: false, - }) - } + // yarn task convex-3crv-vault-deploy-copy --vault vcx3CRV-FRAX --calculator-library Curve3CrvFactoryMetapoolCalculatorLibrary --network mainnet fraxConvexVaultImpl = ( - await deployConvex3CrvLiquidatorVaultImpl(fraxConvexVault, resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary")) + await deployConvex3CrvLiquidatorVaultCopy( + hre, + deployer, + fraxConvexVault, + resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary"), + ) ).impl + // yarn task convex-3crv-vault-deploy-copy --vault vcx3CRV-mUSD --calculator-library Curve3CrvMetapoolCalculatorLibrary --network mainnet musdConvexVaultImpl = ( - await deployConvex3CrvLiquidatorVaultImpl(musdConvexVault, resolveAddress("Curve3CrvMetapoolCalculatorLibrary")) + await deployConvex3CrvLiquidatorVaultCopy( + hre, + deployer, + musdConvexVault, + resolveAddress("Curve3CrvMetapoolCalculatorLibrary"), + ) ).impl + + // yarn task convex-3crv-vault-deploy-copy --vault vcx3CRV-BUSD --calculator-library Curve3CrvFactoryMetapoolCalculatorLibrary --network mainnet busdConvexVaultImpl = ( - await deployConvex3CrvLiquidatorVaultImpl(busdConvexVault, resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary")) + await deployConvex3CrvLiquidatorVaultCopy( + hre, + deployer, + busdConvexVault, + resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary"), + ) ).impl // 2.- save plus meta vault - const periodicAllocationPerfFeeMetaVaultUpgrade = await deployPeriodicAllocationPerfFeeMetaVault(hre, deployer, { - nexus: nexus.address, - asset: await periodicAllocationPerfFeeMetaVault.asset(), - proxy: false, - }) - periodicAllocationPerfFeeMetaVaultImpl = periodicAllocationPerfFeeMetaVaultUpgrade.impl + // yarn task convex-3crv-mv-deploy --asset 3Crv --proxy false --network mainnet + periodicAllocationPerfFeeMetaVaultImpl = ( + await deployPeriodicAllocationPerfFeeMetaVault(hre, deployer, { + nexus: nexus.address, + asset: await periodicAllocationPerfFeeMetaVault.asset(), + proxy: false, + }) + ).impl // 3.- 4626 Wrappers of the save plus meta vault - const usdcMetaVaultUpgrade = await deployCurve3CrvMetaVault(hre, deployer, { - calculatorLibrary: resolveAddress("Curve3CrvCalculatorLibrary"), - nexus: nexus.address, - asset: usdcToken.address, - metaVault: periodicAllocationPerfFeeMetaVault.address, - proxy: false, - }) - usdcMetaVaultImpl = usdcMetaVaultUpgrade.impl + // yarn task curve-3crv-meta-vault-deploy --asset USDC --calculator-library Curve3CrvCalculatorLibrary --proxy false --network mainnet + usdcMetaVaultImpl = ( + await deployCurve3CrvMetaVault(hre, deployer, { + calculatorLibrary: resolveAddress("Curve3CrvCalculatorLibrary"), + nexus: nexus.address, + asset: usdcToken.address, + metaVault: periodicAllocationPerfFeeMetaVault.address, + proxy: false, + }) + ).impl }) it("pause all vaults", async () => { await musdConvexVault.connect(governor.signer).pause() @@ -709,7 +638,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { }) }) }) - context("remove liquidity from yield sources", async () => { + context.skip("remove liquidity from yield sources", async () => { it("PeriodicAllocationPerfFeeMetaVault remove underlying vaults", async () => { const totalSupplyBefore = await periodicAllocationPerfFeeMetaVault.totalSupply() const totalAssetsBefore = await periodicAllocationPerfFeeMetaVault.totalAssets() @@ -721,7 +650,9 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { const totalAssetsAfter = await periodicAllocationPerfFeeMetaVault.totalAssets() const totalSupplyAfter = await periodicAllocationPerfFeeMetaVault.totalSupply() - expect(totalAssetsBefore, "total assets").to.be.eq(totalAssetsAfter) + log(`PAPFV Total Supply: Before ${usdFormatter(totalSupplyBefore)} - After ${usdFormatter(totalSupplyAfter)}`) + log(`PAPFV Total Assets: Before ${usdFormatter(totalAssetsBefore)} - After ${usdFormatter(totalAssetsAfter)}`) + assertBNClosePercent(totalAssetsBefore, totalAssetsAfter) expect(totalSupplyBefore, "total supply").to.be.eq(totalSupplyAfter) }) it("Curve3CrvBasicMetaVault withdraw from convex", async () => { @@ -732,8 +663,11 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { const totalAssetsAfter = await usdcMetaVault.totalAssets() const totalSupplyAfter = await usdcMetaVault.totalSupply() + log(`C3CRV Total Supply: Before ${usdFormatter(totalSupplyBefore)} - After ${usdFormatter(totalSupplyAfter)}`) + log(`C3CRV Total Assets: Before ${usdFormatter(totalAssetsBefore)} - After ${usdFormatter(totalAssetsAfter)}`) + expect(totalSupplyBefore, "total supply").to.be.eq(totalSupplyAfter) - assertBNClose(totalAssetsBefore, totalAssetsAfter, simpleToExactAmount(3, 5), "total assets") + assertBNClose(totalAssetsBefore, totalAssetsAfter, simpleToExactAmount(5, 5), "total assets") const usdcMetavaultBalance = await periodicAllocationPerfFeeMetaVault.balanceOf(usdcMetaVault.address) expect(usdcMetavaultBalance, "usdc metavault balance on underlying vault").to.be.eq(0) @@ -746,14 +680,12 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { context(`Convex3CrvLiquidatorVault ${vaultSymbol}`, async () => { let holder: Account let convex3CrvLiquidatorVault: Convex3CrvLiquidatorVault - before("", async () => { - holder = await impersonateAccount(feeReceiver) + + before(async () => { + holder = await impersonateAccount(resolveAddress("OperationsSigner")) convex3CrvLiquidatorVault = Convex3CrvLiquidatorVault__factory.connect(resolveAddress(vaultSymbol), deployer) }) describe("liquidate assets", () => { - before(async () => { - await threeCrvToken.connect(holder.signer).approve(convex3CrvLiquidatorVault.address, ethers.constants.MaxUint256) - }) it("whale should fail to liquidate vault", async () => { const tx = convex3CrvLiquidatorVault.connect(holder.signer).liquidateVault(0) await expect(tx).to.be.revertedWith("Only governor can execute") @@ -768,7 +700,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { }) describe("basic flow", () => { it("deposit 3Crv reverted", async () => { - await assertVaultDeposit( + await assertVaultDepositRevert( threeCrvWhale1, threeCrvToken, convex3CrvLiquidatorVault, @@ -776,7 +708,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { ) }) it("mint shares reverted", async () => { - await assertVaultMint( + await assertVaultMintRevert( threeCrvWhale1, threeCrvToken, convex3CrvLiquidatorVault, @@ -785,16 +717,13 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { ) }) it("partial withdraw", async () => { - await assertVaultWithdraw(holder, threeCrvToken, convex3CrvLiquidatorVault, simpleToExactAmount(50, ThreeCRV.decimals)) + const maxWithdraw = await convex3CrvLiquidatorVault.maxWithdraw(holder.address) + + await assertVaultWithdraw(holder, threeCrvToken, convex3CrvLiquidatorVault, maxWithdraw.div(2)) }) it("partial redeem", async () => { - await assertVaultRedeem( - holder, - threeCrvToken, - convex3CrvLiquidatorVault, - dataEmitter, - simpleToExactAmount(50, ThreeCRV.decimals), - ) + const maxRedeem = await convex3CrvLiquidatorVault.maxRedeem(holder.address) + await assertVaultRedeem(holder, threeCrvToken, convex3CrvLiquidatorVault, dataEmitter, maxRedeem.div(2)) }) it("total redeem", async () => { await assertVaultRedeem(holder, threeCrvToken, convex3CrvLiquidatorVault, dataEmitter) @@ -805,11 +734,11 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { context("PeriodicAllocationPerfFeeMetaVault", async () => { let holder: Account before("", async () => { - holder = await impersonateAccount(feeReceiver) + holder = await impersonateAccount(resolveAddress("OperationsSigner")) }) describe("basic flow", () => { it("deposit 3Crv reverted", async () => { - await assertVaultDeposit( + await assertVaultDepositRevert( threeCrvWhale1, threeCrvToken, periodicAllocationPerfFeeMetaVault, @@ -817,7 +746,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { ) }) it("mint shares reverted", async () => { - await assertVaultMint( + await assertVaultMintRevert( threeCrvWhale1, threeCrvToken, periodicAllocationPerfFeeMetaVault, @@ -826,21 +755,12 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { ) }) it("partial withdraw", async () => { - await assertVaultWithdraw( - holder, - threeCrvToken, - periodicAllocationPerfFeeMetaVault, - simpleToExactAmount(100, ThreeCRV.decimals), - ) + const maxWithdraw = await periodicAllocationPerfFeeMetaVault.maxWithdraw(holder.address) + await assertVaultWithdraw(holder, threeCrvToken, periodicAllocationPerfFeeMetaVault, maxWithdraw.div(2)) }) it("partial redeem", async () => { - await assertVaultRedeem( - holder, - threeCrvToken, - periodicAllocationPerfFeeMetaVault, - dataEmitter, - simpleToExactAmount(100, ThreeCRV.decimals), - ) + const maxRedeem = await periodicAllocationPerfFeeMetaVault.maxRedeem(holder.address) + await assertVaultRedeem(holder, threeCrvToken, periodicAllocationPerfFeeMetaVault, dataEmitter, maxRedeem.div(2)) }) it("total redeem", async () => { await assertVaultRedeem(holder, threeCrvToken, periodicAllocationPerfFeeMetaVault, dataEmitter) @@ -868,7 +788,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { describe("basic flow", () => { it("deposit erc20Token", async () => { // When deposit via 4626MetaVault - await assertVaultDeposit(usdcWhale, usdcToken, usdcMetaVault, simpleToExactAmount(50000, USDC.decimals)) + await assertVaultDepositRevert(usdcWhale, usdcToken, usdcMetaVault, simpleToExactAmount(50000, USDC.decimals)) const vaultsDataAfter = await snapshotVaults( convex3CrvLiquidatorVaults, @@ -895,7 +815,7 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { }) it("mint shares", async () => { // When mint via 4626MetaVault - await assertVaultMint(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, simpleToExactAmount(70000, ThreeCRV.decimals)) + await assertVaultMintRevert(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, simpleToExactAmount(70000, ThreeCRV.decimals)) const vaultsDataAfter = await snapshotVaults( convex3CrvLiquidatorVaults, @@ -922,11 +842,15 @@ describe("Save+ Basic and Meta Vaults - Shutdown", async () => { // no change on underlying vaults }) it("partial withdraw", async () => { - await assertVaultWithdraw(usdcWhale, usdcToken, usdcMetaVault, simpleToExactAmount(60000, USDC.decimals)) + const maxWithdraw = await usdcMetaVault.maxWithdraw(usdcWhale.address) + + await assertVaultWithdraw(usdcWhale, usdcToken, usdcMetaVault, maxWithdraw.div(2)) // no change on underlying vaults }) it("partial redeem", async () => { - await assertVaultRedeem(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, simpleToExactAmount(7000, ThreeCRV.decimals)) + const maxRedeem = await usdcMetaVault.maxRedeem(usdcWhale.address) + + await assertVaultRedeem(usdcWhale, usdcToken, usdcMetaVault, dataEmitter, maxRedeem.div(2)) // no change on underlying vaults }) it("total redeem", async () => {