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..4689666 100644 --- a/tasks/convex3CrvMetaVault.ts +++ b/tasks/convex3CrvMetaVault.ts @@ -18,13 +18,17 @@ import type { AssetProxy, PeriodicAllocationPerfFeeMetaVault } from "types/gener import type { Convex3CrvVaultsDeployed } from "./deployment/convex3CrvVaults" -// deployPeriodicAllocationPerfFeeMetaVault interface AssetSourcingParams { singleVaultSharesThreshold: number singleSourceVaultIndex: number } -interface PeriodicAllocationPerfFeeMetaVaultParams { +interface PeriodicAllocationPerfFeeMetaVaultUpgrade { + nexus: string + asset: string + proxy: boolean +} +interface PeriodicAllocationPerfFeeMetaVaultDeploy { nexus: string asset: string name: string @@ -38,6 +42,7 @@ interface PeriodicAllocationPerfFeeMetaVaultParams { assetPerShareUpdateThreshold: BN proxy: boolean } +type PeriodicAllocationPerfFeeMetaVaultParams = PeriodicAllocationPerfFeeMetaVaultDeploy | PeriodicAllocationPerfFeeMetaVaultUpgrade export async function deployPeriodicAllocationPerfFeeMetaVaults( hre: HardhatRuntimeEnvironment, @@ -71,20 +76,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 +94,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, @@ -119,14 +123,14 @@ 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, 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) @@ -167,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 4ece1e3..87ef940 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) @@ -159,12 +154,15 @@ export async function deployConvex3CrvLiquidatorVault( address: vaultImpl.address, contract: "contracts/vault/liquidity/convex/Convex3CrvLiquidatorVault.sol:Convex3CrvLiquidatorVault", constructorArguments: constructorArguments, + libraries: linkAddresses, }) // Proxy 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, @@ -181,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) @@ -387,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 da7cb0e..ccf5b0e 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 } @@ -70,12 +80,15 @@ export const deployCurve3CrvMetaVault = async (hre: HardhatRuntimeEnvironment, s address: vaultImpl.address, contract: "contracts/vault/liquidity/curve/Curve3CrvBasicMetaVault.sol:Curve3CrvBasicMetaVault", constructorArguments: constructorArguments, + libraries: libraryAddresses, }) // Proxy 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) @@ -132,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) @@ -150,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/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..cf80e8c --- /dev/null +++ b/test-fork/vault/savePlusShutdown.spec.ts @@ -0,0 +1,864 @@ +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, assertBNClosePercent } from "@utils/assertions" +import { ONE_HOUR, ZERO } from "@utils/constants" +import { impersonateAccount, loadOrExecFixture, setBalancesToAccount } from "@utils/fork" +import { StandardAccounts } from "@utils/machines" +import { simpleToExactAmount } from "@utils/math" +import { increaseTime } from "@utils/time" +import { expect } from "chai" +import * as hre from "hardhat" +import { ethers } from "hardhat" +import { + Convex3CrvLiquidatorVault__factory, + Curve3CrvBasicMetaVault__factory, + DataEmitter__factory, + IERC20__factory, + IERC20Metadata__factory, + InstantProxyAdmin__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, Curve3CrvBasicMetaVault, Curve3CrvPool, DataEmitter, Nexus } from "types" +import type { Account, AnyVault } from "types/common" +import type { ERC20, IERC20Metadata, InstantProxyAdmin, PeriodicAllocationPerfFeeMetaVault } from "types/generated" + +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 +} +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) + 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 assertVaultMintRevert = async ( + staker: Account, + asset: IERC20Metadata, + vault: AnyVault, + dataEmitter: DataEmitter, + mintAmount: BigNumber, +) => { + 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)}`) + + 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 = 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) + 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 = simpleToExactAmount(4, 15) + + // 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)) + assertBNClose(await vault.totalSupply(), totalSharesBefore.sub(redeemAmount), simpleToExactAmount(5, 16), "vault supply after") +} + +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 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 threeCrvToken: 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(16989760) + 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 + + 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) + + 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) + 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) + + // 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.skip("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 + // yarn task convex-3crv-vault-deploy-copy --vault vcx3CRV-FRAX --calculator-library Curve3CrvFactoryMetapoolCalculatorLibrary --network mainnet + fraxConvexVaultImpl = ( + 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 deployConvex3CrvLiquidatorVaultCopy( + hre, + deployer, + musdConvexVault, + resolveAddress("Curve3CrvMetapoolCalculatorLibrary"), + ) + ).impl + + // yarn task convex-3crv-vault-deploy-copy --vault vcx3CRV-BUSD --calculator-library Curve3CrvFactoryMetapoolCalculatorLibrary --network mainnet + busdConvexVaultImpl = ( + await deployConvex3CrvLiquidatorVaultCopy( + hre, + deployer, + busdConvexVault, + resolveAddress("Curve3CrvFactoryMetapoolCalculatorLibrary"), + ) + ).impl + + // 2.- save plus meta vault + // 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 + // 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() + 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.skip("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() + + 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 () => { + 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() + 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(5, 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(resolveAddress("OperationsSigner")) + convex3CrvLiquidatorVault = Convex3CrvLiquidatorVault__factory.connect(resolveAddress(vaultSymbol), deployer) + }) + describe("liquidate assets", () => { + 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 assertVaultDepositRevert( + threeCrvWhale1, + threeCrvToken, + convex3CrvLiquidatorVault, + simpleToExactAmount(50000, ThreeCRV.decimals), + ) + }) + it("mint shares reverted", async () => { + await assertVaultMintRevert( + threeCrvWhale1, + threeCrvToken, + convex3CrvLiquidatorVault, + dataEmitter, + simpleToExactAmount(70000, ThreeCRV.decimals), + ) + }) + it("partial withdraw", async () => { + const maxWithdraw = await convex3CrvLiquidatorVault.maxWithdraw(holder.address) + + await assertVaultWithdraw(holder, threeCrvToken, convex3CrvLiquidatorVault, maxWithdraw.div(2)) + }) + it("partial redeem", async () => { + 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) + }) + }) + }) + }) + context("PeriodicAllocationPerfFeeMetaVault", async () => { + let holder: Account + before("", async () => { + holder = await impersonateAccount(resolveAddress("OperationsSigner")) + }) + describe("basic flow", () => { + it("deposit 3Crv reverted", async () => { + await assertVaultDepositRevert( + threeCrvWhale1, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + simpleToExactAmount(50000, ThreeCRV.decimals), + ) + }) + it("mint shares reverted", async () => { + await assertVaultMintRevert( + threeCrvWhale1, + threeCrvToken, + periodicAllocationPerfFeeMetaVault, + dataEmitter, + simpleToExactAmount(70000, ThreeCRV.decimals), + ) + }) + it("partial withdraw", async () => { + const maxWithdraw = await periodicAllocationPerfFeeMetaVault.maxWithdraw(holder.address) + await assertVaultWithdraw(holder, threeCrvToken, periodicAllocationPerfFeeMetaVault, maxWithdraw.div(2)) + }) + it("partial redeem", async () => { + 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) + 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 assertVaultDepositRevert(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 assertVaultMintRevert(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 () => { + const maxWithdraw = await usdcMetaVault.maxWithdraw(usdcWhale.address) + + await assertVaultWithdraw(usdcWhale, usdcToken, usdcMetaVault, maxWithdraw.div(2)) + // no change on underlying vaults + }) + it("partial redeem", async () => { + 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 () => { + 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 -- */