From 55d1ea26c1ee7fb4f5ac1c8508d199d4d91e968f Mon Sep 17 00:00:00 2001 From: Atharva Date: Sat, 19 Jul 2025 04:05:43 +0530 Subject: [PATCH] #1021: doc: add description for timelock update script (ee726e8) --- config/deploy/BaseConfig.sol | 1 + config/deploy/Mainnet.sol | 6 +- config/deploy/Sepolia.sol | 3 +- script/v2/DeployOracles.sol | 4 + script/v2/Upgradev2_1.s.sol | 376 +++++++++++ script/v2/usd/DeploySwapManager.s.sol | 4 + script/v2/usd/UpgradeRewardsManager.s.sol | 6 - script/v2/usd/UpgradeVaultManager.s.sol | 6 - test/v2/integration/RewardsManager.t.sol | 249 ++++--- test/v2/integration/VaultManager.t.sol | 749 ++++++++++++++++++---- 10 files changed, 1162 insertions(+), 242 deletions(-) create mode 100644 script/v2/Upgradev2_1.s.sol diff --git a/config/deploy/BaseConfig.sol b/config/deploy/BaseConfig.sol index 183f939..8e21fb7 100644 --- a/config/deploy/BaseConfig.sol +++ b/config/deploy/BaseConfig.sol @@ -57,6 +57,7 @@ contract BaseConfig { struct UmbrellaVaults { ERC4626Vault waUsdcStakeToken; + ERC4626Vault waUsdtStakeToken; } struct SparkVaults { diff --git a/config/deploy/Mainnet.sol b/config/deploy/Mainnet.sol index 7b9f966..ba754de 100644 --- a/config/deploy/Mainnet.sol +++ b/config/deploy/Mainnet.sol @@ -49,7 +49,7 @@ contract Mainnet is BaseConfig { oracles: Oracles({ usdc: AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6), usdt: AggregatorV3Interface(0x3E7d1eAB13ad0104d2750B8863b489D65364e32D), - ustb: AggregatorV3Interface(0x289B5036cd942e619E1Ee48670F98d214E745AAC), + ustb: AggregatorV3Interface(0xE4fA682f94610cCd170680cc3B045d77D9E528a8), aUsdt: AggregatorV3Interface(0x380adC857Cd3d0531C0821B5D52F34737C4eCDC4), aUsdc: AggregatorV3Interface(0x95CCDE4C1bb3d56639d22185aa2f95EcfebD7F22), mNav: AggregatorV3Interface(0xC28198Df9aee1c4990994B35ff51eFA4C769e534), @@ -102,6 +102,10 @@ contract Mainnet is BaseConfig { waUsdcStakeToken: ERC4626Vault({ vault: IERC4626(0x6bf183243FdD1e306ad2C4450BC7dcf6f0bf8Aa6), oracle: IERC4626Oracle(address(0)) + }), + waUsdtStakeToken: ERC4626Vault({ + vault: IERC4626(0xA484Ab92fe32B143AEE7019fC1502b1dAA522D31), + oracle: IERC4626Oracle(address(0)) }) }), periphery: PeripheryContracts({ diff --git a/config/deploy/Sepolia.sol b/config/deploy/Sepolia.sol index adea41f..93be4aa 100644 --- a/config/deploy/Sepolia.sol +++ b/config/deploy/Sepolia.sol @@ -87,7 +87,8 @@ contract Sepolia is BaseConfig { sUsdc: ERC4626Vault({vault: IERC4626(address(0)), oracle: IERC4626Oracle(address(0))}) }), umbrellaVaults: UmbrellaVaults({ - waUsdcStakeToken: ERC4626Vault({vault: IERC4626(address(0)), oracle: IERC4626Oracle(address(0))}) + waUsdcStakeToken: ERC4626Vault({vault: IERC4626(address(0)), oracle: IERC4626Oracle(address(0))}), + waUsdtStakeToken: ERC4626Vault({vault: IERC4626(address(0)), oracle: IERC4626Oracle(address(0))}) }), periphery: PeripheryContracts({ aaveV3: IPool(0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951), diff --git a/script/v2/DeployOracles.sol b/script/v2/DeployOracles.sol index 561a897..85c2fb9 100644 --- a/script/v2/DeployOracles.sol +++ b/script/v2/DeployOracles.sol @@ -71,6 +71,10 @@ contract DeployOracles is Configurable, DeploymentUtils, Script { config.umbrellaVaults.waUsdcStakeToken.oracle = IERC4626Oracle(address(oracle)); console2.log("waUsdcStakeTokenOracle deployed to: %s", address(config.umbrellaVaults.waUsdcStakeToken.oracle)); + AaveUmbrellaOracle oracleUsdt = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdtStakeToken.vault); + config.umbrellaVaults.waUsdtStakeToken.oracle = IERC4626Oracle(address(oracleUsdt)); + console2.log("waUsdtStakeTokenOracle deployed to: %s", address(config.umbrellaVaults.waUsdtStakeToken.oracle)); + vm.stopBroadcast(); } diff --git a/script/v2/Upgradev2_1.s.sol b/script/v2/Upgradev2_1.s.sol new file mode 100644 index 0000000..d212e34 --- /dev/null +++ b/script/v2/Upgradev2_1.s.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19; + +import {Script} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {ERC20} from "@solmate/src/tokens/ERC20.sol"; +import {StrategyConfig, StrategyCategory} from "@level/src/v2/common/libraries/StrategyLib.sol"; +import {PauserGuard} from "@level/src/v2/common/guard/PauserGuard.sol"; + +import {DeploymentUtils} from "@level/script/v2/DeploymentUtils.s.sol"; +import {Configurable} from "@level/config/Configurable.sol"; + +import {console2} from "forge-std/console2.sol"; + +import {AggregatorV3Interface} from "@level/src/v2/interfaces/AggregatorV3Interface.sol"; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {BaseConfig} from "@level/config/deploy/BaseConfig.sol"; + +/// @notice Script used to propose a timelock batch transaction on the multisig +/// @notice The script in itself does not make any changes, but prints TX data +contract Upgradev2_1 is Configurable, DeploymentUtils, Script { + uint256 public chainId; + + Vm.Wallet public deployerWallet; + + error InvalidProxyAddress(); + error UpgradeFailed(); + error VerificationFailed(); + + function setUp() external { + uint256 _chainId = vm.envUint("CHAIN_ID"); + setUp_(_chainId); + } + + function setUp_(uint256 _chainId) public { + chainId = _chainId; + initConfig(_chainId); + + vm.label(msg.sender, "Deployer EOA"); + } + + function setUp_(uint256 _chainId, uint256 _privateKey) public { + chainId = _chainId; + initConfig(_chainId); + + if (msg.sender != vm.addr(_privateKey)) { + revert("Private key does not match sender"); + } + + deployerWallet.privateKey = _privateKey; + deployerWallet.addr = vm.addr(_privateKey); + + vm.label(msg.sender, "Deployer EOA"); + } + + function setUp_(BaseConfig.Config memory _config) public { + config = _config; + + vm.label(msg.sender, "Deployer EOA"); + } + + function runWithSetup(address newVaultManagerImpl, address newRewardsManagerImpl) + external + returns (address[] memory, bytes[] memory) + { + uint256 _chainId = vm.envUint("CHAIN_ID"); + setUp_(_chainId); + + (address[] memory targets, bytes[] memory payloads) = run(newVaultManagerImpl, newRewardsManagerImpl); + + return (targets, payloads); + } + + function run(address newVaultManagerImpl, address newRewardsManagerImpl) + public + view + returns (address[] memory, bytes[] memory) + { + if (newVaultManagerImpl == address(0)) { + revert("New vault manager implementation is not set"); + } + if (newRewardsManagerImpl == address(0)) { + revert("New rewards manager implementation is not set"); + } + if (address(config.levelContracts.swapManager) == address(0)) { + revert("Swap manager is not set"); + } + if (address(config.sparkVaults.sUsdc.oracle) == address(0)) { + revert("Spark sUsdc oracle is not set"); + } + if (address(config.oracles.cappedMNav) == address(0)) { + revert("CappedMNav oracle is not set"); + } + if (address(config.umbrellaVaults.waUsdcStakeToken.oracle) == address(0)) { + revert("Umbrella USDC oracle is not set"); + } + if (address(config.umbrellaVaults.waUsdtStakeToken.oracle) == address(0)) { + revert("Umbrella USDT oracle is not set"); + } + + // ======== Old strategies required for rewards manager ======== + StrategyConfig memory aUsdcConfig = StrategyConfig({ + category: StrategyCategory.AAVEV3, + baseCollateral: config.tokens.usdc, + receiptToken: config.tokens.aUsdc, + oracle: AggregatorV3Interface(0x95CCDE4C1bb3d56639d22185aa2f95EcfebD7F22), + depositContract: address(config.periphery.aaveV3), + withdrawContract: address(config.periphery.aaveV3), + heartbeat: 1 days + }); + + StrategyConfig memory steakhouseUsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdc.vault)), + oracle: config.morphoVaults.steakhouseUsdc.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdc.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdc.vault), + heartbeat: 1 days + }); + + StrategyConfig memory aUsdtConfig = StrategyConfig({ + category: StrategyCategory.AAVEV3, + baseCollateral: config.tokens.usdt, + receiptToken: config.tokens.aUsdt, + oracle: AggregatorV3Interface(0x380adC857Cd3d0531C0821B5D52F34737C4eCDC4), + depositContract: address(config.periphery.aaveV3), + withdrawContract: address(config.periphery.aaveV3), + heartbeat: 1 days + }); + + // ============================================================= + + address[] memory targets = new address[](14); + bytes[] memory payloads = new bytes[](14); + + // Upgrade vault manager proxy + targets[0] = address(config.levelContracts.vaultManager); + payloads[0] = abi.encodeWithSelector( + config.levelContracts.vaultManager.upgradeToAndCall.selector, newVaultManagerImpl, "" + ); + + // Upgrade rewards manager proxy + targets[1] = address(config.levelContracts.rewardsManager); + payloads[1] = abi.encodeWithSelector( + config.levelContracts.rewardsManager.upgradeToAndCall.selector, newRewardsManagerImpl, "" + ); + + // Configure pause group for SwapManager + PauserGuard.FunctionSig[] memory swapPauseGroup = new PauserGuard.FunctionSig[](2); + swapPauseGroup[0] = PauserGuard.FunctionSig({ + selector: config.levelContracts.swapManager.setSwapConfig.selector, + target: address(config.levelContracts.swapManager) + }); + swapPauseGroup[1] = PauserGuard.FunctionSig({ + selector: config.levelContracts.swapManager.swap.selector, + target: address(config.levelContracts.swapManager) + }); + targets[2] = address(config.levelContracts.pauserGuard); + payloads[2] = abi.encodeWithSelector( + config.levelContracts.pauserGuard.configureGroup.selector, keccak256("SWAP_PAUSE"), swapPauseGroup + ); + + // Add spark strategy to vault manager + StrategyConfig memory sUsdcConfig = StrategyConfig({ + category: StrategyCategory.SPARK, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.sparkVaults.sUsdc.vault)), + oracle: config.sparkVaults.sUsdc.oracle, + depositContract: address(config.sparkVaults.sUsdc.vault), + withdrawContract: address(config.sparkVaults.sUsdc.vault), + heartbeat: 1 days + }); + targets[3] = address(config.levelContracts.vaultManager); + payloads[3] = abi.encodeWithSelector( + config.levelContracts.vaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.sparkVaults.sUsdc.vault), + sUsdcConfig + ); + + // Add superstate strategy to vault manager + StrategyConfig memory ustbConfig = StrategyConfig({ + category: StrategyCategory.SUPERSTATE, + baseCollateral: config.tokens.usdc, + receiptToken: config.tokens.ustb, + oracle: config.oracles.ustb, + depositContract: address(config.tokens.ustb), + withdrawContract: address(config.periphery.ustbRedemptionIdle), + heartbeat: 1 days + }); + targets[4] = address(config.levelContracts.vaultManager); + payloads[4] = abi.encodeWithSelector( + config.levelContracts.vaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.tokens.ustb), + ustbConfig + ); + + // Add m0 strategy to vault manager + StrategyConfig memory mConfig = StrategyConfig({ + category: StrategyCategory.M0, + baseCollateral: config.tokens.usdc, + receiptToken: config.tokens.wrappedM, + oracle: AggregatorV3Interface(address(config.oracles.cappedMNav)), + depositContract: address(config.levelContracts.swapManager), + withdrawContract: address(config.levelContracts.swapManager), + heartbeat: 26 hours + }); + targets[5] = address(config.levelContracts.vaultManager); + payloads[5] = abi.encodeWithSelector( + config.levelContracts.vaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.tokens.wrappedM), + mConfig + ); + + // Add Umbrella USDC strategy to vault manager + StrategyConfig memory umbrellaUsdcConfig = StrategyConfig({ + category: StrategyCategory.AAVEV3_UMBRELLA, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.umbrellaVaults.waUsdcStakeToken.vault)), + oracle: config.umbrellaVaults.waUsdcStakeToken.oracle, + depositContract: address(config.umbrellaVaults.waUsdcStakeToken.vault), + withdrawContract: address(config.umbrellaVaults.waUsdcStakeToken.vault), + heartbeat: 1 days + }); + targets[6] = address(config.levelContracts.vaultManager); + payloads[6] = abi.encodeWithSelector( + config.levelContracts.vaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.umbrellaVaults.waUsdcStakeToken.vault), + umbrellaUsdcConfig + ); + + // Add Umbrella USDT strategy to vault manager + StrategyConfig memory umbrellaUsdtConfig = StrategyConfig({ + category: StrategyCategory.AAVEV3_UMBRELLA, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.umbrellaVaults.waUsdtStakeToken.vault)), + oracle: config.umbrellaVaults.waUsdtStakeToken.oracle, + depositContract: address(config.umbrellaVaults.waUsdtStakeToken.vault), + withdrawContract: address(config.umbrellaVaults.waUsdtStakeToken.vault), + heartbeat: 1 days + }); + targets[7] = address(config.levelContracts.vaultManager); + payloads[7] = abi.encodeWithSelector( + config.levelContracts.vaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.umbrellaVaults.waUsdtStakeToken.vault), + umbrellaUsdtConfig + ); + + // Update default strategies for USDC in vault manager + address[] memory usdcDefaultStrategies = new address[](3); + usdcDefaultStrategies[0] = address(config.periphery.aaveV3); + usdcDefaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); + usdcDefaultStrategies[2] = address(config.sparkVaults.sUsdc.vault); + targets[8] = address(config.levelContracts.vaultManager); + payloads[8] = abi.encodeWithSelector( + config.levelContracts.vaultManager.setDefaultStrategies.selector, + address(config.tokens.usdc), + usdcDefaultStrategies + ); + + // Set rewards manager allStrategies for USDC + StrategyConfig[] memory usdcConfigs = new StrategyConfig[](6); + usdcConfigs[0] = aUsdcConfig; + usdcConfigs[1] = steakhouseUsdcConfig; + usdcConfigs[2] = sUsdcConfig; + usdcConfigs[3] = ustbConfig; + usdcConfigs[4] = mConfig; + usdcConfigs[5] = umbrellaUsdcConfig; + + targets[9] = address(config.levelContracts.rewardsManager); + payloads[9] = abi.encodeWithSelector( + config.levelContracts.rewardsManager.setAllStrategies.selector, address(config.tokens.usdc), usdcConfigs + ); + + // Set rewards manager allStrategies for USDT + StrategyConfig[] memory usdtConfigs = new StrategyConfig[](2); + usdtConfigs[0] = aUsdtConfig; + usdtConfigs[1] = umbrellaUsdtConfig; + + targets[10] = address(config.levelContracts.rewardsManager); + payloads[10] = abi.encodeWithSelector( + config.levelContracts.rewardsManager.setAllStrategies.selector, address(config.tokens.usdt), usdtConfigs + ); + + // Call umbrella modifyAaveUmbrellaCooldownOperator for USDC + targets[11] = address(config.levelContracts.vaultManager); + payloads[11] = abi.encodeWithSelector( + config.levelContracts.vaultManager.modifyAaveUmbrellaCooldownOperator.selector, + address(config.users.operator), + address(config.umbrellaVaults.waUsdcStakeToken.vault), + true + ); + + // Call umbrella modifyAaveUmbrellaCooldownOperator for USDT + targets[12] = address(config.levelContracts.vaultManager); + payloads[12] = abi.encodeWithSelector( + config.levelContracts.vaultManager.modifyAaveUmbrellaCooldownOperator.selector, + address(config.users.operator), + address(config.umbrellaVaults.waUsdtStakeToken.vault), + true + ); + + // Call modifyAaveUmbrellaRewardsClaimer + targets[13] = address(config.levelContracts.vaultManager); + payloads[13] = abi.encodeWithSelector( + config.levelContracts.vaultManager.modifyAaveUmbrellaRewardsClaimer.selector, + address(config.users.protocolTreasury), + address(0x4655Ce3D625a63d30bA704087E52B4C31E38188B), // Umbrella Rewards Claimer + true + ); + + // printScheduleJson(targets, payloads); + + return (targets, payloads); + } + + function printScheduleJson(address[] memory targets, bytes[] memory payloads) public view { + TimelockController timelock = config.levelContracts.adminTimelock; + + uint256[] memory values = new uint256[](targets.length); + + for (uint256 i = 0; i < targets.length; i++) { + values[i] = 0; + } + + bytes32 salt = keccak256("V2.1_UPGRADE"); + bytes32 predecessor = bytes32(0); + uint256 delay = 5 days; + + bytes32 opHash = timelock.hashOperationBatch(targets, values, payloads, predecessor, salt); + + console2.log("OPERATION HASH:"); + console2.logBytes32(opHash); + + console2.log("----- COPY BELOW FOR GNOSIS SAFE JSON TRANSACTION BUILDER -----"); + + console2.log("{"); + console2.log('"to": "0x%s",', string(toHex(address(timelock)))); + console2.log('"value": "0",'); + console2.log( + '"data": "0x%s",', + string( + toHex( + abi.encodeWithSelector( + timelock.scheduleBatch.selector, targets, values, payloads, predecessor, salt, delay + ) + ) + ) + ); + console2.log('"operation": 0'); + console2.log("}"); + } + + function toHex(address addr) internal pure returns (string memory) { + return toHex(abi.encodePacked(addr)); + } + + function toHex(bytes memory data) internal pure returns (string memory) { + bytes memory alphabet = "0123456789abcdef"; + bytes memory str = new bytes(2 + data.length * 2); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < data.length; i++) { + str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; + str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; + } + return string(str); + } +} diff --git a/script/v2/usd/DeploySwapManager.s.sol b/script/v2/usd/DeploySwapManager.s.sol index de0d079..c2b900a 100644 --- a/script/v2/usd/DeploySwapManager.s.sol +++ b/script/v2/usd/DeploySwapManager.s.sol @@ -68,6 +68,10 @@ contract DeploySwapManager is Configurable, DeploymentUtils, Script { revert("RolesAuthority must be deployed first"); } + if (address(config.oracles.cappedMNav) == address(0)) { + revert("CappedMNav must be deployed first"); + } + bytes memory constructorArgs = abi.encodeWithSignature( "initialize(address,address,address)", deployerWallet.addr, diff --git a/script/v2/usd/UpgradeRewardsManager.s.sol b/script/v2/usd/UpgradeRewardsManager.s.sol index 7ae5e60..7b05dde 100644 --- a/script/v2/usd/UpgradeRewardsManager.s.sol +++ b/script/v2/usd/UpgradeRewardsManager.s.sol @@ -54,12 +54,6 @@ contract UpgradeRewardsManager is Configurable, DeploymentUtils, Script { console2.log("Deploying RewardsManager from address %s", deployerWallet.addr); - RewardsManager proxy = RewardsManager(config.levelContracts.rewardsManager); - - if (address(proxy) == address(0)) { - revert InvalidProxyAddress(); - } - RewardsManager impl = new RewardsManager(); vm.stopBroadcast(); diff --git a/script/v2/usd/UpgradeVaultManager.s.sol b/script/v2/usd/UpgradeVaultManager.s.sol index 888f5e9..7a06b75 100644 --- a/script/v2/usd/UpgradeVaultManager.s.sol +++ b/script/v2/usd/UpgradeVaultManager.s.sol @@ -58,12 +58,6 @@ contract UpgradeVaultManager is Configurable, DeploymentUtils, Script { console2.log("Deploying VaultManager from address %s", deployerWallet.addr); - VaultManager proxy = VaultManager(config.levelContracts.vaultManager); - - if (address(proxy) == address(0)) { - revert InvalidProxyAddress(); - } - VaultManager impl = new VaultManager(); vm.stopBroadcast(); diff --git a/test/v2/integration/RewardsManager.t.sol b/test/v2/integration/RewardsManager.t.sol index 11597ad..9fcef74 100644 --- a/test/v2/integration/RewardsManager.t.sol +++ b/test/v2/integration/RewardsManager.t.sol @@ -28,6 +28,12 @@ import {CappedOneDollarOracle} from "@level/src/v2/oracles/CappedOneDollarOracle import {ISuperstateToken} from "@level/src/v2/interfaces/superstate/ISuperstateToken.sol"; import {IAllowListV2} from "@level/src/v2/interfaces/superstate/IAllowListV2.sol"; import {AaveUmbrellaOracle} from "@level/src/v2/oracles/AaveUmbrellaOracle.sol"; +import {IERC4626StakeToken} from "@level/src/v2/interfaces/aave/IERC4626StakeToken.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Upgradev2_1} from "@level/script/v2/Upgradev2_1.s.sol"; +import {SwapManager} from "@level/src/v2/usd/SwapManager.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {SwapConfig} from "@level/src/v2/usd/SwapManager.sol"; contract RewardsManagerMainnetTests is Utils, Configurable { using SafeTransferLib for ERC20; @@ -55,8 +61,22 @@ contract RewardsManagerMainnetTests is Utils, Configurable { strategist = vm.createWallet("strategist"); initConfig(1); - _upgradeRewardsManager(); - _upgradeVaultManager(); + _deployNewOracles(); + + vm.startPrank(deployer.addr); + _deploySwapManager(); + vm.stopPrank(); + + Upgradev2_1 upgrade = new Upgradev2_1(); + upgrade.setUp_(config); + + RewardsManager newRewardsManager = new RewardsManager(); + VaultManager newVaultManager = new VaultManager(); + (address[] memory targetsSetup, bytes[] memory payloadsSetup) = + upgrade.run(address(newVaultManager), address(newRewardsManager)); + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targetsSetup, payloadsSetup + ); mockOracle = new MockOracle(1e8, 8); @@ -318,45 +338,16 @@ contract RewardsManagerMainnetTests is Utils, Configurable { address[] memory defaultStrategies = new address[](1); defaultStrategies[0] = address(config.umbrellaVaults.waUsdcStakeToken.vault); - // Set oracle - if (address(config.umbrellaVaults.waUsdcStakeToken.oracle) == address(0)) { - AaveUmbrellaOracle oracle = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdcStakeToken.vault); - config.umbrellaVaults.waUsdcStakeToken.oracle = IERC4626Oracle(address(oracle)); - } - - StrategyConfig[] memory strategies = new StrategyConfig[](1); - strategies[0] = StrategyConfig({ - category: StrategyCategory.AAVEV3_UMBRELLA, - baseCollateral: config.tokens.usdc, - receiptToken: ERC20(address(config.umbrellaVaults.waUsdcStakeToken.vault)), - oracle: config.umbrellaVaults.waUsdcStakeToken.oracle, - depositContract: address(config.umbrellaVaults.waUsdcStakeToken.vault), - withdrawContract: address(config.umbrellaVaults.waUsdcStakeToken.vault), - heartbeat: 1 days - }); - - address[] memory targets = new address[](4); + address[] memory targets = new address[](2); targets[0] = address(config.levelContracts.vaultManager); - targets[1] = address(config.levelContracts.vaultManager); - targets[2] = address(config.levelContracts.rewardsManager); - targets[3] = address(config.levelContracts.levelMintingV2); + targets[1] = address(config.levelContracts.levelMintingV2); - bytes[] memory payloads = new bytes[](4); - // VaultManager.addAssetStrategy - payloads[0] = abi.encodeWithSelector( - VaultManager.addAssetStrategy.selector, - address(config.tokens.usdc), - address(config.umbrellaVaults.waUsdcStakeToken.vault), - strategies[0] - ); + bytes[] memory payloads = new bytes[](2); // VaultManager.setDefaultStrategies - payloads[1] = abi.encodeWithSignature( + payloads[0] = abi.encodeWithSignature( "setDefaultStrategies(address,address[])", address(config.tokens.usdc), defaultStrategies ); - // RewardsManager.setAllStrategies - payloads[2] = - abi.encodeWithSelector(RewardsManager.setAllStrategies.selector, address(config.tokens.usdc), strategies); - payloads[3] = abi.encodeWithSignature( + payloads[1] = abi.encodeWithSignature( "addOracle(address,address,bool)", address(config.tokens.usdc), address(mockOracle), false ); @@ -410,6 +401,8 @@ contract RewardsManagerMainnetTests is Utils, Configurable { assertApproxEqRel(assetsInUmbrella, deposit, 0.0001e18, "Assets in strategy do not match"); vm.warp(block.timestamp + 100 days); + _mockChainlinkCall(address(config.oracles.ustb), 105e5); // 10.5 USD per USTB + _mockChainlinkCall(address(config.oracles.mNav), 105e6); // 1.05 USD per M // Get Accrued yield uint256 accruedYield = rewardsManager.getAccruedYield(assets).convertDecimalsDown( @@ -418,6 +411,53 @@ contract RewardsManagerMainnetTests is Utils, Configurable { assertGt(accruedYield, 0, "Accrued yield should be greater than 0"); } + function test_getAccruedYield_slashingOfUmbrella_succeeds() public { + // depositing 1M USDC into umbrella + uint256 deposit = 1_000_000e6; + IERC4626StakeToken stakeToken = IERC4626StakeToken(address(config.umbrellaVaults.waUsdcStakeToken.vault)); + + uint256 vaultSharesBefore = vaultManager.vault().balanceOf(address(vaultManager.vault())); + console2.log("vaultSharesBefore", vaultSharesBefore); + + vm.startPrank(strategist.addr); + vaultManager.deposit(address(config.tokens.usdc), address(stakeToken), deposit); + vm.stopPrank(); + + // Get assets in the strategy + uint256 assetsInStrategy = + _getAssetsInStrategy(address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault)); + + // Check total assets + assertApproxEqRel(assetsInStrategy, deposit, 0.000001e18, "Wrong amount of total assets"); + + uint256 accruedYield = rewardsManager.getAccruedYield(assets); + assertApproxEqAbs(accruedYield, 0, 1, "Accrued yield should be 0"); + + vm.warp(block.timestamp + 100 days); + _mockChainlinkCall(address(config.oracles.ustb), 105e5); // 10.5 USD per USTB + _mockChainlinkCall(address(config.oracles.mNav), 105e6); // 1.05 USD per M + + // Get Accrued yield + accruedYield = rewardsManager.getAccruedYield(assets).convertDecimalsDown( + vaultManager.vault().decimals(), ERC20(assets[0]).decimals() + ); + assertGt(accruedYield, 0, "Accrued yield should be greater than 0"); + + // Simulate slashing + vm.prank(Ownable(address(stakeToken)).owner()); + stakeToken.slash(address(1), 1_000_000e6); + + // New assets in the strategy should be less after slashing + assertLt( + _getAssetsInStrategy(address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault)), + assetsInStrategy, + "New assets in the strategy should be less after slashing" + ); + + accruedYield = rewardsManager.getAccruedYield(assets); + assertEq(accruedYield, 0, "Accrued yield should be 0"); + } + function test_rewardYield_morphoYield_succeeds2(uint256 deposit) public { deposit = bound(deposit, 2000, 500_000e6); deal(address(config.tokens.usdc), address(strategist.addr), deposit); @@ -577,39 +617,6 @@ contract RewardsManagerMainnetTests is Utils, Configurable { function test_sparkYield_succeeds(uint256 deposit) public { deposit = bound(deposit, 1000, 1_000_000e6); - if (address(config.sparkVaults.sUsdc.oracle) == address(0)) { - config.sparkVaults.sUsdc.oracle = deployERC4626Oracle(config.sparkVaults.sUsdc.vault, 4 hours); - } - - // Add spark as a strategy - StrategyConfig[] memory strategies = new StrategyConfig[](1); - strategies[0] = StrategyConfig({ - category: StrategyCategory.SPARK, - baseCollateral: config.tokens.usdc, - receiptToken: ERC20(address(config.sparkVaults.sUsdc.vault)), - oracle: config.sparkVaults.sUsdc.oracle, - depositContract: address(config.sparkVaults.sUsdc.vault), - withdrawContract: address(config.sparkVaults.sUsdc.vault), - heartbeat: 1 days - }); - - address[] memory targets = new address[](2); - targets[0] = address(config.levelContracts.vaultManager); - targets[1] = address(config.levelContracts.rewardsManager); - - bytes[] memory payloads = new bytes[](2); - payloads[0] = abi.encodeWithSelector( - VaultManager.addAssetStrategy.selector, - address(config.tokens.usdc), - address(config.sparkVaults.sUsdc.vault), - strategies[0] - ); - payloads[1] = - abi.encodeWithSelector(RewardsManager.setAllStrategies.selector, address(config.tokens.usdc), strategies); - _scheduleAndExecuteAdminActionBatch( - address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads - ); - // Deposit some USDC into the spark vault vm.prank(strategist.addr); vaultManager.deposit(address(config.tokens.usdc), address(config.sparkVaults.sUsdc.vault), deposit); @@ -628,6 +635,8 @@ contract RewardsManagerMainnetTests is Utils, Configurable { // Travel to the future to get yield vm.warp(block.timestamp + 10 days); + _mockChainlinkCall(address(config.oracles.mNav), 105e6); // 1.05 USD per M + _mockChainlinkCall(address(config.oracles.ustb), 105e5); // 10.5 USD per USTB // Get the accrued yield in the redemption asset's decimals accruedYield = rewardsManager.getAccruedYield(assets); @@ -655,37 +664,6 @@ contract RewardsManagerMainnetTests is Utils, Configurable { function test_ustbYield_succeeds(uint256 deposit) public { deposit = bound(deposit, 1000, 1_000_000e6); - // Add ustb as a strategy - StrategyConfig[] memory strategies = new StrategyConfig[](1); - strategies[0] = StrategyConfig({ - category: StrategyCategory.SUPERSTATE, - baseCollateral: config.tokens.usdc, - receiptToken: config.tokens.ustb, - oracle: config.oracles.ustb, - depositContract: address(config.tokens.ustb), - withdrawContract: address(config.periphery.ustbRedemptionIdle), - heartbeat: 1 days - }); - - address[] memory targets = new address[](2); - targets[0] = address(config.levelContracts.vaultManager); - targets[1] = address(config.levelContracts.rewardsManager); - - bytes[] memory payloads = new bytes[](2); - payloads[0] = abi.encodeWithSelector( - VaultManager.addAssetStrategy.selector, - address(config.tokens.usdc), - address(config.tokens.ustb), - strategies[0] - ); - payloads[1] = - abi.encodeWithSelector(RewardsManager.setAllStrategies.selector, address(config.tokens.usdc), strategies); - _scheduleAndExecuteAdminActionBatch( - address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads - ); - - _mockChainlinkCall(USTB_CHAINLINK_FEED, 105e5); // 10.5 USD per USTB - (uint256 superstateTokenOutAmount,,) = ISuperstateToken(address(config.tokens.ustb)).calculateSuperstateTokenOut( deposit, address(config.tokens.usdc) ); @@ -808,7 +786,7 @@ contract RewardsManagerMainnetTests is Utils, Configurable { console2.log(vm.getLabel(asset), ERC20(asset).balanceOf(vault)); } - function deployERC4626Oracle(IERC4626 vault, uint256 delay) public returns (IERC4626Oracle) { + function deployERC4626Oracle(IERC4626 vault) public returns (IERC4626Oracle) { if (address(config.levelContracts.erc4626OracleFactory) == address(0)) { revert("ERC4626OracleFactory must be deployed first"); } @@ -819,6 +797,75 @@ contract RewardsManagerMainnetTests is Utils, Configurable { return _erc4626Oracle; } + function _deployNewOracles() internal { + // Deploy CappedMNavOracle + CappedOneDollarOracle mNavOracle = new CappedOneDollarOracle(address(config.oracles.mNav)); + + // Deploy sUsdcOracle + config.sparkVaults.sUsdc.oracle = deployERC4626Oracle(config.sparkVaults.sUsdc.vault); + + // Deploy waUsdcStakeTokenOracle + AaveUmbrellaOracle oracle = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdcStakeToken.vault); + config.umbrellaVaults.waUsdcStakeToken.oracle = IERC4626Oracle(address(oracle)); + + // Deploy waUsdtStakeTokenOracle + AaveUmbrellaOracle oracleUsdt = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdtStakeToken.vault); + config.umbrellaVaults.waUsdtStakeToken.oracle = IERC4626Oracle(address(oracleUsdt)); + } + + function _deploySwapManager() internal { + // Replicate SwapManager deploy script + CappedOneDollarOracle mNavOracle = new CappedOneDollarOracle(address(config.oracles.mNav)); + config.oracles.cappedMNav = AggregatorV3Interface(address(mNavOracle)); + + bytes memory constructorArgs = abi.encodeWithSignature( + "initialize(address,address,address)", + deployer.addr, + address(config.periphery.uniswapV3Router), + address(config.levelContracts.pauserGuard) + ); + + SwapManager _swapManager = new SwapManager(); + ERC1967Proxy _swapManagerProxy = new ERC1967Proxy(address(_swapManager), constructorArgs); + + config.levelContracts.swapManager = SwapManager(address(_swapManagerProxy)); + config.levelContracts.swapManager.setAuthority(config.levelContracts.rolesAuthority); + + config.levelContracts.swapManager.setSwapConfig( + address(config.tokens.usdc), + address(config.tokens.wrappedM), + SwapConfig({ + pool: 0x970A7749EcAA4394C8B2Bf5F2471F41FD6b79288, // wM/USDC pool + fee: 100, //0.01% + tickLower: -10, + tickUpper: 10, + slippageBps: 5, //0.05% + active: true + }) + ); + + config.levelContracts.swapManager.setSwapConfig( + address(config.tokens.wrappedM), + address(config.tokens.usdc), + SwapConfig({ + pool: 0x970A7749EcAA4394C8B2Bf5F2471F41FD6b79288, // wM/USDC pool + fee: 100, //0.01% + tickLower: -10, + tickUpper: 10, + slippageBps: 5, //0.05% + active: true + }) + ); + + config.levelContracts.swapManager.addOracle(address(config.tokens.usdc), address(config.oracles.usdc)); + config.levelContracts.swapManager.addOracle(address(config.tokens.wrappedM), address(config.oracles.cappedMNav)); + config.levelContracts.swapManager.setHeartBeat(address(config.tokens.usdc), 1 days); + config.levelContracts.swapManager.setHeartBeat(address(config.tokens.wrappedM), 26 hours); + + // Transfer ownership to admin timelock + config.levelContracts.swapManager.transferOwnership(address(config.levelContracts.adminTimelock)); + } + function _upgradeRewardsManager() internal { RewardsManager impl = new RewardsManager(); vm.prank(address(config.levelContracts.adminTimelock)); diff --git a/test/v2/integration/VaultManager.t.sol b/test/v2/integration/VaultManager.t.sol index 78fda47..79b7a86 100644 --- a/test/v2/integration/VaultManager.t.sol +++ b/test/v2/integration/VaultManager.t.sol @@ -27,6 +27,9 @@ import {CappedOneDollarOracle} from "@level/src/v2/oracles/CappedOneDollarOracle import {AaveUmbrellaOracle} from "@level/src/v2/oracles/AaveUmbrellaOracle.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {SwapConfig} from "@level/src/v2/usd/SwapManager.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Upgradev2_1} from "@level/script/v2/Upgradev2_1.s.sol"; +import {RewardsManager} from "@level/src/v2/usd/RewardsManager.sol"; contract VaultManagerMainnetTests is Utils, Configurable { using SafeTransferLib for ERC20; @@ -49,27 +52,34 @@ contract VaultManagerMainnetTests is Utils, Configurable { StrategyConfig public re7UsdcConfig; StrategyConfig public steakhouseUsdtLiteConfig; StrategyConfig public sparkUsdcConfig; - StrategyConfig public umbrellaConfig; + StrategyConfig public umbrellaUsdcConfig; + StrategyConfig public umbrellaUsdtConfig; event Referral(uint16 indexed referral, address indexed owner, uint256 assets, uint256 shares); function setUp() public { forkMainnet(22664895); - initConfig(1); deployer = vm.createWallet("deployer"); strategist = vm.createWallet("strategist"); + initConfig(1); + _deployNewOracles(); + vm.startPrank(deployer.addr); _deploySwapManager(); vm.stopPrank(); - _upgradeVaultManager(); + Upgradev2_1 upgrade = new Upgradev2_1(); + upgrade.setUp_(config); - // Setup strategist - vm.prank(config.users.admin); - _setupVaultsForTests(); - _setupTreasuriesForTests(); + RewardsManager newRewardsManager = new RewardsManager(); + VaultManager newVaultManager = new VaultManager(); + (address[] memory targetsSetup, bytes[] memory payloadsSetup) = + upgrade.run(address(newVaultManager), address(newRewardsManager)); + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targetsSetup, payloadsSetup + ); address[] memory targets = new address[](2); targets[0] = address(config.levelContracts.rolesAuthority); @@ -98,6 +108,10 @@ contract VaultManagerMainnetTests is Utils, Configurable { vaultManager = config.levelContracts.vaultManager; + _setTotalStrategies(); + } + + function _setTotalStrategies() internal { address[] memory usdcStrategyAddresses = vaultManager.getDefaultStrategies(address(config.tokens.usdc)); address[] memory usdtStrategyAddresses = vaultManager.getDefaultStrategies(address(config.tokens.usdt)); @@ -149,6 +163,22 @@ contract VaultManagerMainnetTests is Utils, Configurable { } } + function _deployNewOracles() internal { + // Deploy CappedMNavOracle + CappedOneDollarOracle mNavOracle = new CappedOneDollarOracle(address(config.oracles.mNav)); + + // Deploy sUsdcOracle + config.sparkVaults.sUsdc.oracle = deployERC4626Oracle(config.sparkVaults.sUsdc.vault); + + // Deploy waUsdcStakeTokenOracle + AaveUmbrellaOracle oracle = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdcStakeToken.vault); + config.umbrellaVaults.waUsdcStakeToken.oracle = IERC4626Oracle(address(oracle)); + + // Deploy waUsdtStakeTokenOracle + AaveUmbrellaOracle oracleUsdt = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdtStakeToken.vault); + config.umbrellaVaults.waUsdtStakeToken.oracle = IERC4626Oracle(address(oracleUsdt)); + } + function _deploySwapManager() internal { // Replicate SwapManager deploy script CappedOneDollarOracle mNavOracle = new CappedOneDollarOracle(address(config.oracles.mNav)); @@ -202,66 +232,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { config.levelContracts.swapManager.transferOwnership(address(config.levelContracts.adminTimelock)); } - function _upgradeVaultManager() internal { - VaultManager impl = new VaultManager(); - vm.prank(address(config.levelContracts.adminTimelock)); - config.levelContracts.vaultManager.upgradeToAndCall(address(impl), ""); - } - - function _setupTreasuriesForTests() internal { - CappedOneDollarOracle mNavOracle = CappedOneDollarOracle(address(config.oracles.cappedMNav)); - - StrategyConfig memory ustbConfig = StrategyConfig({ - category: StrategyCategory.SUPERSTATE, - baseCollateral: config.tokens.usdc, - receiptToken: config.tokens.ustb, - oracle: config.oracles.ustb, - depositContract: address(config.tokens.ustb), - withdrawContract: address(config.periphery.ustbRedemptionIdle), - heartbeat: 1 days - }); - - StrategyConfig memory mConfig = StrategyConfig({ - category: StrategyCategory.M0, - baseCollateral: config.tokens.usdc, - receiptToken: config.tokens.wrappedM, - oracle: AggregatorV3Interface(address(mNavOracle)), - depositContract: address(config.levelContracts.swapManager), - withdrawContract: address(config.levelContracts.swapManager), - heartbeat: 26 hours - }); - - address[] memory targets = new address[](2); - targets[0] = address(config.levelContracts.vaultManager); - targets[1] = address(config.levelContracts.vaultManager); - - bytes[] memory payloads = new bytes[](2); - // Add ustb as a strategy - payloads[0] = abi.encodeWithSelector( - config.levelContracts.vaultManager.addAssetStrategy.selector, - address(config.tokens.usdc), - address(config.tokens.ustb), - ustbConfig - ); - // Add m as a strategy - payloads[1] = abi.encodeWithSelector( - config.levelContracts.vaultManager.addAssetStrategy.selector, - address(config.tokens.usdc), - address(config.tokens.wrappedM), - mConfig - ); - - _scheduleAndExecuteAdminActionBatch( - address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads - ); - } - function _setupVaultsForTests() internal { //--------------- Add test Morpho vaults as strategies - if (address(config.morphoVaults.steakhouseUsdt.vault) == address(0)) { - revert("Steakhouse USDT vaults not deployed"); - } - if (address(config.morphoVaults.re7Usdc.vault) == address(0)) { revert("Re7 USDC vaults not deployed"); } @@ -270,30 +242,21 @@ contract VaultManagerMainnetTests is Utils, Configurable { revert("Steakhouse USDT Lite vaults not deployed"); } - if (address(config.sparkVaults.sUsdc.vault) == address(0)) { - revert("Spark USDC vault not deployed"); - } - - if (address(config.umbrellaVaults.waUsdcStakeToken.vault) == address(0)) { - revert("Umbrella vault not deployed"); - } - if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { - config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault, 4 hours); + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); } if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { - config.morphoVaults.steakhouseUsdt.oracle = - deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault, 4 hours); + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); } if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { config.morphoVaults.steakhouseUsdtLite.oracle = - deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault, 4 hours); + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); } if (address(config.sparkVaults.sUsdc.oracle) == address(0)) { - config.sparkVaults.sUsdc.oracle = deployERC4626Oracle(config.sparkVaults.sUsdc.vault, 4 hours); + config.sparkVaults.sUsdc.oracle = deployERC4626Oracle(config.sparkVaults.sUsdc.vault); } if (address(config.umbrellaVaults.waUsdcStakeToken.oracle) == address(0)) { @@ -301,6 +264,11 @@ contract VaultManagerMainnetTests is Utils, Configurable { config.umbrellaVaults.waUsdcStakeToken.oracle = IERC4626Oracle(address(oracle)); } + if (address(config.umbrellaVaults.waUsdtStakeToken.oracle) == address(0)) { + AaveUmbrellaOracle oracle = new AaveUmbrellaOracle(config.umbrellaVaults.waUsdtStakeToken.vault); + config.umbrellaVaults.waUsdtStakeToken.oracle = IERC4626Oracle(address(oracle)); + } + //--------------- Add test Morpho vaults as strategies steakhouseUsdcConfig = StrategyConfig({ category: StrategyCategory.MORPHO, @@ -352,7 +320,7 @@ contract VaultManagerMainnetTests is Utils, Configurable { heartbeat: 1 days }); - umbrellaConfig = StrategyConfig({ + umbrellaUsdcConfig = StrategyConfig({ category: StrategyCategory.AAVEV3_UMBRELLA, baseCollateral: config.tokens.usdc, receiptToken: ERC20(address(config.umbrellaVaults.waUsdcStakeToken.vault)), @@ -362,6 +330,16 @@ contract VaultManagerMainnetTests is Utils, Configurable { heartbeat: 1 days }); + umbrellaUsdtConfig = StrategyConfig({ + category: StrategyCategory.AAVEV3_UMBRELLA, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.umbrellaVaults.waUsdtStakeToken.vault)), + oracle: config.umbrellaVaults.waUsdtStakeToken.oracle, + depositContract: address(config.umbrellaVaults.waUsdtStakeToken.vault), + withdrawContract: address(config.umbrellaVaults.waUsdtStakeToken.vault), + heartbeat: 1 days + }); + address[] memory usdcDefaultStrategies = new address[](4); usdcDefaultStrategies[0] = address(config.periphery.aaveV3); usdcDefaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); @@ -373,7 +351,7 @@ contract VaultManagerMainnetTests is Utils, Configurable { usdtDefaultStrategies[1] = address(config.morphoVaults.steakhouseUsdt.vault); usdtDefaultStrategies[2] = address(config.morphoVaults.steakhouseUsdtLite.vault); - address[] memory targets = new address[](9); + address[] memory targets = new address[](10); targets[0] = address(config.levelContracts.vaultManager); targets[1] = address(config.levelContracts.vaultManager); targets[2] = address(config.levelContracts.vaultManager); @@ -381,10 +359,11 @@ contract VaultManagerMainnetTests is Utils, Configurable { targets[4] = address(config.levelContracts.vaultManager); targets[5] = address(config.levelContracts.vaultManager); targets[6] = address(config.levelContracts.vaultManager); - targets[7] = address(config.levelContracts.rolesAuthority); + targets[7] = address(config.levelContracts.vaultManager); targets[8] = address(config.levelContracts.rolesAuthority); + targets[9] = address(config.levelContracts.rolesAuthority); - bytes[] memory payloads = new bytes[](9); + bytes[] memory payloads = new bytes[](10); payloads[0] = abi.encodeWithSelector( VaultManager.addAssetStrategy.selector, address(config.tokens.usdc), @@ -413,29 +392,19 @@ contract VaultManagerMainnetTests is Utils, Configurable { VaultManager.addAssetStrategy.selector, address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault), - umbrellaConfig + umbrellaUsdcConfig ); payloads[5] = abi.encodeWithSelector( - VaultManager.setDefaultStrategies.selector, address(config.tokens.usdc), usdcDefaultStrategies + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.umbrellaVaults.waUsdtStakeToken.vault), + umbrellaUsdtConfig ); payloads[6] = abi.encodeWithSelector( - VaultManager.setDefaultStrategies.selector, address(config.tokens.usdt), usdtDefaultStrategies + VaultManager.setDefaultStrategies.selector, address(config.tokens.usdc), usdcDefaultStrategies ); - // Add cooldown operator capability to strategist role payloads[7] = abi.encodeWithSelector( - config.levelContracts.rolesAuthority.setRoleCapability.selector, - STRATEGIST_ROLE, - address(config.levelContracts.vaultManager), - bytes4(abi.encodeWithSignature("modifyAaveUmbrellaCooldownOperator(address,address,bool)")), - true - ); - // Add rewards claimer capability to strategist role - payloads[8] = abi.encodeWithSelector( - config.levelContracts.rolesAuthority.setRoleCapability.selector, - STRATEGIST_ROLE, - address(config.levelContracts.vaultManager), - bytes4(abi.encodeWithSignature("modifyAaveUmbrellaRewardsClaimer(address,address,bool)")), - true + VaultManager.setDefaultStrategies.selector, address(config.tokens.usdt), usdtDefaultStrategies ); _scheduleAndExecuteAdminActionBatch( @@ -443,7 +412,7 @@ contract VaultManagerMainnetTests is Utils, Configurable { ); } - function deployERC4626Oracle(IERC4626 vault, uint256 delay) public returns (IERC4626Oracle) { + function deployERC4626Oracle(IERC4626 vault) public returns (IERC4626Oracle) { if (address(config.levelContracts.erc4626OracleFactory) == address(0)) { revert("ERC4626OracleFactory must be deployed first"); } @@ -613,14 +582,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { // ------------- Umbrella Tests ------------- function test_modifyCooldownOperator_succeeds() public { - // Create a new operator - address operator = makeAddr("operator"); address umbrellaVault = address(config.umbrellaVaults.waUsdcStakeToken.vault); - vm.startPrank(strategist.addr); - vaultManager.modifyAaveUmbrellaCooldownOperator(operator, umbrellaVault, true); - vm.stopPrank(); - // Check we're not in cooldown IERC4626StakeToken.CooldownSnapshot memory cooldownSnapshot = IERC4626StakeToken(umbrellaVault).getStakerCooldown(address(config.levelContracts.boringVault)); @@ -631,7 +594,7 @@ contract VaultManagerMainnetTests is Utils, Configurable { deal(umbrellaVault, address(config.levelContracts.boringVault), 1e18); // Call cooldown on behalf of the boring vault - vm.prank(operator); + vm.prank(config.users.operator); IERC4626StakeToken(umbrellaVault).cooldownOnBehalfOf(address(config.levelContracts.boringVault)); // Check we're in cooldown @@ -670,17 +633,11 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_settingClaimer_succeeds() public { - // Create a new operator - address operator = makeAddr("operator"); address umbrellaRewardsController = 0x4655Ce3D625a63d30bA704087E52B4C31E38188B; address umbrellaVault = address(config.umbrellaVaults.waUsdcStakeToken.vault); - vm.startPrank(strategist.addr); - vaultManager.modifyAaveUmbrellaRewardsClaimer(operator, umbrellaRewardsController, true); - vm.stopPrank(); - (bool isClaimerAuthorized,) = umbrellaRewardsController.staticcall( - abi.encodeWithSignature("isClaimerAuthorized(address,address)", umbrellaVault, operator) + abi.encodeWithSignature("isClaimerAuthorized(address,address)", address(0), config.users.protocolTreasury) ); assertEq(isClaimerAuthorized, true); } @@ -710,6 +667,72 @@ contract VaultManagerMainnetTests is Utils, Configurable { assertApproxEqAbs(balance, expectedStakedBalance, 1, "Wrong amount of stk-waToken"); } + function test_depositingToUmbrellaUsdt_succeeds(uint256 deposit) public { + deposit = bound(deposit, 1e3, INITIAL_BALANCE); + vm.startPrank(strategist.addr); + + // Deposit USDT into the vault + vaultManager.deposit( + address(config.tokens.usdt), address(config.umbrellaVaults.waUsdtStakeToken.vault), deposit + ); + + // Expected waUsdt balance after depositing aUsdt + uint256 expectedWrappedBalance = + IERC4626(config.umbrellaVaults.waUsdtStakeToken.vault.asset()).previewDeposit(deposit); + + // Expected stk-waToken balance after depositing waUsdt + uint256 expectedStakedBalance = + config.umbrellaVaults.waUsdtStakeToken.vault.previewDeposit(expectedWrappedBalance); + + // Balance of stk-waToken in the vault + uint256 balance = + config.umbrellaVaults.waUsdtStakeToken.vault.balanceOf(address(config.levelContracts.boringVault)); + + // Allow rounding error of 1 + assertApproxEqAbs(balance, expectedStakedBalance, 1, "Wrong amount of stk-waToken"); + } + + function test_slashingOfUmbrellaStake(uint256 deposit) public { + // depositing 1M USDC into umbrella + uint256 deposit = 1_000_000e6; + IERC4626StakeToken stakeToken = IERC4626StakeToken(address(config.umbrellaVaults.waUsdcStakeToken.vault)); + + vm.startPrank(strategist.addr); + vaultManager.deposit( + address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault), deposit + ); + vm.stopPrank(); + + // Get assets in the strategy + uint256 assetsInStrategy = + _getAssetsInStrategy(address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault)); + + // Check total assets + assertApproxEqRel(assetsInStrategy, deposit, 0.000001e18, "Wrong amount of total assets"); + + uint256 rate = stakeToken.convertToAssets(1e6); + + // Simulate slashing + vm.prank(Ownable(address(stakeToken)).owner()); + stakeToken.slash(address(1), 1_000_000e6); + + uint256 newRate = stakeToken.convertToAssets(1e6); + + // New rate should be less + assertLt(newRate, rate); + + // New assets in the strategy should be less after slashing + assertLt( + _getAssetsInStrategy(address(config.tokens.usdc), address(config.umbrellaVaults.waUsdcStakeToken.vault)), + assetsInStrategy, + "New assets in the strategy should be less after slashing" + ); + + // We see that if we had around 1M USDC invested in Aave Umbrella, + // and if a slashing event occured which seized 1M USDC from the vault, + // Our total loss was around 48k USDC. + } + function test_wrappingOfUsdcToWaUsdc_succeeds(uint256 deposit) public { deposit = bound(deposit, 1e3, INITIAL_BALANCE); deal(address(config.tokens.usdc), strategist.addr, deposit); @@ -1021,18 +1044,117 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_depositDefault_usdc_morphoV1_1_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); _depositDefault_vaultOnly(deposit, config.tokens.usdc, address(config.morphoVaults.re7Usdc.vault)); } function test_depositDefault_usdt_morphoOnly_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ) + ); _depositDefault_vaultOnly(deposit, config.tokens.usdt, address(config.morphoVaults.steakhouseUsdt.vault)); } function test_depositDefault_usdt_morphoV1_1_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ) + ); + _depositDefault_vaultOnly(deposit, config.tokens.usdt, address(config.morphoVaults.steakhouseUsdtLite.vault)); } function test_depositDefault_usdc_multipleStrategiesWithdrawSome(uint256 deposit) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); @@ -1104,6 +1226,59 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_depositDefault_usdt_multipleStrategiesWithdrawSome(uint256 deposit) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + address[] memory targets = new address[](2); + bytes[] memory payloads = new bytes[](2); + + targets[0] = address(config.levelContracts.vaultManager); + targets[1] = address(config.levelContracts.vaultManager); + + payloads[0] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ); + + payloads[1] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ); + + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdt.vault); @@ -1156,22 +1331,122 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_withdrawDefault_usdc_morphoV1_1_succeeds(uint256 deposit, uint256 withdrawal) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); _withdrawDefault_vaultOnly(deposit, withdrawal, config.tokens.usdc, address(config.morphoVaults.re7Usdc.vault)); } function test_withdrawDefault_usdt_morphoOnly_succeeds(uint256 deposit, uint256 withdrawal) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ) + ); + _withdrawDefault_vaultOnly( deposit, withdrawal, config.tokens.usdt, address(config.morphoVaults.steakhouseUsdt.vault) ); } function test_withdrawDefault_usdt_morphoV1_1_succeeds(uint256 deposit, uint256 withdrawal) public { + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ) + ); + _withdrawDefault_vaultOnly( deposit, withdrawal, config.tokens.usdt, address(config.morphoVaults.steakhouseUsdtLite.vault) ); } function test_withdrawDefault_usdc_multipleStrategiesWithdrawAll(uint256 deposit, uint256 withdrawal) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); @@ -1181,6 +1456,59 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_withdrawDefault_usdt_multipleStrategiesWithdrawAll(uint256 deposit, uint256 withdrawal) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + address[] memory targets = new address[](2); + bytes[] memory payloads = new bytes[](2); + + targets[0] = address(config.levelContracts.vaultManager); + targets[1] = address(config.levelContracts.vaultManager); + + payloads[0] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ); + + payloads[1] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ); + + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdt.vault); @@ -1190,6 +1518,31 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_rebalance_usdc_fromAave_toMorpho_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); @@ -1205,6 +1558,31 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_rebalance_usdc_fromMorpho_toAave_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.re7Usdc.oracle) == address(0)) { + config.morphoVaults.re7Usdc.oracle = deployERC4626Oracle(config.morphoVaults.re7Usdc.vault); + } + re7UsdcConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdc, + receiptToken: ERC20(address(config.morphoVaults.re7Usdc.vault)), + oracle: config.morphoVaults.re7Usdc.oracle, + depositContract: address(config.morphoVaults.re7Usdc.vault), + withdrawContract: address(config.morphoVaults.re7Usdc.vault), + heartbeat: 1 days + }); + + _scheduleAndExecuteAdminAction( + address(config.users.admin), + address(config.levelContracts.adminTimelock), + address(config.levelContracts.vaultManager), + abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdc), + address(config.morphoVaults.re7Usdc.vault), + re7UsdcConfig + ) + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdc.vault); @@ -1235,6 +1613,59 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_rebalance_usdt_betweenMorpho_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + address[] memory targets = new address[](2); + bytes[] memory payloads = new bytes[](2); + + targets[0] = address(config.levelContracts.vaultManager); + targets[1] = address(config.levelContracts.vaultManager); + + payloads[0] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ); + + payloads[1] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ); + + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdt.vault); @@ -1250,6 +1681,59 @@ contract VaultManagerMainnetTests is Utils, Configurable { } function test_rebalance_usdt_fromMorpho_toAave_succeeds(uint256 deposit) public { + if (address(config.morphoVaults.steakhouseUsdt.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdt.oracle = deployERC4626Oracle(config.morphoVaults.steakhouseUsdt.vault); + } + + if (address(config.morphoVaults.steakhouseUsdtLite.oracle) == address(0)) { + config.morphoVaults.steakhouseUsdtLite.oracle = + deployERC4626Oracle(config.morphoVaults.steakhouseUsdtLite.vault); + } + + steakhouseUsdtConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdt.vault)), + oracle: config.morphoVaults.steakhouseUsdt.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdt.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdt.vault), + heartbeat: 1 days + }); + + steakhouseUsdtLiteConfig = StrategyConfig({ + category: StrategyCategory.MORPHO, + baseCollateral: config.tokens.usdt, + receiptToken: ERC20(address(config.morphoVaults.steakhouseUsdtLite.vault)), + oracle: config.morphoVaults.steakhouseUsdtLite.oracle, + depositContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + withdrawContract: address(config.morphoVaults.steakhouseUsdtLite.vault), + heartbeat: 1 days + }); + + address[] memory targets = new address[](2); + bytes[] memory payloads = new bytes[](2); + + targets[0] = address(config.levelContracts.vaultManager); + targets[1] = address(config.levelContracts.vaultManager); + + payloads[0] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdt.vault), + steakhouseUsdtConfig + ); + + payloads[1] = abi.encodeWithSelector( + VaultManager.addAssetStrategy.selector, + address(config.tokens.usdt), + address(config.morphoVaults.steakhouseUsdtLite.vault), + steakhouseUsdtLiteConfig + ); + + _scheduleAndExecuteAdminActionBatch( + address(config.users.admin), address(config.levelContracts.adminTimelock), targets, payloads + ); + address[] memory defaultStrategies = new address[](3); defaultStrategies[0] = address(config.periphery.aaveV3); defaultStrategies[1] = address(config.morphoVaults.steakhouseUsdt.vault); @@ -1502,6 +1986,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { "Wrong amount of vault shares" ); + _setTotalStrategies(); + assertApproxEqRel( _getTotalAssets(address(asset)), INITIAL_BALANCE, 0.000001e18, "Wrong amount of total assets after deposit" ); @@ -1538,6 +2024,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { "Wrong amount of underlying" ); + _setTotalStrategies(); + assertApproxEqRel( _getTotalAssets(address(asset)), INITIAL_BALANCE, 0.000001e18, "Wrong amount of total assets after deposit" ); @@ -1566,6 +2054,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { "Wrong amount of underlying after deposit" ); + _setTotalStrategies(); + assertApproxEqRel( _getTotalAssets(address(asset)), INITIAL_BALANCE, 0.000001e18, "Wrong amount of total assets after deposit" ); @@ -1603,6 +2093,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { "Wrong amount after withdrawal" ); + _setTotalStrategies(); + assertApproxEqRel( _getTotalAssets(address(asset)), INITIAL_BALANCE, @@ -1626,6 +2118,8 @@ contract VaultManagerMainnetTests is Utils, Configurable { abi.encodeWithSignature("setDefaultStrategies(address,address[])", address(asset), defaultStrategies) ); + _setTotalStrategies(); + vm.startPrank(strategist.addr); for (uint256 i = 0; i < defaultStrategies.length; i++) { @@ -1741,7 +2235,7 @@ contract VaultManagerMainnetTests is Utils, Configurable { function test_removeAssetStrategy_succeeds() public { // Get initial state address[] memory initialUsdcStrategies = vaultManager.getDefaultStrategies(address(config.tokens.usdc)); - assertEq(initialUsdcStrategies.length, 4, "Initial USDC strategies count should be 4"); + assertEq(initialUsdcStrategies.length, 3, "Initial USDC strategies count should be 3"); // Remove Aave V3 strategy (which is in defaultStrategies) vm.startPrank(config.users.admin); @@ -1750,13 +2244,13 @@ contract VaultManagerMainnetTests is Utils, Configurable { // Verify Aave V3 was removed from defaultStrategies address[] memory afterAaveRemoval = vaultManager.getDefaultStrategies(address(config.tokens.usdc)); - assertEq(afterAaveRemoval.length, 3, "USDC strategies count should be 3 after removing Aave"); + assertEq(afterAaveRemoval.length, 2, "USDC strategies count should be 2 after removing Aave"); assertEq( afterAaveRemoval[0], address(config.morphoVaults.steakhouseUsdc.vault), "First strategy should be Steakhouse" ); - assertEq(afterAaveRemoval[1], address(config.morphoVaults.re7Usdc.vault), "Second strategy should be Re7"); + assertEq(afterAaveRemoval[1], address(config.sparkVaults.sUsdc.vault), "Second strategy should be Spark"); // Verify Aave V3 was removed from assetToStrategy ( @@ -1772,23 +2266,24 @@ contract VaultManagerMainnetTests is Utils, Configurable { uint256(category), uint256(StrategyCategory.UNDEFINED), "Aave strategy should be undefined after removal" ); - // Remove Re7 strategy + // Remove Spark strategy vm.startPrank(config.users.admin); - vaultManager.removeAssetStrategy(address(config.tokens.usdc), address(config.morphoVaults.re7Usdc.vault)); + vaultManager.removeAssetStrategy(address(config.tokens.usdc), address(config.sparkVaults.sUsdc.vault)); vm.stopPrank(); - // Verify Re7 was removed from defaultStrategies - address[] memory afterRe7Removal = vaultManager.getDefaultStrategies(address(config.tokens.usdc)); - assertEq(afterRe7Removal.length, 2, "USDC strategies count should be 2 after removing Re7"); + // Verify Spark was removed from defaultStrategies + address[] memory afterSparkRemoval = vaultManager.getDefaultStrategies(address(config.tokens.usdc)); + assertEq(afterSparkRemoval.length, 1, "USDC strategies count should be 1 after removing Spark"); assertEq( - afterRe7Removal[0], address(config.morphoVaults.steakhouseUsdc.vault), "First strategy should be Steakhouse" + afterSparkRemoval[0], + address(config.morphoVaults.steakhouseUsdc.vault), + "First strategy should be Steakhouse" ); - assertEq(afterRe7Removal[1], address(config.sparkVaults.sUsdc.vault), "Second strategy should be Spark"); - // Verify Re7 was removed from assetToStrategy + // Verify Spark was removed from assetToStrategy (category, baseCollateral, receiptToken, oracle, depositContract, withdrawContract, heartbeat) = - vaultManager.assetToStrategy(address(config.tokens.usdc), address(config.morphoVaults.re7Usdc.vault)); + vaultManager.assetToStrategy(address(config.tokens.usdc), address(config.sparkVaults.sUsdc.vault)); assertEq( - uint256(category), uint256(StrategyCategory.UNDEFINED), "Re7 strategy should be undefined after removal" + uint256(category), uint256(StrategyCategory.UNDEFINED), "Spark strategy should be undefined after removal" ); } }