diff --git a/.gitmodules b/.gitmodules index e26b691..4b7350e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/aave-v3-core"] path = lib/aave-v3-core url = https://github.com/aave/aave-v3-core +[submodule "lib/morpho-blue"] + path = lib/morpho-blue + url = https://github.com/morpho-org/morpho-blue diff --git a/broadcast/deploy-eth.s.sol/1/run-1769611299.json b/broadcast/deploy-eth.s.sol/1/run-1769611299.json new file mode 100644 index 0000000..4811b67 --- /dev/null +++ b/broadcast/deploy-eth.s.sol/1/run-1769611299.json @@ -0,0 +1,128 @@ +{ + "transactions": [ + { + "hash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "function": "seed(uint256)", + "arguments": [ + "10000000000000000" + ], + "transaction": { + "from": "0xf5ac5943d16fc865824910033b756519dc396682", + "to": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "gas": "0x11092a", + "value": "0x0", + "input": "0x95564837000000000000000000000000000000000000000000000000002386f26fc10000", + "nonce": "0x3c", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x2c18394", + "logs": [ + { + "address": "0xc808b5f2315e47ddf95d2ce63e1445024a3dc4c9", + "topics": [ + "0x9a3674ff60a2c4c28f4df6f1f1c4a5f9721658e388e8a66ac38bf55e7de4d953" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc10000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x441", + "removed": false + }, + { + "address": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000b402e0479579b00f9dafc0526234bbc4583920b4" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x442", + "removed": false + }, + { + "address": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000b402e0479579b00f9dafc0526234bbc4583920b4" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x443", + "removed": false + }, + { + "address": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682" + ], + "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x444", + "removed": false + }, + { + "address": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "topics": [ + "0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000de0b6b3a7640000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x445", + "removed": false + } + ], + "logsBloom": "0x00000000000400008000000080000400000000000000000000000004000000000000000000000000000000000000000800000000000000000004000004200000000000410800000000000008000000000000000000000000000100000000000000000002020000000000000000000800000000000004000000000010000000000000000800000000002000000000000000000000008000000000000000000000020000000000000000000000000000000000000000100000000000000000000000000002000000000000000000000000020000000008000000000000000020000010000000000000000000000000000000000000000000000000000000040000", + "type": "0x2", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "gasUsed": "0xc6219", + "effectiveGasPrice": "0xc1e07ab", + "from": "0xf5ac5943d16fc865824910033b756519dc396682", + "to": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1769611299, + "chain": 1, + "commit": "4197572" +} \ No newline at end of file diff --git a/broadcast/deploy-eth.s.sol/1/run-latest.json b/broadcast/deploy-eth.s.sol/1/run-latest.json new file mode 100644 index 0000000..4811b67 --- /dev/null +++ b/broadcast/deploy-eth.s.sol/1/run-latest.json @@ -0,0 +1,128 @@ +{ + "transactions": [ + { + "hash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "function": "seed(uint256)", + "arguments": [ + "10000000000000000" + ], + "transaction": { + "from": "0xf5ac5943d16fc865824910033b756519dc396682", + "to": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "gas": "0x11092a", + "value": "0x0", + "input": "0x95564837000000000000000000000000000000000000000000000000002386f26fc10000", + "nonce": "0x3c", + "chainId": "0x1" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x2c18394", + "logs": [ + { + "address": "0xc808b5f2315e47ddf95d2ce63e1445024a3dc4c9", + "topics": [ + "0x9a3674ff60a2c4c28f4df6f1f1c4a5f9721658e388e8a66ac38bf55e7de4d953" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc10000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x441", + "removed": false + }, + { + "address": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000b402e0479579b00f9dafc0526234bbc4583920b4" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x442", + "removed": false + }, + { + "address": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000b402e0479579b00f9dafc0526234bbc4583920b4" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc10000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x443", + "removed": false + }, + { + "address": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682" + ], + "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x444", + "removed": false + }, + { + "address": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "topics": [ + "0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682", + "0x000000000000000000000000f5ac5943d16fc865824910033b756519dc396682" + ], + "data": "0x000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000de0b6b3a7640000", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "blockTimestamp": "0x697a201f", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "logIndex": "0x445", + "removed": false + } + ], + "logsBloom": "0x00000000000400008000000080000400000000000000000000000004000000000000000000000000000000000000000800000000000000000004000004200000000000410800000000000008000000000000000000000000000100000000000000000002020000000000000000000800000000000004000000000010000000000000000800000000002000000000000000000000008000000000000000000000020000000000000000000000000000000000000000100000000000000000000000000002000000000000000000000000020000000008000000000000000020000010000000000000000000000000000000000000000000000000000000040000", + "type": "0x2", + "transactionHash": "0xc444efa8f79193262d23fb8db2650e732690bdc1afa465c11d9558c2abc2ed5f", + "transactionIndex": "0x1ba", + "blockHash": "0xfaa5b5eaf14d8410443732e11a079a52d41f85c69f1781781e26116fc359d865", + "blockNumber": "0x1734eb3", + "gasUsed": "0xc6219", + "effectiveGasPrice": "0xc1e07ab", + "from": "0xf5ac5943d16fc865824910033b756519dc396682", + "to": "0xb402e0479579b00f9dafc0526234bbc4583920b4", + "contractAddress": null + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1769611299, + "chain": 1, + "commit": "4197572" +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 65ccabb..ee0797a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,9 @@ viaIR = true [rpc_endpoints] etherlink="https://node.mainnet.etherlink.com" hyperevm="https://rpc.hyperlend.finance" +mainnet="https://ethereum-rpc.publicnode.com" [etherscan] etherlink={ key = "NO_KEY", chain = 42793, url= "https://explorer.etherlink.com/api/" } +mainnet={ key = "${ETHERSCAN_API_KEY}", chain = 1 } + diff --git a/lib/morpho-blue b/lib/morpho-blue new file mode 160000 index 0000000..55d2d99 --- /dev/null +++ b/lib/morpho-blue @@ -0,0 +1 @@ +Subproject commit 55d2d99304fb3fb930c688462ae2ccabb1d533ad diff --git a/remappings.txt b/remappings.txt index 64bcbbd..8d6c14f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ forge-std/=lib/forge-std/src/ halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ aave-v3-core/=lib/aave-v3-core/ -openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ \ No newline at end of file +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ +morpho-blue/=lib/morpho-blue/src/ \ No newline at end of file diff --git a/script/deploy-eth.s.sol b/script/deploy-eth.s.sol new file mode 100644 index 0000000..376ddfd --- /dev/null +++ b/script/deploy-eth.s.sol @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {UniversalDexModule} from "../src/modules/dex/UniversalDexModule.sol"; +import {AaveV3FlashloanModule} from "../src/modules/aave/AaveV3FlashloanModule.sol"; +import {AaveV3CallbackHandler} from "../src/modules/callback/AaveV3CallbackHandler.sol"; +import {AaveV3EmodeModule} from "../src/modules/aave/AaveV3EmodeModule.sol"; +import {AaveV3SupplyModule} from "../src/modules/aave/AaveV3SupplyModule.sol"; +import {AaveV3WithdrawModule} from "../src/modules/aave/AaveV3WithdrawModule.sol"; +import {AaveV3BorrowModule} from "../src/modules/aave/AaveV3BorrowModule.sol"; +import {AaveV3RepayModule} from "../src/modules/aave/AaveV3RepayModule.sol"; +import {SuperloopModuleRegistry} from "../src/core/ModuleRegistry/ModuleRegistry.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {DataTypes} from "../src/common/DataTypes.sol"; +import {Superloop} from "../src/core/Superloop/Superloop.sol"; +import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol"; +import {DepositManagerCallbackHandler} from "../src/modules/callback/DepositManagerCallbackHandler.sol"; +import {WithdrawManagerCallbackHandler} from "../src/modules/callback/WithdrawManagerCallbackHandler.sol"; +import {DepositManager} from "../src/core/DepositManager/DepositManager.sol"; +import {WithdrawManager} from "../src/core/WithdrawManager/WithdrawManager.sol"; +import {UniversalAccountant} from "../src/core/Accountant/universalAccountant/UniversalAccountant.sol"; +import {AaveV3AccountantPlugin} from "../src/plugins/accountant/AaveV3AccountantPlugin.sol"; +import {AaveV3PreliquidationFallbackHandler} from "../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; +import {VaultRouter} from "../src/helpers/VaultRouter.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {MorphoFlashloanModule} from "../src/modules/morpho/MorphoFlashloanModule.sol"; +import {MorphoCallbackHandler} from "../src/modules/callback/MorphoCallbackHandler.sol"; +import {MerklModule} from "../src/modules/merkl/MerklModule.sol"; +import {IMorphoFlashLoanCallback} from "morpho-blue/interfaces/IMorphoCallbacks.sol"; + +contract Deploy is Script { + address public deployer; + uint256 public deployerPvtKey; + address public vaultAdmin; + address public rebalanceAdmin; + address public treasury; + address public vaultOperator; + + address public asset; + address[] public lendAssets; + address[] public borrowAssets; + string public name; + string public symbol; + uint256 public supplyCap; + uint256 public minimumDepositAmount; + uint256 public instantWithdrawFee; + uint256 public cashReserve; + uint256 public performanceFee; + uint256 public seedAmount; + + address public AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e; + address public constant USDe = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3; + address public constant sUSDe = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497; + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address public constant POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + address public constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address public constant DISTRIBUTOR = 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae; + address public constant VAULT_ADMIN = address(0); // TODO: change to actual address + address public constant TREASURY = address(0); // TODO: change to actual address + uint8 public constant EMODE_CATEGORY = 2; + uint256 public constant USDe_SCALE = 10 ** 18; + uint256 public constant sUSDe_SCALE = 10 ** 18; + uint256 public constant USDC_SCALE = 10 ** 6; + uint256 public constant USDT_SCALE = 10 ** 6; + + SuperloopModuleRegistry public moduleRegistry; + + // aave modules + AaveV3FlashloanModule public flashloanModule; + AaveV3CallbackHandler public aaveFlashloanCallbackHandler; + AaveV3EmodeModule public emodeModule; + AaveV3SupplyModule public supplyModule; + AaveV3WithdrawModule public withdrawModule; + AaveV3BorrowModule public borrowModule; + AaveV3RepayModule public repayModule; + + // morpho module + MorphoFlashloanModule public morphoFlashloanModule; + MorphoCallbackHandler public morphoCallbackHandler; + + // dex module + UniversalDexModule public dexModule; + + // merkl module + MerklModule public merklModule; + + DepositManagerCallbackHandler public depositManagerCallbackHandler; + WithdrawManagerCallbackHandler public withdrawManagerCallbackHandler; + + // TODO: Add later + AaveV3PreliquidationFallbackHandler public preliquidationFallbackHandler; + + address public accountantImplementation; + address public withdrawManagerImplementation; + address public depositManagerImplementation; + address public vaultImplementation; + + address public accountantAaveV3Plugin; + + UniversalAccountant public accountant; + WithdrawManager public withdrawManager; + DepositManager public depositManager; + + Superloop public superloop; + VaultRouter public vaultRouter; + + function setUp() public { + asset = USDe; + name = "Test Superloop USDe"; + symbol = "TestsloopUSDe"; + supplyCap = 100_000 * USDe_SCALE; // in token terms + minimumDepositAmount = 100; // in token terms + instantWithdrawFee = 10; // 0.1% in BPS + cashReserve = 100; // 1% in BPS + performanceFee = 1000; // 10% in bps + lendAssets = new address[](2); + lendAssets[0] = USDe; + lendAssets[1] = sUSDe; + borrowAssets = new address[](2); + borrowAssets[0] = USDC; + borrowAssets[1] = USDT; + + seedAmount = (1 * USDe_SCALE) / 100; // in token terms + + // TODO: Check this config every time before deploying + console.log("--------------------------------"); + console.log("Using configs: "); + console.log("ASSET", asset); + console.log("NAME", name); + console.log("SYMBOL", symbol); + console.log("PERFORMANCE_FEE", performanceFee); + console.log("SUPPLY_CAP", supplyCap); + console.log("MINIMUM_DEPOSIT_AMOUNT", minimumDepositAmount); + console.log("INSTANT_WITHDRAW_FEE", instantWithdrawFee); + console.log("CASH_RESERVE", cashReserve); + console.log("SEED_AMOUNT", seedAmount); + console.log("--------------------------------"); + + vm.createSelectFork("mainnet"); + + deployerPvtKey = vm.envUint("PRIVATE_KEY"); + deployer = vm.addr(deployerPvtKey); + + vaultAdmin = deployer; // TODO: change to actual address + treasury = deployer; // TODO: change to actual address + // xtz vault's operator + vaultOperator = 0x0E9852b16AE49C99B84b0241E3C6F4a5692C6b05; + + console.log("--------------------------------"); + console.log("Depolyer config and roles: "); + console.log("DEPLOYER", deployer); + console.log("VAULT_ADMIN", vaultAdmin); + console.log("TREASURY", treasury); + console.log("VAULT_OPERATOR", vaultOperator); + console.log("--------------------------------"); + } + + function run() public { + vm.startBroadcast(deployerPvtKey); + + // deploy module registry + moduleRegistry = SuperloopModuleRegistry(0x6C520b2f5daDcFdA822AAF7f2b7470b3A686Fe1A); + + // deploy all the modules + deployModules(); + + address[] memory modules = new address[](13); + modules[0] = address(flashloanModule); + modules[1] = address(aaveFlashloanCallbackHandler); + modules[2] = address(emodeModule); + modules[3] = address(supplyModule); + modules[4] = address(withdrawModule); + modules[5] = address(borrowModule); + modules[6] = address(repayModule); + modules[7] = address(dexModule); + modules[8] = address(morphoFlashloanModule); + modules[9] = address(morphoCallbackHandler); + modules[10] = address(merklModule); + modules[11] = address(depositManagerCallbackHandler); + modules[12] = address(withdrawManagerCallbackHandler); + + // deploy vault + DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ + asset: asset, + name: name, + symbol: symbol, + supplyCap: supplyCap, + superloopModuleRegistry: address(moduleRegistry), + modules: modules, + accountant: address(accountant), + withdrawManager: address(withdrawManager), + minimumDepositAmount: minimumDepositAmount, + instantWithdrawFee: instantWithdrawFee, + cashReserve: cashReserve, + depositManager: address(depositManager), + vaultAdmin: vaultAdmin, + treasury: treasury, + vaultOperator: vaultOperator + }); + vaultImplementation = address(new Superloop()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(vaultImplementation), + address(vaultAdmin), + abi.encodeWithSelector(Superloop.initialize.selector, initData) + ); + superloop = Superloop(payable(address(proxy))); + + // deploy accountant + _deployAccountant(address(superloop)); + moduleRegistry.setModule("universalAccountant", address(accountant)); + superloop.setRegisteredModule(address(accountant), true); + superloop.setAccountantModule(address(accountant)); + + // deploy withdraw manager + _deployWithdrawManager(address(superloop)); + moduleRegistry.setModule("withdrawManager", address(withdrawManager)); + superloop.setRegisteredModule(address(withdrawManager), true); + superloop.setWithdrawManagerModule(address(withdrawManager)); + + // deploy deposit manager + _deployDepositManager(address(superloop)); + moduleRegistry.setModule("depositManager", address(depositManager)); + superloop.setRegisteredModule(address(depositManager), true); + superloop.setDepositManagerModule(address(depositManager)); + + // set callback handler + _setupCallbackHandlers(); + + // call emode module and setup emode 3 + _setupEmode(); + + // setup vault router + _setupVaultRouter(); + + // seed the vault + _seedVault(); + + /** + * ROLE TRANSFERS : + * 1. Vault admin => safe + * 2. Vault proxy admin => safe + * 3. Accountant plugin owner => safe + * 4. Accountant => safe + * 5. Accountant proxy admin => safe + * 6. Deposit manager proxy admin => safe + * 7. Withdraw manager proxy admin => safe + * 8. Module registry owner => safe + * 9. Vault operator => rebalance admin + * 10. Vault router owner => vault admin + */ + _logAddresses(); + + vm.stopBroadcast(); + } + + function deployModules() internal { + // deploy morpho flashloan module + morphoFlashloanModule = new MorphoFlashloanModule(MORPHO); + // moduleRegistry.setModule("MorphoFlashloanModule", address(morphoFlashloanModule)); + morphoCallbackHandler = new MorphoCallbackHandler(); + moduleRegistry.setModule("MorphoCallbackHandler", address(morphoCallbackHandler)); + + // deploy aave flashloan module + flashloanModule = new AaveV3FlashloanModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3FlashloanModule", address(flashloanModule)); + + aaveFlashloanCallbackHandler = new AaveV3CallbackHandler(); + moduleRegistry.setModule("AaveV3CallbackHandler", address(aaveFlashloanCallbackHandler)); + + depositManagerCallbackHandler = new DepositManagerCallbackHandler(); + moduleRegistry.setModule("DepositManagerCallbackHandler", address(depositManagerCallbackHandler)); + + withdrawManagerCallbackHandler = new WithdrawManagerCallbackHandler(); + moduleRegistry.setModule("WithdrawManagerCallbackHandler", address(withdrawManagerCallbackHandler)); + + // TODO: add later + // preliquidationFallbackHandler = new AaveV3PreliquidationFallbackHandler( + // AAVE_V3_POOL_ADDRESSES_PROVIDER, + // address(superloop), + // 2, + // 8, + // DataTypes.AaveV3PreliquidationParamsInit({}) + // ); + // moduleRegistry.setModule("AaveV3PreliquidationFallbackHandler", address(preliquidationFallbackHandler)); + + emodeModule = new AaveV3EmodeModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3EmodeModule", address(emodeModule)); + + supplyModule = new AaveV3SupplyModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3SupplyModule", address(supplyModule)); + + withdrawModule = new AaveV3WithdrawModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3WithdrawModule", address(withdrawModule)); + + borrowModule = new AaveV3BorrowModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3BorrowModule", address(borrowModule)); + + repayModule = new AaveV3RepayModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3RepayModule", address(repayModule)); + + dexModule = new UniversalDexModule(); + moduleRegistry.setModule("UniversalDexModule", address(dexModule)); + + // deploy merkl module + merklModule = new MerklModule(DISTRIBUTOR); + moduleRegistry.setModule("MerklModule", address(merklModule)); + } + + function _deployAccountant(address vault) internal { + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, + lendAssets: lendAssets, + borrowAssets: borrowAssets + }); + accountantAaveV3Plugin = address(new AaveV3AccountantPlugin(accountantPluginInitData)); + + address[] memory registeredAccountants = new address[](1); + registeredAccountants[0] = accountantAaveV3Plugin; + + // deploy accountant + DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ + registeredAccountants: registeredAccountants, performanceFee: uint16(performanceFee), vault: address(vault) + }); + + accountantImplementation = address(new UniversalAccountant()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + accountantImplementation, + address(vaultAdmin), + abi.encodeWithSelector(UniversalAccountant.initialize.selector, initData) + ); + + accountant = UniversalAccountant(address(proxy)); + } + + function _deployWithdrawManager(address vault) internal { + withdrawManagerImplementation = address(new WithdrawManager()); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(withdrawManagerImplementation), + address(vaultAdmin), + abi.encodeWithSelector(WithdrawManager.initialize.selector, vault) + ); + + withdrawManager = WithdrawManager(address(proxy)); + } + + function _deployDepositManager(address vault) internal { + depositManagerImplementation = address(new DepositManager()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(depositManagerImplementation), + address(vaultAdmin), + abi.encodeWithSelector(DepositManager.initialize.selector, vault) + ); + depositManager = DepositManager(address(proxy)); + } + + function _setupEmode() internal { + DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: EMODE_CATEGORY}); + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(emodeModule), + data: abi.encodeWithSelector(emodeModule.execute.selector, params) + }); + + superloop.operate(moduleExecutionData); + } + + function _setupCallbackHandlers() internal { + bytes32 key = keccak256(abi.encodePacked(POOL, IFlashLoanSimpleReceiver.executeOperation.selector)); + bytes32 morphoKey = keccak256(abi.encodePacked(MORPHO, IMorphoFlashLoanCallback.onMorphoFlashLoan.selector)); + bytes32 depositKey = + keccak256(abi.encodePacked(address(depositManager), depositManagerCallbackHandler.executeDeposit.selector)); + bytes32 withdrawKey = keccak256( + abi.encodePacked(address(withdrawManager), withdrawManagerCallbackHandler.executeWithdraw.selector) + ); + superloop.setCallbackHandler(morphoKey, address(morphoCallbackHandler)); + superloop.setCallbackHandler(key, address(aaveFlashloanCallbackHandler)); + superloop.setCallbackHandler(depositKey, address(depositManagerCallbackHandler)); + superloop.setCallbackHandler(withdrawKey, address(withdrawManagerCallbackHandler)); + } + + function _seedVault() internal { + IERC20(asset).approve(address(superloop), seedAmount); + superloop.seed(seedAmount); + } + + function _logAddresses() internal view { + // log module registry address + console.log("--------------------------------"); + console.log("Module Registry: %s", address(moduleRegistry)); + console.log("--------------------------------"); + + // log morpho flashloan module + console.log("--------------------------------"); + console.log("Morpho Flashloan Module: %s", address(morphoFlashloanModule)); + console.log("--------------------------------"); + + // log all aave module addresses + console.log("--------------------------------"); + console.log("AaveV3 Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("AaveV3FlashloanModule: %s", address(flashloanModule)); + console.log("--------------------------------"); + console.log("AaveV3EmodeModule: %s", address(emodeModule)); + console.log("--------------------------------"); + console.log("AaveV3SupplyModule: %s", address(supplyModule)); + console.log("--------------------------------"); + console.log("AaveV3WithdrawModule: %s", address(withdrawModule)); + console.log("--------------------------------"); + console.log("AaveV3BorrowModule: %s", address(borrowModule)); + console.log("--------------------------------"); + console.log("AaveV3RepayModule: %s", address(repayModule)); + + // log all the dex modules + console.log("--------------------------------"); + console.log("Dex Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Universal Dex Module: %s", address(dexModule)); + console.log("--------------------------------"); + + // log all the callback handler modules + console.log("--------------------------------"); + console.log("Callback Handler Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Deposit Manager Callback Handler: %s", address(depositManagerCallbackHandler)); + console.log("--------------------------------"); + console.log("Withdraw Manager Callback Handler: %s", address(withdrawManagerCallbackHandler)); + console.log("--------------------------------"); + console.log("AaveV3CallbackHandler: %s", address(aaveFlashloanCallbackHandler)); + console.log("--------------------------------"); + console.log("Morpho Callback Handler: %s", address(morphoCallbackHandler)); + + // log merkl module + console.log("--------------------------------"); + console.log("Merkl Module: %s", address(merklModule)); + console.log("--------------------------------"); + + // log accountant along with accountant plugin + console.log("--------------------------------"); + console.log("Accountant: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Accountant: %s", address(accountant)); + console.log("--------------------------------"); + console.log("Accountant Implementation: %s", address(accountantImplementation)); + console.log("--------------------------------"); + console.log("Accountant Plugin: %s", address(accountantAaveV3Plugin)); + console.log("--------------------------------"); + + // log withdraw manager along with withdraw manager implementation + console.log("--------------------------------"); + console.log("Withdraw Manager: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Withdraw Manager: %s", address(withdrawManager)); + console.log("--------------------------------"); + console.log("Withdraw Manager Implementation: %s", address(withdrawManagerImplementation)); + console.log("--------------------------------"); + + // log deposit manager along with deposit manager implementation + console.log("--------------------------------"); + console.log("Deposit Manager: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Deposit Manager: %s", address(depositManager)); + console.log("--------------------------------"); + console.log("Deposit Manager Implementation: %s", address(depositManagerImplementation)); + console.log("--------------------------------"); + + // log superloop along with implementation and proxy admin + console.log("--------------------------------"); + console.log("Superloop: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Superloop: %s", address(superloop)); + console.log("--------------------------------"); + console.log("Superloop Implementation: %s", address(vaultImplementation)); + console.log("--------------------------------"); + + console.log("--------------------------------"); + console.log("Vault Router: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Vault Router: %s", address(vaultRouter)); + } + + function _setupVaultRouter() internal { + address[] memory supportedVaults = new address[](1); + supportedVaults[0] = address(superloop); + + address[] memory supportedDepositManagers = new address[](1); + supportedDepositManagers[0] = address(depositManager); + + address[] memory supportedTokens = new address[](3); + supportedTokens[0] = USDe; + supportedTokens[1] = USDC; + supportedTokens[2] = USDT; + + vaultRouter = new VaultRouter(supportedVaults, supportedTokens, address(dexModule), supportedDepositManagers); + + vaultRouter.transferOwnership(vaultAdmin); + } +} diff --git a/script/deploy-migration.s.sol b/script/deploy-migration.s.sol new file mode 100644 index 0000000..a2021b9 --- /dev/null +++ b/script/deploy-migration.s.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {MigrationHelper} from "../src/helpers/MigrationHelper.sol"; + +contract DeployMigration is Script { + address public deployer; + uint256 public deployerPvtKey; + + address public AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC; + address public REPAY_MODULE = 0x9AF8cCabC21ff594dA237f9694C4A9C6123480c6; + address public WITHDRAW_MODULE = 0x1f5Ba080B9E5705DA47212167cA44611F78DB130; + address public DEPOSIT_MODULE = 0x66e82124412C61D7fF90ACFBa82936DD037D737E; + address public BORROW_MODULE = 0x3de57294989d12066a94a8A16E977992F3cF8433; + address public DEX_MODULE = 0x38F5efC1267F6103c9b0FE802E1731E245f09Cd0; + address public constant USDC = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; + + function setUp() public { + vm.createSelectFork("etherlink"); + deployerPvtKey = vm.envUint("PRIVATE_KEY"); + deployer = vm.addr(deployerPvtKey); + } + + function run() public returns (address) { + vm.startBroadcast(deployerPvtKey); + + MigrationHelper migrationHelper = new MigrationHelper( + AAVE_V3_POOL_ADDRESSES_PROVIDER, + REPAY_MODULE, + WITHDRAW_MODULE, + DEPOSIT_MODULE, + BORROW_MODULE, + DEX_MODULE, + USDC + ); + + vm.stopBroadcast(); + + return address(migrationHelper); + } +} diff --git a/script/deploy-v2-for-migration.s.sol b/script/deploy-v2-for-migration.s.sol new file mode 100644 index 0000000..f1f3b06 --- /dev/null +++ b/script/deploy-v2-for-migration.s.sol @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {UniversalDexModule} from "../src/modules/dex/UniversalDexModule.sol"; +import {AaveV3FlashloanModule} from "../src/modules/aave/AaveV3FlashloanModule.sol"; +import {AaveV3CallbackHandler} from "../src/modules/callback/AaveV3CallbackHandler.sol"; +import {AaveV3EmodeModule} from "../src/modules/aave/AaveV3EmodeModule.sol"; +import {AaveV3SupplyModule} from "../src/modules/aave/AaveV3SupplyModule.sol"; +import {AaveV3WithdrawModule} from "../src/modules/aave/AaveV3WithdrawModule.sol"; +import {AaveV3BorrowModule} from "../src/modules/aave/AaveV3BorrowModule.sol"; +import {AaveV3RepayModule} from "../src/modules/aave/AaveV3RepayModule.sol"; +import {SuperloopModuleRegistry} from "../src/core/ModuleRegistry/ModuleRegistry.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {DataTypes} from "../src/common/DataTypes.sol"; +import {Superloop} from "../src/core/Superloop/Superloop.sol"; +import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol"; +import {DepositManagerCallbackHandler} from "../src/modules/callback/DepositManagerCallbackHandler.sol"; +import {WithdrawManagerCallbackHandler} from "../src/modules/callback/WithdrawManagerCallbackHandler.sol"; +import {DepositManager} from "../src/core/DepositManager/DepositManager.sol"; +import {WithdrawManager} from "../src/core/WithdrawManager/WithdrawManager.sol"; +import {UniversalAccountant} from "../src/core/Accountant/universalAccountant/UniversalAccountant.sol"; +import {AaveV3AccountantPlugin} from "../src/plugins/accountant/AaveV3AccountantPlugin.sol"; +import {AaveV3PreliquidationFallbackHandler} from "../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; +import {VaultRouter} from "../src/helpers/VaultRouter.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract Deploy is Script { + address public deployer; + uint256 public deployerPvtKey; + address public vaultAdmin; + address public rebalanceAdmin; + address public treasury; + address public vaultOperator; + + address public asset; + address public lendAsset; + address public borrowAsset; + string public name; + string public symbol; + uint256 public supplyCap; + uint256 public minimumDepositAmount; + uint256 public instantWithdrawFee; + uint256 public cashReserve; + uint256 public performanceFee; + uint256 public seedAmount; + uint8 public emodeCategory; + + address public AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC; + address public constant ST_XTZ = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; + address public constant XTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; + address public constant LBTC = 0xecAc9C5F704e954931349Da37F60E39f515c11c1; + address public constant WBTC = 0xbFc94CD2B1E55999Cfc7347a9313e88702B83d0F; + address public constant POOL = 0x3bD16D195786fb2F509f2E2D7F69920262EF114D; + address public constant VAULT_ADMIN = 0x81b833Df09A7ce39C00ecE916EC54166d2a6B193; + address public constant TREASURY = 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f; + uint256 public constant XTZ_SCALE = 10 ** 18; + uint256 public constant BTC_SCALE = 10 ** 8; + + SuperloopModuleRegistry public moduleRegistry; + + // aave modules + AaveV3FlashloanModule public flashloanModule; + AaveV3CallbackHandler public aaveFlashloanCallbackHandler; + AaveV3EmodeModule public emodeModule; + AaveV3SupplyModule public supplyModule; + AaveV3WithdrawModule public withdrawModule; + AaveV3BorrowModule public borrowModule; + AaveV3RepayModule public repayModule; + + // dex module + UniversalDexModule public dexModule; + + DepositManagerCallbackHandler public depositManagerCallbackHandler; + WithdrawManagerCallbackHandler public withdrawManagerCallbackHandler; + + // TODO: Add later + AaveV3PreliquidationFallbackHandler public preliquidationFallbackHandler; + + address public accountantImplementation; + address public withdrawManagerImplementation; + address public depositManagerImplementation; + address public vaultImplementation; + + address public accountantAaveV3Plugin; + + UniversalAccountant public accountant; + WithdrawManager public withdrawManager; + DepositManager public depositManager; + + Superloop public superloop; + VaultRouter public vaultRouter; + + function setUp() public { + asset = XTZ; // WBTC + name = "Superloop XTZ"; // "Superloop WBTC" + symbol = "sloopXTZ"; // "sloopWBTC" + supplyCap = 760_000 * XTZ_SCALE; // 12.5 * BTC_SCALE + minimumDepositAmount = 100; // in token terms + instantWithdrawFee = 10; // 0.1% in BPS + cashReserve = 100; // 1% in BPS + performanceFee = 1500; // 10% in bps + lendAsset = ST_XTZ; // LBTC + borrowAsset = XTZ; // WBTC + seedAmount = 0; + emodeCategory = 3; // 2; + + // TODO: Check this config every time before deploying + console.log("--------------------------------"); + console.log("Using configs: "); + console.log("ASSET", asset); + console.log("NAME", name); + console.log("SYMBOL", symbol); + console.log("PERFORMANCE_FEE", performanceFee); + console.log("SUPPLY_CAP", supplyCap); + console.log("MINIMUM_DEPOSIT_AMOUNT", minimumDepositAmount); + console.log("INSTANT_WITHDRAW_FEE", instantWithdrawFee); + console.log("CASH_RESERVE", cashReserve); + console.log("SEED_AMOUNT", seedAmount); + console.log("--------------------------------"); + + vm.createSelectFork("etherlink"); + + deployerPvtKey = vm.envUint("PRIVATE_KEY"); + deployer = vm.addr(deployerPvtKey); + + vaultAdmin = deployer; + treasury = TREASURY; + vaultOperator = deployer; + + console.log("--------------------------------"); + console.log("Depolyer config and roles: "); + console.log("DEPLOYER", deployer); + console.log("VAULT_ADMIN", vaultAdmin); + console.log("TREASURY", treasury); + console.log("VAULT_OPERATOR", vaultOperator); + console.log("--------------------------------"); + } + + function run() public { + vm.startBroadcast(deployerPvtKey); + + // deploy module registry + moduleRegistry = new SuperloopModuleRegistry(); + + // deploy all the modules + deployModules(); + + address[] memory modules = new address[](10); + modules[0] = address(flashloanModule); + modules[1] = address(aaveFlashloanCallbackHandler); + modules[2] = address(emodeModule); + modules[3] = address(supplyModule); + modules[4] = address(withdrawModule); + modules[5] = address(borrowModule); + modules[6] = address(repayModule); + modules[7] = address(dexModule); + modules[8] = address(depositManagerCallbackHandler); + modules[9] = address(withdrawManagerCallbackHandler); + + // deploy vault + DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ + asset: asset, + name: name, + symbol: symbol, + supplyCap: supplyCap, + superloopModuleRegistry: address(moduleRegistry), + modules: modules, + accountant: address(accountant), + withdrawManager: address(withdrawManager), + minimumDepositAmount: minimumDepositAmount, + instantWithdrawFee: instantWithdrawFee, + cashReserve: cashReserve, + depositManager: address(depositManager), + vaultAdmin: vaultAdmin, + treasury: treasury, + vaultOperator: vaultOperator + }); + vaultImplementation = address(new Superloop()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(vaultImplementation), + address(vaultAdmin), + abi.encodeWithSelector(Superloop.initialize.selector, initData) + ); + superloop = Superloop(payable(address(proxy))); + + // deploy accountant + _deployAccountant(address(superloop)); + moduleRegistry.setModule("universalAccountant", address(accountant)); + superloop.setRegisteredModule(address(accountant), true); + superloop.setAccountantModule(address(accountant)); + + // deploy withdraw manager + _deployWithdrawManager(address(superloop)); + moduleRegistry.setModule("withdrawManager", address(withdrawManager)); + superloop.setRegisteredModule(address(withdrawManager), true); + superloop.setWithdrawManagerModule(address(withdrawManager)); + + // deploy deposit manager + _deployDepositManager(address(superloop)); + moduleRegistry.setModule("depositManager", address(depositManager)); + superloop.setRegisteredModule(address(depositManager), true); + superloop.setDepositManagerModule(address(depositManager)); + + // set callback handler + _setupCallbackHandlers(); + + // call emode module and setup emode + _setupEmode(); + + // setup vault router + _setupVaultRouter(); + + /** + * ROLE TRANSFERS : + * 1. Vault admin => safe + * 2. Vault proxy admin => safe + * 3. Accountant plugin owner => safe + * 4. Accountant => safe + * 5. Accountant proxy admin => safe + * 6. Deposit manager proxy admin => safe + * 7. Withdraw manager proxy admin => safe + * 8. Module registry owner => safe + * 9. Vault operator => rebalance admin + * 10. Vault router owner => vault admin + */ + _logAddresses(); + + vm.stopBroadcast(); + } + + function deployModules() internal { + flashloanModule = new AaveV3FlashloanModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3FlashloanModule", address(flashloanModule)); + + aaveFlashloanCallbackHandler = new AaveV3CallbackHandler(); + moduleRegistry.setModule("AaveV3CallbackHandler", address(aaveFlashloanCallbackHandler)); + + depositManagerCallbackHandler = new DepositManagerCallbackHandler(); + moduleRegistry.setModule("DepositManagerCallbackHandler", address(depositManagerCallbackHandler)); + + withdrawManagerCallbackHandler = new WithdrawManagerCallbackHandler(); + moduleRegistry.setModule("WithdrawManagerCallbackHandler", address(withdrawManagerCallbackHandler)); + + // TODO: add later + // preliquidationFallbackHandler = new AaveV3PreliquidationFallbackHandler( + // AAVE_V3_POOL_ADDRESSES_PROVIDER, + // address(superloop), + // 2, + // 8, + // DataTypes.AaveV3PreliquidationParamsInit({}) + // ); + // moduleRegistry.setModule("AaveV3PreliquidationFallbackHandler", address(preliquidationFallbackHandler)); + + emodeModule = new AaveV3EmodeModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3EmodeModule", address(emodeModule)); + + supplyModule = new AaveV3SupplyModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3SupplyModule", address(supplyModule)); + + withdrawModule = new AaveV3WithdrawModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3WithdrawModule", address(withdrawModule)); + + borrowModule = new AaveV3BorrowModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3BorrowModule", address(borrowModule)); + + repayModule = new AaveV3RepayModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + moduleRegistry.setModule("AaveV3RepayModule", address(repayModule)); + + dexModule = UniversalDexModule(0x38F5efC1267F6103c9b0FE802E1731E245f09Cd0); + // moduleRegistry.setModule("UniversalDexModule", address(dexModule)); + } + + function _deployAccountant(address vault) internal { + address[] memory lendAssets = new address[](1); + lendAssets[0] = lendAsset; + address[] memory borrowAssets = new address[](1); + borrowAssets[0] = borrowAsset; + + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, + lendAssets: lendAssets, + borrowAssets: borrowAssets + }); + accountantAaveV3Plugin = address(new AaveV3AccountantPlugin(accountantPluginInitData)); + + address[] memory registeredAccountants = new address[](1); + registeredAccountants[0] = accountantAaveV3Plugin; + + // deploy accountant + DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ + registeredAccountants: registeredAccountants, performanceFee: uint16(performanceFee), vault: address(vault) + }); + + accountantImplementation = address(new UniversalAccountant()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + accountantImplementation, + address(vaultAdmin), + abi.encodeWithSelector(UniversalAccountant.initialize.selector, initData) + ); + + accountant = UniversalAccountant(address(proxy)); + } + + function _deployWithdrawManager(address vault) internal { + withdrawManagerImplementation = address(new WithdrawManager()); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(withdrawManagerImplementation), + address(vaultAdmin), + abi.encodeWithSelector(WithdrawManager.initialize.selector, vault) + ); + + withdrawManager = WithdrawManager(address(proxy)); + } + + function _deployDepositManager(address vault) internal { + depositManagerImplementation = address(new DepositManager()); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(depositManagerImplementation), + address(vaultAdmin), + abi.encodeWithSelector(DepositManager.initialize.selector, vault) + ); + depositManager = DepositManager(address(proxy)); + } + + function _setupEmode() internal { + DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: emodeCategory}); + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(emodeModule), + data: abi.encodeWithSelector(emodeModule.execute.selector, params) + }); + + superloop.operate(moduleExecutionData); + } + + function _setupCallbackHandlers() internal { + bytes32 key = keccak256(abi.encodePacked(POOL, IFlashLoanSimpleReceiver.executeOperation.selector)); + bytes32 depositKey = + keccak256(abi.encodePacked(address(depositManager), depositManagerCallbackHandler.executeDeposit.selector)); + bytes32 withdrawKey = keccak256( + abi.encodePacked(address(withdrawManager), withdrawManagerCallbackHandler.executeWithdraw.selector) + ); + superloop.setCallbackHandler(key, address(aaveFlashloanCallbackHandler)); + superloop.setCallbackHandler(depositKey, address(depositManagerCallbackHandler)); + superloop.setCallbackHandler(withdrawKey, address(withdrawManagerCallbackHandler)); + } + + function _seedVault() internal { + IERC20(asset).approve(address(superloop), seedAmount); + superloop.seed(seedAmount); + } + + function _logAddresses() internal view { + // log module registry address + console.log("--------------------------------"); + console.log("Module Registry: %s", address(moduleRegistry)); + console.log("--------------------------------"); + + // log all aave module addresses + console.log("--------------------------------"); + console.log("AaveV3 Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("AaveV3FlashloanModule: %s", address(flashloanModule)); + console.log("--------------------------------"); + console.log("AaveV3EmodeModule: %s", address(emodeModule)); + console.log("--------------------------------"); + console.log("AaveV3SupplyModule: %s", address(supplyModule)); + console.log("--------------------------------"); + console.log("AaveV3WithdrawModule: %s", address(withdrawModule)); + console.log("--------------------------------"); + console.log("AaveV3BorrowModule: %s", address(borrowModule)); + console.log("--------------------------------"); + console.log("AaveV3RepayModule: %s", address(repayModule)); + + // log all the dex modules + console.log("--------------------------------"); + console.log("Dex Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Universal Dex Module: %s", address(dexModule)); + console.log("--------------------------------"); + + // log all the callback handler modules + console.log("--------------------------------"); + console.log("Callback Handler Modules: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Deposit Manager Callback Handler: %s", address(depositManagerCallbackHandler)); + console.log("--------------------------------"); + console.log("Withdraw Manager Callback Handler: %s", address(withdrawManagerCallbackHandler)); + console.log("--------------------------------"); + console.log("AaveV3CallbackHandler: %s", address(aaveFlashloanCallbackHandler)); + console.log("--------------------------------"); + + // log accountant along with accountant plugin + console.log("--------------------------------"); + console.log("Accountant: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Accountant: %s", address(accountant)); + console.log("--------------------------------"); + console.log("Accountant Implementation: %s", address(accountantImplementation)); + console.log("--------------------------------"); + console.log("Accountant Plugin: %s", address(accountantAaveV3Plugin)); + console.log("--------------------------------"); + + // log withdraw manager along with withdraw manager implementation + console.log("--------------------------------"); + console.log("Withdraw Manager: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Withdraw Manager: %s", address(withdrawManager)); + console.log("--------------------------------"); + console.log("Withdraw Manager Implementation: %s", address(withdrawManagerImplementation)); + console.log("--------------------------------"); + + // log deposit manager along with deposit manager implementation + console.log("--------------------------------"); + console.log("Deposit Manager: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Deposit Manager: %s", address(depositManager)); + console.log("--------------------------------"); + console.log("Deposit Manager Implementation: %s", address(depositManagerImplementation)); + console.log("--------------------------------"); + + // log superloop along with implementation and proxy admin + console.log("--------------------------------"); + console.log("Superloop: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Superloop: %s", address(superloop)); + console.log("--------------------------------"); + console.log("Superloop Implementation: %s", address(vaultImplementation)); + console.log("--------------------------------"); + + console.log("--------------------------------"); + console.log("Vault Router: %s", "--------------------------------"); + console.log("--------------------------------"); + console.log("Vault Router: %s", address(vaultRouter)); + } + + function _setupVaultRouter() internal { + address[] memory supportedVaults = new address[](1); + supportedVaults[0] = address(superloop); + + address[] memory supportedDepositManagers = new address[](1); + supportedDepositManagers[0] = address(depositManager); + + address[] memory supportedTokens = new address[](9); + supportedTokens[0] = XTZ; + supportedTokens[1] = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; // USDC + supportedTokens[2] = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; // ST_XTZ + supportedTokens[3] = 0x2C03058C8AFC06713be23e58D2febC8337dbfE6A; // USDT + supportedTokens[4] = 0xDD629E5241CbC5919847783e6C96B2De4754e438; // mtbill + supportedTokens[5] = 0x2247B5A46BB79421a314aB0f0b67fFd11dd37Ee4; // mbasis + supportedTokens[6] = 0xbFc94CD2B1E55999Cfc7347a9313e88702B83d0F; // wbtc + supportedTokens[7] = 0xfc24f770F94edBca6D6f885E12d4317320BcB401; // weth + supportedTokens[8] = 0xecAc9C5F704e954931349Da37F60E39f515c11c1; // lbtc + + vaultRouter = new VaultRouter(supportedVaults, supportedTokens, address(dexModule), supportedDepositManagers); + + vaultRouter.transferOwnership(vaultAdmin); + } +} diff --git a/script/deploy-v2.s.sol b/script/deploy-v2.s.sol index 2bf2381..60c3a9f 100644 --- a/script/deploy-v2.s.sol +++ b/script/deploy-v2.s.sol @@ -13,8 +13,9 @@ import {AaveV3WithdrawModule} from "../src/modules/aave/AaveV3WithdrawModule.sol import {AaveV3BorrowModule} from "../src/modules/aave/AaveV3BorrowModule.sol"; import {AaveV3RepayModule} from "../src/modules/aave/AaveV3RepayModule.sol"; import {SuperloopModuleRegistry} from "../src/core/ModuleRegistry/ModuleRegistry.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {DataTypes} from "../src/common/DataTypes.sol"; import {Superloop} from "../src/core/Superloop/Superloop.sol"; import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol"; @@ -283,12 +284,12 @@ contract Deploy is Script { address[] memory borrowAssets = new address[](1); borrowAssets[0] = borrowAsset; - DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = DataTypes - .AaveV3AccountantPluginModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets - }); + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, + lendAssets: lendAssets, + borrowAssets: borrowAssets + }); accountantAaveV3Plugin = address(new AaveV3AccountantPlugin(accountantPluginInitData)); address[] memory registeredAccountants = new address[](1); @@ -296,9 +297,7 @@ contract Deploy is Script { // deploy accountant DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ - registeredAccountants: registeredAccountants, - performanceFee: uint16(performanceFee), - vault: address(vault) + registeredAccountants: registeredAccountants, performanceFee: uint16(performanceFee), vault: address(vault) }); accountantImplementation = address(new UniversalAccountant()); diff --git a/script/migration.s.sol b/script/migration-btc-vault.s.sol similarity index 61% rename from script/migration.s.sol rename to script/migration-btc-vault.s.sol index 7e42f3d..4866a97 100644 --- a/script/migration.s.sol +++ b/script/migration-btc-vault.s.sol @@ -12,50 +12,86 @@ import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; import {console2} from "forge-std/console2.sol"; import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; +// TODO: update address(0) to actual addresses contract Migration is Script { - uint256 public adminPvtKey; - address public admin; + uint256 public migratorPvtKey; + address public migrator; address public oldVault; address public newVault; MigrationHelper public migrationHelper; IPoolDataProvider public poolDataProvider = IPoolDataProvider(0x99e8269dDD5c7Af0F1B3973A591b47E8E001BCac); address public AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC; - address REPAY_MODULE = 0xB8A8415B20b119aDEaff3Ed078980B0585190E5B; - address WITHDRAW_MODULE = 0x545D45227Ec1dE0dae88C5B576A234931fa2e428; - address DEPOSIT_MODULE = 0x193EcEA94c424dF41d466b6bcA597ADb5B788999; - address BORROW_MODULE = 0x23fd2A315e6F30552278849AF467e978EF76c5C4; - address DEX_MODULE = 0x2871677D649019A4e901C8b0f5a3B6Fa88900a91; + address public REPAY_MODULE = 0x9AF8cCabC21ff594dA237f9694C4A9C6123480c6; + address public WITHDRAW_MODULE = 0x1f5Ba080B9E5705DA47212167cA44611F78DB130; + address public DEPOSIT_MODULE = 0x66e82124412C61D7fF90ACFBa82936DD037D737E; + address public BORROW_MODULE = 0x3de57294989d12066a94a8A16E977992F3cF8433; + address public DEX_MODULE = 0x38F5efC1267F6103c9b0FE802E1731E245f09Cd0; address public constant USDC = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; - address public constant ST_XTZ = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; - address public constant XTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; address public constant LBTC = 0xecAc9C5F704e954931349Da37F60E39f515c11c1; address public constant WBTC = 0xbFc94CD2B1E55999Cfc7347a9313e88702B83d0F; - address newVaultDepositManager = 0xC2d2325DF20bB36E5859068dEBBbeefd239D6192; - uint256 maxSharesDelta = 100; + address newVaultDepositManager = address(0); + uint256 maxSharesDelta = 0; uint256 batches = 1; - uint256 maxPercentageChangeInExchangeRate = 5; // 0.05% ? + uint256 maxPercentageChangeInExchangeRate = 5; // 0.05% - // TODO: Add read time users to the list + // TODO: 42 holders, check this before running address[] public ALL_USERS = [ - 0x5c53414E1f15D7668c2b9EC0A92482A64845f5f6, - 0x52f86e43dAbdDee24A35227598bF4569FE59034D, + 0x9D5132Dd45CdaCd1De3a7b7f1f13da7F025fF726, + 0xa7CBb758849979CEc43FD146fe01EB2BF560202C, + 0x6bA9c7d86eBD6A20817d482C42C9dE2806EeFB7e, + 0x9D0214C3dB28875fe2b9eb9cD9d4F71E7817890A, + 0x2B38f0De645E05Ec410AE86Ef479a1e42E45Bd12, + 0x8fb2FB82fF44da85E43d0A71CA992DbBC3243c5a, + 0xced306BfCB4a057aE3905063fD601774F9F18730, + 0x0C1856Cb7444cc8596c706185b54535F45C2623B, + 0x11fC7853944570C1F9D9EBE7Ac24e2FeFddf0314, + 0xb3d1aCA9bF004d0da560930Cf9277c604eDf2283, + 0xB438Dbaae78225eEfcDBB04E207A8eFB06036a2c, + 0x997b96BAD648c39226281Bc002f9857274E42A01, + 0xd52929B69680A6f74D2eB9c8F1ef482f37b1b32B, + 0x706FC1a8e457De0cf52e7679C2922aEF7F7a397e, + 0x3497818F50f79A11236B941dDAd35Da68A57864E, + 0xB35722e595F2B974c2B1D14A80Fab5d0c60c2fa3, + 0xA9f7b86FCC86EA1b913dDcaB8c9514a8e677666E, + 0x7077e395C9FA4E480366A5DC5792d2504d78dffF, + 0x664324F8B7430b4C22Dc036234Dd752A4755Fda7, + 0xD22ADba687aAd7DD9CEC3b18D970C581734783bA, + 0x5BC81274740A73D33ec8539182c326b8b58004C2, + 0x02F73B8e29dD6Ec38Bc5D8B3826051DC562c3060, + 0x4e90Da1dDCD5B0Fb295b6A69251b5D04D5bCCA7a, + 0x150a0D516C0ceFa39f980Da57d5422E91A94238b, + 0x4fb30f8CcE1F80FC9CC45F7F626069be7549aF59, + 0xF382ce5457Bba6113e082DD638b6671Cb2277B1f, + 0xb95f08B2eda7B23Ce412A6dE82eA5f5100335A3A, + 0x5b6Da8BC8696Aa4D20030151345ec652cf1eC727, + 0x0396816A361e2353a91Fde8438600a9353b34ce7, + 0xA1ebf2043e76446eFA2724bD1Ec18321776096FF, + 0x9214835f82752E0574F3828A4400C36Ba386Df82, + 0xb0DebcB643eE79f19Ef659Bd01D0FAC12d058604, + 0x3d72971b117EE8EF445D138557151f66CB0D408F, + 0x09e63186b4F94FafcCC60a42D3f77b4A10995674, + 0xc210a4D56D99E095b126664fDCE545b369ED3FD0, + 0xEC23a0C9D4107cAD33271c4F1955a21f4c041208, 0x03adFaA573aC1a9b19D2b8F79a5aAFFb9c2A0532, - 0xF5AC5943D16fC865824910033B756519DC396682 + 0x6a58EBB35664A171A1B070DE48ee93278A63c168, + 0x521e6f07bDfa2fF8D072887F0ef3bB908a3D9e2a, + 0xF5AC5943D16fC865824910033B756519DC396682, + 0xBD73cF5baf12F120Ee3f6C4ad82df9a12649e578, + 0x44119A62EA645242234cAF408c4c20513E660EBb ]; function setUp() public { vm.createSelectFork("etherlink"); - adminPvtKey = vm.envUint("PRIVATE_KEY"); - admin = vm.addr(adminPvtKey); + migratorPvtKey = vm.envUint("PRIVATE_KEY"); + migrator = vm.addr(migratorPvtKey); - // TODO: Add old vault address - oldVault = 0x9a3f9C4d3B5A40fc064f4f9dB11dE617CDCBD3eF; - newVault = 0x93fcCf4b8dDE650c98f6F1c18831B5c8D2966210; + oldVault = 0xC557529dd252e5a02E6C653b0B88984aFa3c8199; + newVault = address(0); - migrationHelper = _deployMigrationHelper(); + migrationHelper = MigrationHelper(address(0)); vm.label(oldVault, "oldVault"); vm.label(newVault, "newVault"); @@ -63,7 +99,7 @@ contract Migration is Script { } function run() public { - vm.startBroadcast(adminPvtKey); + vm.startBroadcast(migratorPvtKey); console.log("OLD VAULT STATE BEFORE MIGRATION"); _logVaultState(oldVault); @@ -111,11 +147,7 @@ contract Migration is Script { } function _preMigrationSetup() internal { - ISuperloop(oldVault).setVaultAdmin(address(migrationHelper)); - // ISuperloop(oldVault).setPrivilegedAddress( - // address(migrationHelper), - // true - // ); + ISuperloop(oldVault).setPrivilegedAddress(address(migrationHelper), true); ISuperloop(newVault).setRegisteredModule(REPAY_MODULE, true); ISuperloop(newVault).setRegisteredModule(WITHDRAW_MODULE, true); @@ -126,11 +158,12 @@ contract Migration is Script { ISuperloop(newVault).setDepositManagerModule(address(migrationHelper)); ISuperloop(newVault).setVaultOperator(address(migrationHelper)); IAccountantModule(ISuperloop(newVault).accountant()).setVault(address(migrationHelper)); - Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))).transferOwnership( - address(migrationHelper) - ); + Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))) + .transferOwnership(address(migrationHelper)); // assert these roles + address withdrawManager = ISuperloopLegacy(oldVault).withdrawManagerModule(); + assert(IERC20(WBTC).balanceOf(withdrawManager) == 0 && IERC20(oldVault).balanceOf(withdrawManager) == 0); assert(ISuperloop(newVault).depositManagerModule() == address(migrationHelper)); assert(ISuperloop(newVault).vaultOperator() == address(migrationHelper)); assert(IAccountantModule(ISuperloop(newVault).accountant()).vault() == address(migrationHelper)); @@ -141,7 +174,7 @@ contract Migration is Script { function _performMigration() internal { migrationHelper.migrate( - oldVault, newVault, ALL_USERS, ST_XTZ, XTZ, batches, maxSharesDelta, maxPercentageChangeInExchangeRate + oldVault, newVault, ALL_USERS, LBTC, WBTC, batches, maxSharesDelta, maxPercentageChangeInExchangeRate ); for (uint256 i = 0; i < ALL_USERS.length; i++) { @@ -154,16 +187,14 @@ contract Migration is Script { IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate(); assert(newVaultLastRealizedFeeExchangeRate == oldVaultLastRealizedFeeExchangeRate); - assert(Ownable(address(accountant)).owner() == address(admin)); + assert(Ownable(address(accountant)).owner() == address(migrator)); assert(accountant.vault() == address(newVault)); } function _postMigrationSetup() internal { - ISuperloop(newVault).setVaultOperator(admin); + ISuperloop(newVault).setVaultOperator(migrator); ISuperloop(newVault).setDepositManagerModule(newVaultDepositManager); - // ISuperloop(oldVault).setPrivilegedAddress(address(migrationHelper), false); - ISuperloop(newVault).setRegisteredModule(REPAY_MODULE, false); ISuperloop(newVault).setRegisteredModule(WITHDRAW_MODULE, false); ISuperloop(newVault).setRegisteredModule(DEPOSIT_MODULE, false); @@ -171,17 +202,17 @@ contract Migration is Script { ISuperloop(newVault).setRegisteredModule(DEX_MODULE, false); // assert these roles - assert(ISuperloop(newVault).vaultOperator() == admin); + assert(ISuperloop(newVault).vaultOperator() == migrator); assert(ISuperloop(newVault).depositManagerModule() == newVaultDepositManager); - assert(Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))).owner() == address(admin)); + assert(Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))).owner() == address(migrator)); assert(IAccountantModule(ISuperloop(newVault).accountant()).vault() == newVault); } function _logVaultState(address vault) internal view { - uint256 vaultXTZBalance = IERC20(XTZ).balanceOf(vault); - uint256 vaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(vault); - (uint256 vaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, vault); - (,, uint256 vaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, vault); + uint256 vaultXTZBalance = IERC20(WBTC).balanceOf(vault); + uint256 vaultSTXTZBalance = IERC20(LBTC).balanceOf(vault); + (uint256 vaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(LBTC, vault); + (,, uint256 vaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(WBTC, vault); console.log("vaultXTZBalance", vaultXTZBalance); console.log("vaultSTXTZBalance", vaultSTXTZBalance); diff --git a/script/migration-xtz-vault.s.sol b/script/migration-xtz-vault.s.sol new file mode 100644 index 0000000..3bfa39f --- /dev/null +++ b/script/migration-xtz-vault.s.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {MigrationHelper} from "../src/helpers/MigrationHelper.sol"; +import {ISuperloop} from "../src/interfaces/ISuperloop.sol"; +import {IAccountantModule} from "../src/interfaces/IAccountantModule.sol"; +import {console} from "forge-std/console.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {ISuperloopLegacy} from "../src/helpers/ISuperloopLegacy.sol"; +import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; +import {console2} from "forge-std/console2.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; + +// TODO: update address(0) to actual addresses +contract Migration is Script { + uint256 public migratorPvtKey; + address public migrator; + address public oldVault; + address public newVault; + MigrationHelper public migrationHelper; + + IPoolDataProvider public poolDataProvider = IPoolDataProvider(0x99e8269dDD5c7Af0F1B3973A591b47E8E001BCac); + address public AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC; + address REPAY_MODULE = 0xB8A8415B20b119aDEaff3Ed078980B0585190E5B; + address WITHDRAW_MODULE = 0x545D45227Ec1dE0dae88C5B576A234931fa2e428; + address DEPOSIT_MODULE = 0x193EcEA94c424dF41d466b6bcA597ADb5B788999; + address BORROW_MODULE = 0x23fd2A315e6F30552278849AF467e978EF76c5C4; + address DEX_MODULE = 0x2871677D649019A4e901C8b0f5a3B6Fa88900a91; + address public constant USDC = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; + + address public constant ST_XTZ = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; + address public constant XTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; + + address newVaultDepositManager = address(0); + uint256 maxSharesDelta = 0; + uint256 batches = 4; + uint256 maxPercentageChangeInExchangeRate = 5; // 0.05% + + // TODO: 119 holders, check this before running + address[] public ALL_USERS = [ + 0x2B38f0De645E05Ec410AE86Ef479a1e42E45Bd12, + 0xb3d1aCA9bF004d0da560930Cf9277c604eDf2283, + 0x11fC7853944570C1F9D9EBE7Ac24e2FeFddf0314, + 0xced306BfCB4a057aE3905063fD601774F9F18730, + 0x8fb2FB82fF44da85E43d0A71CA992DbBC3243c5a, + 0x5d8809340760b1bB54642BE91Bb5A2871C0d7a10, + 0x40F832B71D2C525A9aa4b4908Ec511Ed93c8a308, + 0xde5017f32C3AB32dd0286891A11F5d5D54C13728, + 0x4f2421553E571627C6801521316732693016d9cF, + 0x6EA314366871459Dbc3e1A2Eb422e23B538c7ADC, + 0x953D1668BC03e0EE9145A7c4F79956b73db90B67, + 0x46C9CCC6857E33a8209706BB5043700b4608Dda8, + 0x67Efd0FfD493Fa5C55f2a19033557557e3A5197C, + 0xbE00FA424243C56c7CF5792aFBd6b8Dc4d6CBE4E, + 0xEC23a0C9D4107cAD33271c4F1955a21f4c041208, + 0x3d72971b117EE8EF445D138557151f66CB0D408F, + 0xc2E97Ae6Ca9aAFB19CE0B8bCd1F4C50285db2377, + 0x5EdF9DeC5b003AF80B65e129Ab17DBE64106a8e3, + 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f, + 0x664324F8B7430b4C22Dc036234Dd752A4755Fda7, + 0x07ABc6225724a3f29e09173f585AacDE7A701dB3, + 0xB35722e595F2B974c2B1D14A80Fab5d0c60c2fa3, + 0x37b14bF02B1D01E196E3439d1689fEf845E9314A, + 0x93e3A2a50e7A6091B877Fc939e0a3f43954811d8, + 0xd9aeb979cbe3a85F2d6fca3723f846Af8CA56E05, + 0x4A2cFAa5B24850493298DCb8969fe11FEeFfc366, + 0x1C6Aed70006d8d65c6407Fa9b27C9359C1572f40, + 0x92c34B736B832d33209497f3904C4423d11E2a8a, + 0xCAf9131ADF841cC8A3f8C14cBc94Aa16cF0a2faA, + 0x9214835f82752E0574F3828A4400C36Ba386Df82, + 0xF09E6d5EE9b5B7FC84412260FD6E6D70dCadcd9C, + 0x8b529eF78046008f9d1FbC91c7407030De96EE32, + 0xa77E705d7166750F53F60ca7e246BAFBE40f5c42, + 0x4fb30f8CcE1F80FC9CC45F7F626069be7549aF59, + 0xc210a4D56D99E095b126664fDCE545b369ED3FD0, + 0x03adFaA573aC1a9b19D2b8F79a5aAFFb9c2A0532, + 0x79bc970B69CC8fb31406C409692eD19612E39731, + 0xb9b4800282BDC0DD2284fD321cf6800a24a7B8B0, + 0x06e477F88ae55721B72f0F2c545328eDe998803e, + 0xc25404122C1776689A6fD620d0F816Cb9e36e5dD, + 0x2192013589F926637cB288004db2f1dadb71D390, + 0xb0DebcB643eE79f19Ef659Bd01D0FAC12d058604, + 0x09e63186b4F94FafcCC60a42D3f77b4A10995674, + 0x521e6f07bDfa2fF8D072887F0ef3bB908a3D9e2a, + 0x341979e95a3e2969EaDa286b635d6e29F7a76a71, + 0xAD89C778C7dBC0C7546cE7Fdf6A1d87C369A7892, + 0x4987f13D50AC3246E589433349095492e820f8fd, + 0x22472e7cA126b25fA031DeA6A7E0a7fbD9B2cdfd, + 0x595e187831CB1ebAf3e4c09423eef3E74DfDaD1E, + 0xaD96ACD2e6574682AEBf8f25774FA643bcaa3B5a, + 0x82C421023dBE34CC526F4c027577363E6C7Af9b6, + 0xc51101BAbC9Cc75118fBCe5187914e45841A7f01, + 0x09505A42D693D828B78CC87ed64DB9A0348Ca5e6, + 0xdD543DC0efeA305A4c595305C784166fbe87897B, + 0xbb85bFF98F145af30BA935Ae4DeD97C7A5Ce9bF2, + 0xfd005e01FAa45d3CD3fD4641FBCd9FFa1d26C703, + 0xaB14C744e5316475dd2384E6Bb95F9eCF4480bD5, + 0x595f5e9cF36dF0AAf12Ae27f1b3dC18622Fc5748, + 0xF5AC5943D16fC865824910033B756519DC396682, + 0xa0c507E52359160a334d9f1AeAA459b8BD39568a, + 0x0E16cAA0E000cD3B68b4128C6aE62E846cb5b431, + 0x603c081e1d72063Cd691CAD3Bd2FEd68eBE8922a, + 0x5c53414E1f15D7668c2b9EC0A92482A64845f5f6, + 0xa87D56273CBE184927D92eEF1613ab2a72eacAC8, + 0xCc9757171B3cf4Dc86D1a86370D5B10662519096, + 0xeB92f0f58b218ad22a22741D5aa5bb7250E70045, + 0xa2608e76d835D0b3DB2Bbcbd4Bd1F78D47208353, + 0x9fD8Ef7E3867E962B401bC7289272653FEC11245, + 0x73cF6fCbEb5a5f56e550Cce5e0eeAC47f622EA2e, + 0x9b4A7002a086d4F8dFf04Af524987DdCa9b6ddCd, + 0x94Ae5310ba47b42D6D8DF3F20cD55650Ad43Be37, + 0xFd01eF34D30E6fA1169CBa769a22eEE454906f38, + 0xE3c3cD192Bdf289558A0bA645bc2c2c822F7EB06, + 0x18514a31077b33f32BDbeA52443f2C0EEb094Be1, + 0x0E9852b16AE49C99B84b0241E3C6F4a5692C6b05, + 0x2E6Ff365367c9DB7C4a6140d37394aB8CF25C8A2, + 0x49400E8A2d270669ED7cfc4Fd5A2804c0A87e1BB, + 0xDDc91d503Eeb7C3CB78559fB998C0a23354821ee, + 0x0C5Cd230e9EBF5A707CF1f822Dfc49664304DfD2, + 0x92751712B2F6ADE4AD1F35a837591A66394C6799, + 0x57Fe7D8A10F5C4aC2Ca953a5B0a6b4BBfEC187C9, + 0x5413E315A8c242132aC5DD626B0eceE616e5023D, + 0xf458B8FB16D661bC1E260991EBa9b1FFBC8bA39b, + 0xC6Ad105483F89de96A3E423980fB550465A05D9b, + 0x26C43439031301a85e19868647DF46177869c4C8, + 0x9cB4a586bbdE7528DB256c3FCE08845255d892D9, + 0xb1f607F3B753bEf4756a30cfC235629A0c2d1E57, + 0x1CbB1E8274E44287517A34De6AF3CF97dfD9E0Cb, + 0x2b3F539dAf59126F6F566adD0dEDc19959305e9d, + 0x2eCe359621497F4C9053c30912e9ecB7e74718c6, + 0x1A50D1AeD031eE6B22b37c84eD8f3f3487C5Ca47, + 0x117AD17Bf596D6337931f41141F34275Eab16129, + 0x5BD39fcEE33D45C5c5f615e09D2AA785Aa0c36C6, + 0x698230D8445C21E99f4CC81F8F733d205a661cD3, + 0x10b453C5379877d6b55B71D73Af7D8Fbc69eeF91, + 0x13deFCe8Be050f902f8eE06CB7f0E743e3e8b705, + 0xFF5A5bBC1b0f124974165b2190f460438d7CD220, + 0x7F5B2c355bFC0C6F3aE2D394534C29D3609FF542, + 0x007EA857fdbE8D19997b507a21B4c377e3B21D82, + 0x97F4aa12F637BAe9c07cA492eE281534b4F3BB50, + 0x23b3511474c960960E4C030e572dADE633Cfc0F6, + 0x7cA50563448C1f87cF131D96dEF7124f995cf3aC, + 0xA8617c6BdE242B213bA283c93aCf034dfcbE66fA, + 0x68f6609d45A9dA001A88f7A3b9ec236Acf27e1f5, + 0x37ae94b377B6D6522BCED1a448D144C69B6bfa9a, + 0x8Fb2B9010780c418fbf84733ca3F0F5623EC2e7a, + 0x5F4BD8107FF5EB8fD25480C938662bBC11da27B8, + 0x57453361422BfBE5332eF75CBE1C38242c5deEEA, + 0x96b29b8dD6240144e203F9f45D55F8FE6fC466aB, + 0x00ab2b9D924aaDCb60126A4050B68e8531A6A5a6, + 0x431588Aff8ea1BEcB1d8188D87195Aa95678BA0A, + 0x0E9B063789909565CEdA1Fba162474405A151E66, + 0x5952f70FEF1CbC26856d149646D4A8F97E923eE7, + 0x894d222eCeC91B9dFFf8bcBD164B7a72DF7469aD, + 0x8B714cB45683733fD4Dc423FE6BF9A1d9F8DDD2C, + 0x5E9b31FA592913C2aD2356ed843F66057c81455D, + 0x58F1eFd2fBf415CFC0Ce30A13c36A2642690eDA0, + 0x84e47C7E20Ee8e8d6195A99788226EB488f5Fad7, + 0xCce7AcFb0d4760df15a1f87C92324B166e9C0638 + ]; + + function setUp() public { + vm.createSelectFork("etherlink"); + migratorPvtKey = vm.envUint("PRIVATE_KEY"); + migrator = vm.addr(migratorPvtKey); + + oldVault = 0xe24e5DEbA01Ab0B5D78A0093442De0864832803E; + newVault = address(0); + + migrationHelper = MigrationHelper(address(0)); + + vm.label(oldVault, "oldVault"); + vm.label(newVault, "newVault"); + vm.label(address(migrationHelper), "migrationHelper"); + } + + function run() public { + vm.startBroadcast(migratorPvtKey); + + console.log("OLD VAULT STATE BEFORE MIGRATION"); + _logVaultState(oldVault); + console.log( + "old vault last realized fee exchange rate", + IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate() + ); + + console.log("================================================"); + + _preMigrationSetup(); + _performMigration(); + _postMigrationSetup(); + + console.log("NEW VAULT STATE AFTER MIGRATION"); + _logVaultState(newVault); + console.log( + "new vault last realized fee exchange rate", + IAccountantModule(ISuperloop(newVault).accountant()).lastRealizedFeeExchangeRate() + ); + console.log("================================================"); + + console.log("OLD VAULT STATE AFTER MIGRATION"); + _logVaultState(oldVault); + console.log( + "old vault last realized fee exchange rate", + IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate() + ); + + console.log("================================================"); + + vm.stopBroadcast(); + } + + function _deployMigrationHelper() internal returns (MigrationHelper) { + return new MigrationHelper( + AAVE_V3_POOL_ADDRESSES_PROVIDER, + REPAY_MODULE, + WITHDRAW_MODULE, + DEPOSIT_MODULE, + BORROW_MODULE, + DEX_MODULE, + USDC + ); + } + + function _preMigrationSetup() internal { + ISuperloop(oldVault).setPrivilegedAddress(address(migrationHelper), true); + + ISuperloop(newVault).setRegisteredModule(REPAY_MODULE, true); + ISuperloop(newVault).setRegisteredModule(WITHDRAW_MODULE, true); + ISuperloop(newVault).setRegisteredModule(DEPOSIT_MODULE, true); + ISuperloop(newVault).setRegisteredModule(BORROW_MODULE, true); + ISuperloop(newVault).setRegisteredModule(DEX_MODULE, true); + + ISuperloop(newVault).setDepositManagerModule(address(migrationHelper)); + ISuperloop(newVault).setVaultOperator(address(migrationHelper)); + IAccountantModule(ISuperloop(newVault).accountant()).setVault(address(migrationHelper)); + Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))) + .transferOwnership(address(migrationHelper)); + + // assert these roles + address withdrawManager = ISuperloopLegacy(oldVault).withdrawManagerModule(); + assert(IERC20(XTZ).balanceOf(withdrawManager) == 0 && IERC20(oldVault).balanceOf(withdrawManager) == 0); + assert(ISuperloop(newVault).depositManagerModule() == address(migrationHelper)); + assert(ISuperloop(newVault).vaultOperator() == address(migrationHelper)); + assert(IAccountantModule(ISuperloop(newVault).accountant()).vault() == address(migrationHelper)); + assert( + Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))).owner() == address(migrationHelper) + ); + } + + function _performMigration() internal { + migrationHelper.migrate( + oldVault, newVault, ALL_USERS, ST_XTZ, XTZ, batches, maxSharesDelta, maxPercentageChangeInExchangeRate + ); + + for (uint256 i = 0; i < ALL_USERS.length; i++) { + assert(ISuperloop(newVault).balanceOf(ALL_USERS[i]) == ISuperloop(oldVault).balanceOf(ALL_USERS[i])); + } + IAccountantModule accountant = IAccountantModule(ISuperloop(newVault).accountant()); + uint256 newVaultLastRealizedFeeExchangeRate = + IAccountantModule(ISuperloop(newVault).accountant()).lastRealizedFeeExchangeRate(); + uint256 oldVaultLastRealizedFeeExchangeRate = + IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate(); + + assert(newVaultLastRealizedFeeExchangeRate == oldVaultLastRealizedFeeExchangeRate); + assert(Ownable(address(accountant)).owner() == address(migrator)); + assert(accountant.vault() == address(newVault)); + } + + function _postMigrationSetup() internal { + ISuperloop(newVault).setVaultOperator(migrator); + ISuperloop(newVault).setDepositManagerModule(newVaultDepositManager); + + ISuperloop(newVault).setRegisteredModule(REPAY_MODULE, false); + ISuperloop(newVault).setRegisteredModule(WITHDRAW_MODULE, false); + ISuperloop(newVault).setRegisteredModule(DEPOSIT_MODULE, false); + ISuperloop(newVault).setRegisteredModule(BORROW_MODULE, false); + ISuperloop(newVault).setRegisteredModule(DEX_MODULE, false); + + // assert these roles + assert(ISuperloop(newVault).vaultOperator() == migrator); + assert(ISuperloop(newVault).depositManagerModule() == newVaultDepositManager); + assert(Ownable(address(IAccountantModule(ISuperloop(newVault).accountant()))).owner() == address(migrator)); + assert(IAccountantModule(ISuperloop(newVault).accountant()).vault() == newVault); + } + + function _logVaultState(address vault) internal view { + uint256 vaultXTZBalance = IERC20(XTZ).balanceOf(vault); + uint256 vaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(vault); + (uint256 vaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, vault); + (,, uint256 vaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, vault); + + console.log("vaultXTZBalance", vaultXTZBalance); + console.log("vaultSTXTZBalance", vaultSTXTZBalance); + console.log("vaultLendBalance", vaultLendBalance); + console.log("vaultBorrowBalance", vaultBorrowBalance); + console.log("vault total supply", ISuperloop(vault).totalSupply()); + console.log("vault total assets", ISuperloop(vault).totalAssets()); + } +} diff --git a/src/common/DataTypes.sol b/src/common/DataTypes.sol index 3df3384..0c1d07f 100644 --- a/src/common/DataTypes.sol +++ b/src/common/DataTypes.sol @@ -54,7 +54,6 @@ library DataTypes { DEFERRED, // low slippage queue PRIORITY, // medium slippage queue INSTANT // high slippage queue - } struct WithdrawQueue { @@ -105,7 +104,6 @@ library DataTypes { UNPROCESSED, // Request is pending processing CLAIMABLE, // Request is ready to be claimed CANCELLED // Request has been cancelled - } /** @@ -222,7 +220,6 @@ library DataTypes { enum CallType { CALL, // Regular call to external contract DELEGATECALL // Delegate call to external contract - } /** @@ -363,4 +360,40 @@ library DataTypes { uint256 preIF1; uint256 preIF2; } + + /** + * @notice Structure for Merkl claim parameters + * @param users The addresses of the users to claim rewards for + * @param tokens The addresses of the tokens to claim rewards for + * @param amounts The amounts of the tokens to claim rewards for + * @param proofs The proofs of the Merkle tree + */ + struct MerklClaimParams { + address[] users; + address[] tokens; + uint256[] amounts; + bytes32[][] proofs; + } + + /** + * @notice Structure for vault action parameters + * @param vault The address of the vault + * @param amount The amount of the vault to supply + */ + struct VaultActionParams { + address vault; + uint256 amount; + } + + /** + * @notice Structure for Morpho flashloan parameters + * @param asset The address of the asset to flashloan + * @param amount The amount to flashloan + * @param callbackExecutionData The data to execute in the flashloan callback + */ + struct MorphoFlashloanParams { + address asset; + uint256 amount; + bytes callbackExecutionData; + } } diff --git a/src/core/Accountant/aaveV3Accountant/AccountantAaveV3.sol b/src/core/Accountant/aaveV3Accountant/AccountantAaveV3.sol index 37e3113..d1ef19a 100644 --- a/src/core/Accountant/aaveV3Accountant/AccountantAaveV3.sol +++ b/src/core/Accountant/aaveV3Accountant/AccountantAaveV3.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.13; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {IPoolAddressesProvider} from "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol"; import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; @@ -15,6 +16,12 @@ import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol import {Errors} from "../../../common/Errors.sol"; import {AccountantAaveV3Base} from "./AccountantAaveV3Base.sol"; +/** + * @title AccountantAaveV3 + * @author Superlend + * @notice Manages total assets calculation and performance fee tracking for Aave V3 positions + * @dev Calculates total assets by aggregating lending positions, subtracting borrowing positions, and converting to base asset using Aave oracle prices + */ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { event LastRealizedFeeExchangeRateUpdated(uint256 oldRate, uint256 newRate); @@ -28,6 +35,10 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { __AccountantAaveV3Base_init(_msgSender()); } + /** + * @notice Initializes the AccountantAaveV3 module with Aave V3 configuration + * @param data Initialization data containing pool addresses provider, lend/borrow assets, performance fee, and vault address + */ function __AccountantAaveV3Module_init(DataTypes.AaveV3AccountantModuleInitData memory data) internal onlyInitializing @@ -39,7 +50,11 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { AccountantAaveV3Storage.setVault(data.vault); } - // get total assets for the contract + /** + * @notice Calculates total assets by aggregating Aave V3 positions and converting to base asset + * @return Total assets value in base asset terms + * @dev Sums lending positions (positive balance), subtracts borrowing positions (negative balance), adds base asset balance, and converts to base asset using oracle prices + */ function getTotalAssets() public view returns (uint256) { AccountantAaveV3Storage.AccountantAaveV3State storage $ = AccountantAaveV3Storage.getAccountantAaveV3Storage(); address baseAsset = IERC4626($.vault).asset(); @@ -68,6 +83,7 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { } } + // sum all borrowing positions len = $.borrowAssets.length; for (uint256 i; i < len;) { address borrowAsset = $.borrowAssets[i]; @@ -82,9 +98,10 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { } } + // add base asset balance held in vault uint256 baseAssetPrice = aaveOracle.getAssetPrice(baseAsset); - positiveBalance += - (IERC20(baseAsset).balanceOf($.vault) * commonDecimalFactor * baseAssetPrice) / (10 ** baseDecimals); + positiveBalance += (IERC20(baseAsset).balanceOf($.vault) * commonDecimalFactor * baseAssetPrice) + / (10 ** baseDecimals); // convert to base asset uint256 totalAssetsInBaseAsset = (positiveBalance - negativeBalance) / baseAssetPrice; @@ -92,6 +109,14 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { return totalAssetsInBaseAsset; } + /** + * @notice Calculates the performance fee based on exchange rate appreciation + * @param totalShares Total number of shares in the vault + * @param exchangeRate Current exchange rate (assets per share) + * @param decimals Decimal precision for the calculation + * @return Performance fee amount in base asset terms + * @dev Calculates fee only on positive performance (when current rate > last realized rate) + */ function getPerformanceFee(uint256 totalShares, uint256 exchangeRate, uint8 decimals) public view @@ -103,6 +128,7 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { uint256 latestAssetAmount = totalShares * exchangeRate; uint256 prevAssetAmount = totalShares * $.lastRealizedFeeExchangeRate; + // return 0 if there's no positive performance if (prevAssetAmount > latestAssetAmount) return 0; uint256 interestGenerated = latestAssetAmount - prevAssetAmount; @@ -113,6 +139,12 @@ contract AccountantAaveV3 is ReentrancyGuardUpgradeable, AccountantAaveV3Base { return performanceFee; } + /** + * @notice Updates the last realized fee exchange rate after performance fee collection + * @param lastRealizedFeeExchangeRate_ New exchange rate to set as the last realized rate + * @param totalSupply Total supply of shares in the vault + * @dev Only allows rate updates that are higher than the previous rate (unless totalSupply is 0) + */ function setLastRealizedFeeExchangeRate(uint256 lastRealizedFeeExchangeRate_, uint256 totalSupply) public onlyVault diff --git a/src/core/Accountant/aaveV3Accountant/AccountantAaveV3Base.sol b/src/core/Accountant/aaveV3Accountant/AccountantAaveV3Base.sol index a7432ce..f087fbf 100644 --- a/src/core/Accountant/aaveV3Accountant/AccountantAaveV3Base.sol +++ b/src/core/Accountant/aaveV3Accountant/AccountantAaveV3Base.sol @@ -6,6 +6,12 @@ import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/a import {AccountantAaveV3Storage} from "../../lib/AccountantAaveV3Storage.sol"; import {IAccountantAaveV3Module} from "../../../interfaces/IAccountantAaveV3Module.sol"; +/** + * @title AccountantAaveV3Base + * @author Superlend + * @notice Base contract providing owner-controlled configuration for AccountantAaveV3 + * @dev Handles owner-only functions for setting Aave V3 pool addresses provider, lend/borrow assets, performance fee, and vault address + */ abstract contract AccountantAaveV3Base is OwnableUpgradeable, IAccountantAaveV3Module { event PoolAddressesProviderUpdated(address indexed oldProvider, address indexed newProvider); event LendAssetsUpdated(address[] oldAssets, address[] newAssets); @@ -17,30 +23,50 @@ abstract contract AccountantAaveV3Base is OwnableUpgradeable, IAccountantAaveV3M __Ownable_init(owner); } + /** + * @notice Sets the Aave V3 pool addresses provider + * @param poolAddressesProvider_ Address of the Aave V3 pool addresses provider + */ function setPoolAddressesProvider(address poolAddressesProvider_) external onlyOwner { address oldProvider = AccountantAaveV3Storage.getAccountantAaveV3Storage().poolAddressesProvider; AccountantAaveV3Storage.setPoolAddressesProvider(poolAddressesProvider_); emit PoolAddressesProviderUpdated(oldProvider, poolAddressesProvider_); } + /** + * @notice Sets the list of assets that are being lent on Aave V3 + * @param lendAssets_ Array of asset addresses being lent + */ function setLendAssets(address[] memory lendAssets_) external onlyOwner { address[] memory oldAssets = AccountantAaveV3Storage.getAccountantAaveV3Storage().lendAssets; AccountantAaveV3Storage.setLendAssets(lendAssets_); emit LendAssetsUpdated(oldAssets, lendAssets_); } + /** + * @notice Sets the list of assets that are being borrowed on Aave V3 + * @param borrowAssets_ Array of asset addresses being borrowed + */ function setBorrowAssets(address[] memory borrowAssets_) external onlyOwner { address[] memory oldAssets = AccountantAaveV3Storage.getAccountantAaveV3Storage().borrowAssets; AccountantAaveV3Storage.setBorrowAssets(borrowAssets_); emit BorrowAssetsUpdated(oldAssets, borrowAssets_); } + /** + * @notice Sets the performance fee rate in basis points + * @param performanceFee_ Performance fee in basis points (BPS) + */ function setPerformanceFee(uint16 performanceFee_) external onlyOwner { uint16 oldFee = AccountantAaveV3Storage.getAccountantAaveV3Storage().performanceFee; AccountantAaveV3Storage.setPerformanceFee(performanceFee_); emit PerformanceFeeUpdated(oldFee, performanceFee_); } + /** + * @notice Sets the vault address that this accountant is associated with + * @param vault_ Address of the Superloop vault + */ function setVault(address vault_) external onlyOwner { address oldVault = AccountantAaveV3Storage.getAccountantAaveV3Storage().vault; AccountantAaveV3Storage.setVault(vault_); diff --git a/src/core/Accountant/universalAccountant/UniversalAccountant.sol b/src/core/Accountant/universalAccountant/UniversalAccountant.sol index 17410b0..8dabd9d 100644 --- a/src/core/Accountant/universalAccountant/UniversalAccountant.sol +++ b/src/core/Accountant/universalAccountant/UniversalAccountant.sol @@ -5,11 +5,18 @@ pragma solidity ^0.8.13; import {UniversalAccountantBase} from "./UniversalAccountantBase.sol"; import {DataTypes} from "../../../common/DataTypes.sol"; import {UniversalAccountantStorage} from "../../lib/UniversalAccountantStorage.sol"; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {Errors} from "../../../common/Errors.sol"; import {IAaveV3AccountantPlugin} from "../../../interfaces/IAaveV3AccountantPlugin.sol"; +/** + * @title UniversalAccountant + * @author Superlend + * @notice Manages total assets aggregation and performance fee calculations across multiple accountant plugins + * @dev Aggregates asset values from registered accountant plugins and calculates performance fees based on exchange rate changes + */ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradeable { event LastRealizedFeeExchangeRateUpdated(uint256 oldRate, uint256 newRate); @@ -23,6 +30,10 @@ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradea __UniversalAccountantBase_init(_msgSender()); } + /** + * @notice Initializes the UniversalAccountant module with registered accountants, performance fee, and vault address + * @param data Initialization data containing registered accountants, performance fee, and vault address + */ function __UniversalAccountantModule_init(DataTypes.UniversalAccountantModuleInitData memory data) internal onlyInitializing @@ -32,6 +43,11 @@ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradea UniversalAccountantStorage.setVault(data.vault); } + /** + * @notice Aggregates total assets from all registered accountant plugins + * @return Total assets value across all registered accountant plugins + * @dev Iterates through all registered accountants and sums their total assets for the vault + */ function getTotalAssets() public view returns (uint256) { UniversalAccountantStorage.UniversalAccountantState storage $ = UniversalAccountantStorage.getUniversalAccountantStorage(); @@ -47,6 +63,14 @@ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradea return totalAssets; } + /** + * @notice Calculates the performance fee based on exchange rate appreciation + * @param totalShares Total number of shares in the vault + * @param exchangeRate Current exchange rate (assets per share) + * @param decimals Decimal precision for the calculation + * @return Performance fee amount in base asset terms + * @dev Calculates fee only on positive performance (when current rate > last realized rate) + */ function getPerformanceFee(uint256 totalShares, uint256 exchangeRate, uint8 decimals) public view @@ -59,6 +83,7 @@ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradea uint256 latestAssetAmount = totalShares * exchangeRate; uint256 prevAssetAmount = totalShares * $.lastRealizedFeeExchangeRate; + // return 0 if there's no positive performance if (prevAssetAmount > latestAssetAmount) return 0; uint256 interestGenerated = latestAssetAmount - prevAssetAmount; @@ -69,6 +94,12 @@ contract UniversalAccountant is UniversalAccountantBase, ReentrancyGuardUpgradea return performanceFee; } + /** + * @notice Updates the last realized fee exchange rate after performance fee collection + * @param lastRealizedFeeExchangeRate_ New exchange rate to set as the last realized rate + * @param totalSupply Total supply of shares in the vault + * @dev Only allows rate updates that are higher than the previous rate (unless totalSupply is 0) + */ function setLastRealizedFeeExchangeRate(uint256 lastRealizedFeeExchangeRate_, uint256 totalSupply) public onlyVault diff --git a/src/core/Accountant/universalAccountant/UniversalAccountantBase.sol b/src/core/Accountant/universalAccountant/UniversalAccountantBase.sol index 5bb6d2f..811b916 100644 --- a/src/core/Accountant/universalAccountant/UniversalAccountantBase.sol +++ b/src/core/Accountant/universalAccountant/UniversalAccountantBase.sol @@ -6,6 +6,12 @@ import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/a import {UniversalAccountantStorage} from "../../lib/UniversalAccountantStorage.sol"; import {IAccountantModule} from "../../../interfaces/IAccountantModule.sol"; +/** + * @title UniversalAccountantBase + * @author Superlend + * @notice Base contract providing owner-controlled configuration for UniversalAccountant + * @dev Handles owner-only functions for setting registered accountants, performance fee, and vault address + */ abstract contract UniversalAccountantBase is OwnableUpgradeable, IAccountantModule { event PerformanceFeeUpdated(uint16 oldFee, uint16 newFee); event VaultUpdated(address indexed oldVault, address indexed newVault); @@ -15,19 +21,31 @@ abstract contract UniversalAccountantBase is OwnableUpgradeable, IAccountantModu __Ownable_init(owner); } + /** + * @notice Sets the list of registered accountant plugins to aggregate assets from + * @param registeredAccountants_ Array of accountant plugin addresses + */ function setRegisteredAccountants(address[] memory registeredAccountants_) external onlyOwner { address[] memory oldAccountants = - UniversalAccountantStorage.getUniversalAccountantStorage().registeredAccountants; + UniversalAccountantStorage.getUniversalAccountantStorage().registeredAccountants; UniversalAccountantStorage.setRegisteredAccountants(registeredAccountants_); emit RegisteredAccountantsUpdated(oldAccountants, registeredAccountants_); } + /** + * @notice Sets the performance fee rate in basis points + * @param performanceFee_ Performance fee in basis points (BPS) + */ function setPerformanceFee(uint16 performanceFee_) external onlyOwner { uint16 oldFee = UniversalAccountantStorage.getUniversalAccountantStorage().performanceFee; UniversalAccountantStorage.setPerformanceFee(performanceFee_); emit PerformanceFeeUpdated(oldFee, performanceFee_); } + /** + * @notice Sets the vault address that this accountant is associated with + * @param vault_ Address of the Superloop vault + */ function setVault(address vault_) external onlyOwner { address oldVault = UniversalAccountantStorage.getUniversalAccountantStorage().vault; UniversalAccountantStorage.setVault(vault_); diff --git a/src/core/DepositManager/DepositManager.sol b/src/core/DepositManager/DepositManager.sol index b6e67ad..748f4f6 100644 --- a/src/core/DepositManager/DepositManager.sol +++ b/src/core/DepositManager/DepositManager.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.13; import {Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20Metadata, IERC20} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol"; @@ -107,7 +108,7 @@ contract DepositManager is Initializable, ReentrancyGuardUpgradeable, Context, D snapshot.totalAssetsAfter = ISuperloop(cache.vault).totalAssets() + 1; // calculate shares such that the exchange rate is not updated - uint256 totalNewSharesToMint = _calculateSharesToMint(snapshot, cache.vaultDecimalOffset); + uint256 totalNewSharesToMint = _calculateSharesToMint(snapshot); // decrease the total pending deposits before minting shares $.totalPendingDeposits -= data.amount; @@ -259,15 +260,11 @@ contract DepositManager is Initializable, ReentrancyGuardUpgradeable, Context, D }); } - function _calculateSharesToMint(DataTypes.ExchangeRateSnapshot memory snapshot, uint8 decimalOffset) - internal - pure - returns (uint256) - { + function _calculateSharesToMint(DataTypes.ExchangeRateSnapshot memory snapshot) internal pure returns (uint256) { uint256 totalSupplyAfter = Math.mulDiv( snapshot.totalSupplyBefore, snapshot.totalAssetsAfter, snapshot.totalAssetsBefore, Math.Rounding.Ceil ); - uint256 totalNewSharesToMint = (totalSupplyAfter + 10 ** decimalOffset) - snapshot.totalSupplyBefore; + uint256 totalNewSharesToMint = totalSupplyAfter - snapshot.totalSupplyBefore; return totalNewSharesToMint; } diff --git a/src/core/Superloop/Superloop.sol b/src/core/Superloop/Superloop.sol index cc58d6e..c359f42 100644 --- a/src/core/Superloop/Superloop.sol +++ b/src/core/Superloop/Superloop.sol @@ -134,7 +134,8 @@ contract Superloop is SuperloopVault, SuperloopActions, SuperloopBase { } function realizePerformanceFee() external { - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); } /** diff --git a/src/core/Superloop/SuperloopVault.sol b/src/core/Superloop/SuperloopVault.sol index 24964cf..e621fb9 100644 --- a/src/core/Superloop/SuperloopVault.sol +++ b/src/core/Superloop/SuperloopVault.sol @@ -8,8 +8,9 @@ import { } from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {SuperloopStorage} from "../lib/SuperloopStorage.sol"; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {IAccountantModule} from "../../interfaces/IAccountantModule.sol"; import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol"; @@ -50,19 +51,23 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab } function previewDeposit(uint256 assets) public view override returns (uint256) { - return _convertToSharesWithPerformanceFee(assets, Math.Rounding.Floor); + uint256 totalAssetsCached = totalAssets(); + return _convertToSharesWithPerformanceFee(assets, totalAssetsCached, Math.Rounding.Floor); } function previewMint(uint256 shares) public view override returns (uint256) { - return _convertToAssetsWithPerformanceFee(shares, Math.Rounding.Ceil); + uint256 totalAssetsCached = totalAssets(); + return _convertToAssetsWithPerformanceFee(shares, totalAssetsCached, Math.Rounding.Ceil); } function previewWithdraw(uint256 assets) public view override returns (uint256) { - return _convertToSharesWithPerformanceFee(assets, Math.Rounding.Ceil); + uint256 totalAssetsCached = totalAssets(); + return _convertToSharesWithPerformanceFee(assets, totalAssetsCached, Math.Rounding.Ceil); } function previewRedeem(uint256 shares) public view override returns (uint256) { - return _convertToAssetsWithPerformanceFee(shares, Math.Rounding.Floor); + uint256 totalAssetsCached = totalAssets(); + return _convertToAssetsWithPerformanceFee(shares, totalAssetsCached, Math.Rounding.Floor); } function maxMint(address) public view override returns (uint256) { @@ -77,15 +82,15 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab } function deposit(uint256 assets, address receiver) public override nonReentrant whenNotPaused returns (uint256) { - // realize performance fee - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); require(assets > 0, Errors.INVALID_AMOUNT); // check supply cap require(assets <= maxDeposit(address(0)), Errors.SUPPLY_CAP_EXCEEDED); - // calculate the cash reserve at curren total assets - uint256 cashReserveShortfall = _getCashReserveShortfall(); + // calculate the cash reserve at curren total + uint256 cashReserveShortfall = _getCashReserveShortfall(totalAssetsCached); require(assets <= cashReserveShortfall, Errors.INSUFFICIENT_CASH_SHORTFALL); // preview deposit @@ -98,14 +103,15 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab function mint(uint256 shares, address receiver) public override nonReentrant whenNotPaused returns (uint256) { // realize performance fee - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); require(shares > 0, Errors.INVALID_SHARES_AMOUNT); require(shares <= maxMint(address(0)), Errors.SUPPLY_CAP_EXCEEDED); uint256 assets = previewMint(shares); - uint256 cashReserveShortfall = _getCashReserveShortfall(); + uint256 cashReserveShortfall = _getCashReserveShortfall(totalAssetsCached); require(assets <= cashReserveShortfall, Errors.INSUFFICIENT_CASH_SHORTFALL); require(assets > 0, Errors.INVALID_AMOUNT); @@ -122,7 +128,8 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab returns (uint256) { // realize performance fee - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); // check for max withdraw require(assets <= maxWithdraw(owner), Errors.INSUFFICIENT_BALANCE); @@ -150,7 +157,8 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab returns (uint256) { // realize performance fee - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); // check for max redeem require(shares <= maxRedeem(owner), Errors.INSUFFICIENT_BALANCE); @@ -199,7 +207,8 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab function _seed(uint256 assets) internal returns (uint256) { // realize performance fee => set last ex. rate to 1 - _realizePerformanceFee(); + uint256 totalAssetsCached = totalAssets(); + _realizePerformanceFee(totalAssetsCached); require(assets > 0, Errors.INVALID_AMOUNT); // check supply cap @@ -213,13 +222,11 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab return shares; } - function _realizePerformanceFee() internal { + function _realizePerformanceFee(uint256 totalAssetsCached) internal { SuperloopStorage.SuperloopEssentialRoles storage $ = SuperloopStorage.getSuperloopEssentialRolesStorage(); + (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(totalAssetsCached); - // calculate current exchange rate - (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(); - - uint256 sharesToMint = _getPerformanceFeeAndShares(exchangeRate, $.accountant, decimals); + uint256 sharesToMint = _getPerformanceFeeAndShares(exchangeRate, $.accountant, decimals, totalAssetsCached); // if vault made profit, take a performance fee if (sharesToMint > 0) { @@ -230,16 +237,16 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab } // update the last realized fee exchange rate on the accountant module - (exchangeRate,) = _getCurrentExchangeRate(); + (exchangeRate,) = _getCurrentExchangeRate(totalAssetsCached); IAccountantModule($.accountant).setLastRealizedFeeExchangeRate(exchangeRate, totalSupply()); } - function _getPerformanceFeeAndShares(uint256 exchangeRate, address accountant, uint8 decimals) - internal - view - returns (uint256 shares) - { - uint256 totalAssetsCached = totalAssets(); + function _getPerformanceFeeAndShares( + uint256 exchangeRate, + address accountant, + uint8 decimals, + uint256 totalAssetsCached + ) internal view returns (uint256 shares) { uint256 totalSupplyCached = totalSupply(); // get performance fee @@ -256,43 +263,43 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab return shares; } - function _getCurrentExchangeRate() internal view returns (uint256, uint8) { + function _getCurrentExchangeRate(uint256 totalAssetsCached) internal view returns (uint256, uint8) { uint8 decimals = IERC20Metadata(asset()).decimals(); uint256 assets = 1 * 10 ** decimals; return ( - Math.mulDiv(assets, totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), Math.Rounding.Floor), + Math.mulDiv(assets, totalAssetsCached + 1, totalSupply() + 10 ** _decimalsOffset(), Math.Rounding.Floor), decimals ); } - function _convertToSharesWithPerformanceFee(uint256 assets, Math.Rounding rounding) + function _convertToSharesWithPerformanceFee(uint256 assets, uint256 totalAssetsCached, Math.Rounding rounding) internal view returns (uint256) { - (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(); + (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(totalAssetsCached); uint256 treasuryShares = _getPerformanceFeeAndShares( - exchangeRate, SuperloopStorage.getSuperloopEssentialRolesStorage().accountant, decimals + exchangeRate, SuperloopStorage.getSuperloopEssentialRolesStorage().accountant, decimals, totalAssetsCached ); uint256 _totalSupply = totalSupply() + treasuryShares + 10 ** _decimalsOffset(); - uint256 _totalAssets = totalAssets() + 1; + uint256 _totalAssets = totalAssetsCached + 1; uint256 shares = Math.mulDiv(assets, _totalSupply, _totalAssets, rounding); return shares; } - function _convertToAssetsWithPerformanceFee(uint256 shares, Math.Rounding rounding) + function _convertToAssetsWithPerformanceFee(uint256 shares, uint256 totalAssetsCached, Math.Rounding rounding) internal view returns (uint256) { - (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(); + (uint256 exchangeRate, uint8 decimals) = _getCurrentExchangeRate(totalAssetsCached); uint256 treasuryShares = _getPerformanceFeeAndShares( - exchangeRate, SuperloopStorage.getSuperloopEssentialRolesStorage().accountant, decimals + exchangeRate, SuperloopStorage.getSuperloopEssentialRolesStorage().accountant, decimals, totalAssetsCached ); uint256 _totalSupply = totalSupply() + treasuryShares + 10 ** _decimalsOffset(); - uint256 _totalAssets = totalAssets() + 1; + uint256 _totalAssets = totalAssetsCached + 1; uint256 assets = Math.mulDiv(shares, _totalAssets, _totalSupply, rounding); @@ -303,9 +310,9 @@ abstract contract SuperloopVault is ERC4626Upgradeable, ReentrancyGuardUpgradeab return SuperloopStorage.DECIMALS_OFFSET; } - function _getCashReserveShortfall() internal view returns (uint256) { + function _getCashReserveShortfall(uint256 totalAssetsCached) internal view returns (uint256) { uint256 cashReserveExpected = Math.mulDiv( - totalAssets(), + totalAssetsCached, SuperloopStorage.getSuperloopStorage().cashReserve, SuperloopStorage.MAX_BPS_VALUE, Math.Rounding.Floor diff --git a/src/core/WithdrawManager/WithdrawManager.sol b/src/core/WithdrawManager/WithdrawManager.sol index f7c4cd8..90608c5 100644 --- a/src/core/WithdrawManager/WithdrawManager.sol +++ b/src/core/WithdrawManager/WithdrawManager.sol @@ -5,8 +5,9 @@ pragma solidity ^0.8.13; import {WithdrawManagerBase} from "./WithdrawManagerBase.sol"; import {WithdrawManagerStorage} from "../lib/WithdrawManagerStorage.sol"; import {DataTypes} from "../../common/DataTypes.sol"; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {Context} from "openzeppelin-contracts/contracts/utils/Context.sol"; import {ISuperloop} from "../../interfaces/ISuperloop.sol"; import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/src/helpers/MigrationHelper.sol b/src/helpers/MigrationHelper.sol index e57c910..4f74b9b 100644 --- a/src/helpers/MigrationHelper.sol +++ b/src/helpers/MigrationHelper.sol @@ -388,8 +388,7 @@ contract MigrationHelper is FlashLoanSimpleReceiverBase, Ownable, ReentrancyGuar // Step 3: Transfer all assets from old vault to new vault via DEX module DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](2); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: lendAsset, - data: abi.encodeWithSelector(IERC20.transfer.selector, address(newVault), lendBalance) + target: lendAsset, data: abi.encodeWithSelector(IERC20.transfer.selector, address(newVault), lendBalance) }); uint256 xtzBalanceOldVault = IERC20(borrowAsset).balanceOf(oldVault); swapParamsData[1] = DataTypes.ExecuteSwapParamsData({ @@ -455,8 +454,7 @@ contract MigrationHelper is FlashLoanSimpleReceiverBase, Ownable, ReentrancyGuar // Step 3: Transfer borrowed asset back to migration helper for flash loan repayment DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](1); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: borrowAsset, - data: abi.encodeWithSelector(IERC20.transfer.selector, address(this), borrowBalance) + target: borrowAsset, data: abi.encodeWithSelector(IERC20.transfer.selector, address(this), borrowBalance) }); DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ tokenIn: placeholderAsset, diff --git a/src/interfaces/IWithdrawManager.sol b/src/interfaces/IWithdrawManager.sol index e97bdd5..a4f10ac 100644 --- a/src/interfaces/IWithdrawManager.sol +++ b/src/interfaces/IWithdrawManager.sol @@ -86,10 +86,7 @@ interface IWithdrawManager { * @param ids Array of withdrawal request IDs * @return Array of withdrawal request data */ - function withdrawRequests(uint256[] memory ids) - external - view - returns (DataTypes.WithdrawRequestDataLegacy[] memory); + function withdrawRequests(uint256[] memory ids) external view returns (DataTypes.WithdrawRequestDataLegacy[] memory); /** * @notice Gets the withdrawal request ID for a specific user diff --git a/src/mock/IUniPool.sol b/src/mock/IUniPool.sol new file mode 100644 index 0000000..9c2e31f --- /dev/null +++ b/src/mock/IUniPool.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +interface IUniPool { + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, // +ive means exact input, -ive means exact output + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + function slot0() external view returns (Slot0 memory); +} diff --git a/src/mock/MockIRouter.sol b/src/mock/MockIRouter.sol index 495b1c9..c838513 100644 --- a/src/mock/MockIRouter.sol +++ b/src/mock/MockIRouter.sol @@ -14,6 +14,7 @@ interface IRouter { address tokenOut; uint24 fee; address recipient; + uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; @@ -26,6 +27,7 @@ interface IRouter { address tokenOut; uint24 fee; address recipient; + uint256 deadline; uint256 amountOut; uint256 amountInMaximum; uint160 sqrtPriceLimitX96; diff --git a/src/mock/MockWithdrawManager.sol b/src/mock/MockWithdrawManager.sol index 34ead7d..5b4c90d 100644 --- a/src/mock/MockWithdrawManager.sol +++ b/src/mock/MockWithdrawManager.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.13; import {Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; -import {ReentrancyGuardUpgradeable} from - "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; +import { + ReentrancyGuardUpgradeable +} from "openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/modules/callback/MorphoCallbackHandler.sol b/src/modules/callback/MorphoCallbackHandler.sol new file mode 100644 index 0000000..576c6e3 --- /dev/null +++ b/src/modules/callback/MorphoCallbackHandler.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Context} from "openzeppelin-contracts/contracts/utils/Context.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; + +/** + * @title MorphoCallbackHandler + * @author Superlend + * @notice Callback handler for Morpho flashloan operations + * @dev Handles flashloan callbacks and adjusts approval amounts to include premiums + */ +contract MorphoCallbackHandler is Context { + /** + * @notice Executes the flashloan callback operation + * @param params The encoded callback data parameters + * @return callbackData The processed callback data + * @return success Always returns true to indicate successful execution + */ + function onMorphoFlashLoan(uint256, bytes calldata params) + external + pure + returns (DataTypes.CallbackData memory, bool) + { + DataTypes.CallbackData memory callbackData = abi.decode(params, (DataTypes.CallbackData)); + return (callbackData, true); + } +} diff --git a/src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol b/src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol index 85d8cbb..54672f5 100644 --- a/src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol +++ b/src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol @@ -139,9 +139,9 @@ contract AaveV3PreliquidationFallbackHandler is Context { uint256 debtToCover = Math.min(Math.min(params.debtToCover, maxDebtToCover), borrowTokens); uint256 debtCoverUSD = (debtToCover * borrowPriceUSD) / (10 ** borrowReserveDecimals); // in oracle decimals - uint256 collateralToSieze = ( - ((debtCoverUSD * 10 ** lendReserveDecimals) / collateralPriceUSD) * currentIncentiveFactor - ) / WadRayMath.WAD; // in tokens + uint256 collateralToSieze = + (((debtCoverUSD * 10 ** lendReserveDecimals) / collateralPriceUSD) * currentIncentiveFactor) + / WadRayMath.WAD; // in tokens // transfer debt tokens to self SafeERC20.safeTransferFrom(IERC20(borrowReserve), _msgSender(), address(this), debtToCover); diff --git a/src/modules/merkl/IDistributor.sol b/src/modules/merkl/IDistributor.sol new file mode 100644 index 0000000..86dea74 --- /dev/null +++ b/src/modules/merkl/IDistributor.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +interface IDistributor { + struct MerkleTree { + bytes32 merkleRoot; + bytes32 ipfsHash; + } + + function updateTree(MerkleTree calldata _tree) external; + + function claim( + address[] calldata users, + address[] calldata tokens, + uint256[] calldata amounts, + bytes32[][] calldata proofs + ) external; +} diff --git a/src/modules/merkl/MerklModule.sol b/src/modules/merkl/MerklModule.sol new file mode 100644 index 0000000..2fbdae2 --- /dev/null +++ b/src/modules/merkl/MerklModule.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {IDistributor} from "./IDistributor.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; + +/** + * @title MerklModule + * @author Superlend + * @notice Module for claiming rewards from Merkl + * @dev Extends IDistributor to provide claiming functionality + */ +contract MerklModule { + /** + * @notice The distributor contract + */ + IDistributor public immutable distributor; + + /** + * @notice Emitted when rewards are claimed + * @param users The addresses of the users who claimed rewards + * @param tokens The addresses of the tokens claimed + * @param amounts The amounts of the tokens claimed + */ + event MerklRewardClaimed(address[] users, address[] tokens, uint256[] amounts); + + /** + * @notice Constructor + * @param _distributor The address of the distributor contract + */ + constructor(address _distributor) { + distributor = IDistributor(_distributor); + } + + /** + * @notice Executes the claim operation + * @param params The parameters for the claim operation + */ + function execute(DataTypes.MerklClaimParams memory params) external { + distributor.claim(params.users, params.tokens, params.amounts, params.proofs); + + emit MerklRewardClaimed(params.users, params.tokens, params.amounts); + } +} diff --git a/src/modules/morpho/MorphoFlashloanModule.sol b/src/modules/morpho/MorphoFlashloanModule.sol new file mode 100644 index 0000000..2ae5ee0 --- /dev/null +++ b/src/modules/morpho/MorphoFlashloanModule.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {IMorphoBase} from "morpho-blue/interfaces/IMorpho.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; +import {Context} from "openzeppelin-contracts/contracts/utils/Context.sol"; +import {Errors} from "../../common/Errors.sol"; +import {SuperloopStorage} from "../../core/lib/SuperloopStorage.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract MorphoFlashloanModule is Context { + event MorphoFlashloanExecuted(address indexed asset, uint256 amount, address indexed borrower); + + IMorphoBase public immutable morpho; + + constructor(address morpho_) { + morpho = IMorphoBase(morpho_); + } + + function execute(DataTypes.MorphoFlashloanParams memory params) external onlyExecutionContext { + // format the params for flashloan + bytes memory callbackData = abi.encode( + DataTypes.CallbackData({ + asset: params.asset, + addressToApprove: address(morpho), + amountToApprove: params.amount, + executionData: params.callbackExecutionData + }) + ); + + // call the flashloan + morpho.flashLoan(params.asset, params.amount, callbackData); + + // remove approval from morpho after flashloan is done + SafeERC20.forceApprove(IERC20(params.asset), address(morpho), 0); + + emit MorphoFlashloanExecuted(params.asset, params.amount, address(this)); + } + + modifier onlyExecutionContext() { + require(_isExecutionContext(), Errors.NOT_IN_EXECUTION_CONTEXT); + _; + } + + function _isExecutionContext() internal view returns (bool) { + return SuperloopStorage.isInExecutionContext(); + } +} diff --git a/src/modules/vault/VaultActionModule.sol b/src/modules/vault/VaultActionModule.sol new file mode 100644 index 0000000..82027c9 --- /dev/null +++ b/src/modules/vault/VaultActionModule.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Errors} from "../../common/Errors.sol"; +import {SuperloopStorage} from "../../core/lib/SuperloopStorage.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; + +/** + * @title VaultActionModule + * @author Superlend + * @notice Abstract contract for vault action modules providing base functionality + * @dev Provides common vault integration and execution context validation + */ +abstract contract VaultActionModule { + /** + * @notice Emitted when assets are supplied to a vault + * @param vault The address of the vault + * @param amount The amount of the underlying asset supplied + * @param shares The amount of shares minted + */ + event VaultSupplied(address indexed vault, uint256 amount, uint256 shares); + + /** + * @notice Emitted when assets are withdrawn from a vault + * @param vault The address of the vault + * @param amount The amount of the underlying asset withdrawn + * @param shares The amount of shares withdrawn + */ + event VaultWithdrawn(address indexed vault, uint256 amount, uint256 shares); + + /** + * @notice Executes a vault action with the provided parameters + * @param params The parameters for the vault action + */ + function execute(DataTypes.VaultActionParams memory params) external virtual; + + /** + * @notice Modifier to ensure the function is called within an execution context + * @dev Reverts if not in execution context + */ + modifier onlyExecutionContext() { + require(_isExecutionContext(), Errors.NOT_IN_EXECUTION_CONTEXT); + _; + } + + /** + * @notice Internal function to check if the current call is within an execution context + * @return True if in execution context, false otherwise + */ + function _isExecutionContext() internal view returns (bool) { + return SuperloopStorage.isInExecutionContext(); + } +} diff --git a/src/modules/vault/VaultSupplyModule.sol b/src/modules/vault/VaultSupplyModule.sol new file mode 100644 index 0000000..ae162ec --- /dev/null +++ b/src/modules/vault/VaultSupplyModule.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {VaultActionModule} from "./VaultActionModule.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; + +/** + * @title VaultSupplyModule + * @author Superlend + * @notice Module for supplying assets to a vault + * @dev Extends IERC4626 to provide vault supply functionality + */ +contract VaultSupplyModule is VaultActionModule { + /** + * @notice Executes the supply operation + * @param params The parameters for the supply operation + */ + function execute(DataTypes.VaultActionParams memory params) external override onlyExecutionContext { + address underlyingAsset = IERC4626(params.vault).asset(); + uint256 amount = + params.amount == type(uint256).max ? IERC20(underlyingAsset).balanceOf(address(this)) : params.amount; + + if (amount == 0) return; + + SafeERC20.forceApprove(IERC20(underlyingAsset), address(IERC4626(params.vault)), amount); + + uint256 shares = IERC4626(params.vault).deposit(amount, address(this)); + + emit VaultSupplied(params.vault, amount, shares); + } +} diff --git a/src/modules/vault/VaultWithdrawModule.sol b/src/modules/vault/VaultWithdrawModule.sol new file mode 100644 index 0000000..5ec5f4c --- /dev/null +++ b/src/modules/vault/VaultWithdrawModule.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {DataTypes} from "../../common/DataTypes.sol"; +import {VaultActionModule} from "./VaultActionModule.sol"; + +/** + * @title VaultWithdrawModule + * @author Superlend + * @notice Module for withdrawing assets from a vault + * @dev Extends IERC4626 to provide vault withdraw functionality + */ +contract VaultWithdrawModule is VaultActionModule { + /** + * @notice Executes the withdraw operation + * @param params The parameters for the withdraw operation + */ + function execute(DataTypes.VaultActionParams memory params) external override onlyExecutionContext { + uint256 amount = + params.amount == type(uint256).max ? IERC4626(params.vault).maxWithdraw(address(this)) : params.amount; + + if (amount == 0) return; + + uint256 shares = IERC4626(params.vault).withdraw(amount, address(this), address(this)); + + emit VaultWithdrawn(params.vault, amount, shares); + } +} diff --git a/test/core/TestBase.sol b/test/core/TestBase.sol index 0f56ef9..c3a8594 100644 --- a/test/core/TestBase.sol +++ b/test/core/TestBase.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.13; -import {Test, console} from "forge-std/Test.sol"; +import {console} from "forge-std/Test.sol"; +import {TestEnv} from "./TestEnv.sol"; import {Vm} from "forge-std/Vm.sol"; import {Superloop} from "../../src/core/Superloop/Superloop.sol"; import {DataTypes} from "../../src/common/DataTypes.sol"; import {SuperloopModuleRegistry} from "../../src/core/ModuleRegistry/ModuleRegistry.sol"; import {AaveV3FlashloanModule} from "../../src/modules/aave/AaveV3FlashloanModule.sol"; import {AaveV3CallbackHandler} from "../../src/modules/callback/AaveV3CallbackHandler.sol"; +import {MorphoCallbackHandler} from "../../src/modules/callback/MorphoCallbackHandler.sol"; import {AaveV3EmodeModule} from "../../src/modules/aave/AaveV3EmodeModule.sol"; import {AaveV3SupplyModule} from "../../src/modules/aave/AaveV3SupplyModule.sol"; import {AaveV3WithdrawModule} from "../../src/modules/aave/AaveV3WithdrawModule.sol"; @@ -22,35 +24,27 @@ import {WithdrawManager} from "../../src/core/WithdrawManager/WithdrawManager.so import {DepositManager} from "../../src/core/DepositManager/DepositManager.sol"; import {DepositManagerCallbackHandler} from "../../src/modules/callback/DepositManagerCallbackHandler.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {UniversalAccountant} from "../../src/core/Accountant/universalAccountant/UniversalAccountant.sol"; import {AaveV3AccountantPlugin} from "../../src/plugins/Accountant/AaveV3AccountantPlugin.sol"; import {WithdrawManagerCallbackHandler} from "../../src/modules/callback/WithdrawManagerCallbackHandler.sol"; import {UnwrapModule} from "../../src/modules/helper/UnwrapModule.sol"; import {WrapModule} from "../../src/modules/helper/WrapModule.sol"; import {AaveV3PreliquidationFallbackHandler} from "../../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {HyperliquidStakeModule} from "../../src/modules/stake/hyperliquid/HyperliquidStakeModule.sol"; +import {KinetiqStakeModule} from "../../src/modules/stake/hyperliquid/KinetiqStakeModule.sol"; +import {HyperbeatStakingModule} from "../../src/modules/stake/hyperliquid/HyperbeatStakingModule.sol"; +import {VaultSupplyModule} from "../../src/modules/vault/VaultSupplyModule.sol"; +import {VaultWithdrawModule} from "../../src/modules/vault/VaultWithdrawModule.sol"; -contract TestBase is Test { - address public constant ST_XTZ = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; - address public constant XTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; - address public constant AAVE_V3_POOL_ADDRESSES_PROVIDER = 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC; - address public constant AAVE_V3_POOL_DATA_PROVIDER = 0x99e8269dDD5c7Af0F1B3973A591b47E8E001BCac; - address public constant AAVE_V3_PRICE_ORACLE = 0xeCF313dE38aA85EF618D06D1A602bAa917D62525; - address public constant POOL = 0x3bD16D195786fb2F509f2E2D7F69920262EF114D; - address public constant XTZ_WHALE = 0x008ae222661B6A42e3A097bd7AAC15412829106b; - address public constant STXTZ_WHALE = 0x65142dEC2969f1a3083Ad31541Ef4B73871C8C9B; - address public constant USDT_WHALE = 0x998098A1B2E95e2b8f15360676428EdFd976861f; - uint256 public constant PERFORMANCE_FEE = 2000; // 20% - address public constant USDT = 0x2C03058C8AFC06713be23e58D2febC8337dbfE6A; - address public constant USDC = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; - address public constant WXTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; - address public constant WBTC = 0xbFc94CD2B1E55999Cfc7347a9313e88702B83d0F; - address public constant ROUTER = 0xbfe9C246A5EdB4F021C8910155EC93e7CfDaB7a0; - address public constant USDC_WHALE = 0xd03bfdF9B26DB1e6764724d914d7c3d18106a9Fb; - address public constant POOL_CONFIGURATOR = 0x30F6880Bb1cF780a49eB4Ef64E64585780AAe060; - address public constant POOL_ADMIN = 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f; +import {MerklModule} from "../../src/modules/merkl/MerklModule.sol"; +import {MorphoFlashloanModule} from "../../src/modules/morpho/MorphoFlashloanModule.sol"; + +abstract contract TestBase is TestEnv { uint256 public constant WAD = 10 ** 18; uint256 public constant BPS = 10000; bytes32 id = bytes32("1"); @@ -61,16 +55,22 @@ contract TestBase is Test { uint256 public constant PRE_IF2 = ((80 + BPS) * WAD) / BPS; uint256 public LLTV = (9600 * WAD) / BPS; + bool public USE_MORPHO = false; + + TestEnvironment public environment; + address public admin; address public treasury; SuperloopModuleRegistry public moduleRegistry; Superloop public superloop; AaveV3FlashloanModule public flashloanModule; + MorphoFlashloanModule public morphoFlashloanModule; DepositManagerCallbackHandler public depositManagerCallbackHandler; WithdrawManagerCallbackHandler public withdrawManagerCallbackHandler; AaveV3PreliquidationFallbackHandler public preliquidationFallbackHandler; AaveV3CallbackHandler public callbackHandler; + MorphoCallbackHandler public morphoCallbackHandler; AaveV3SupplyModule public supplyModule; AaveV3WithdrawModule public withdrawModule; AaveV3BorrowModule public borrowModule; @@ -81,16 +81,38 @@ contract TestBase is Test { WithdrawManager public withdrawManager; UnwrapModule public unwrapModule; WrapModule public wrapModule; - DepositManager public depositManager; + MerklModule public merklModule; + + Superloop public superloopBtc; + DepositManager public depositManagerBtc; + WithdrawManager public withdrawManagerBtc; + UniversalAccountant public accountantBtc; + AccountantAaveV3 public accountantAaveV3Btc; + + VaultSupplyModule public vaultSupplyModule; + VaultWithdrawModule public vaultWithdrawModule; address public mockModule; AaveV3EmodeModule public emodeModule; IPoolDataProvider public poolDataProvider; IPool public pool; - function setUp() public virtual { - vm.createSelectFork("etherlink"); + HyperliquidStakeModule public hyperliquidStakeModule; + KinetiqStakeModule public kinetiqStakeModule; + HyperbeatStakingModule public hyperbeatStakingModule; + + function setUp() public virtual override { + super.setUp(); + + uint256 envIndex = 2; // TODO: move this to config + environment = testEnvironments[envIndex]; + + if (environment.morpho != address(0)) { + USE_MORPHO = true; + } + + vm.createSelectFork(environment.chainName); admin = makeAddr("admin"); treasury = makeAddr("treasury"); @@ -98,52 +120,84 @@ contract TestBase is Test { moduleRegistry = new SuperloopModuleRegistry(); mockModule = makeAddr("mockModule"); moduleRegistry.setModule("MockModule", mockModule); - poolDataProvider = IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER); - pool = IPool(POOL); + poolDataProvider = IPoolDataProvider(environment.poolDataProvider); + pool = IPool(environment.pool); vm.stopPrank(); vm.label(admin, "admin"); vm.label(treasury, "treasury"); vm.label(mockModule, "mockModule"); vm.label(address(moduleRegistry), "moduleRegistry"); - vm.label(XTZ, "XTZ"); - vm.label(ST_XTZ, "ST_XTZ"); - vm.label(AAVE_V3_POOL_ADDRESSES_PROVIDER, "AAVE_V3_POOL_ADDRESSES_PROVIDER"); - vm.label(AAVE_V3_POOL_DATA_PROVIDER, "AAVE_V3_POOL_DATA_PROVIDER"); - vm.label(AAVE_V3_PRICE_ORACLE, "AAVE_V3_PRICE_ORACLE"); - vm.label(POOL, "POOL"); - vm.label(XTZ_WHALE, "XTZ_WHALE"); - vm.label(STXTZ_WHALE, "STXTZ_WHALE"); - vm.label(address(poolDataProvider), "poolDataProvider"); - vm.label(address(pool), "pool"); + for (uint256 i = 0; i < environment.lendAssets.length; i++) { + string memory symbol = IERC20Metadata(environment.lendAssets[i]).symbol(); + vm.label(environment.lendAssets[i], symbol); + } + for (uint256 i = 0; i < environment.borrowAssets.length; i++) { + string memory symbol = IERC20Metadata(environment.borrowAssets[i]).symbol(); + vm.label(environment.borrowAssets[i], symbol); + } + vm.label(environment.poolAddressesProvider, "AAVE_V3_POOL_ADDRESSES_PROVIDER"); + vm.label(environment.poolDataProvider, "AAVE_V3_POOL_DATA_PROVIDER"); + vm.label(environment.priceOracle, "AAVE_V3_PRICE_ORACLE"); + vm.label(environment.pool, "POOL"); + vm.label(environment.vaultAssetWhale, "VAULT_ASSET_WHALE"); + vm.label(environment.stablecoin, "STABLECOIN"); + vm.label(environment.stablecoinWhale, "STABLECOIN_WHALE"); + // vm.label(STXTZ_WHALE, "STXTZ_WHALE"); } function _deployModules() internal { - flashloanModule = new AaveV3FlashloanModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + merklModule = new MerklModule(environment.distributor); + moduleRegistry.setModule("MerklModule", address(merklModule)); + + morphoFlashloanModule = new MorphoFlashloanModule(environment.morpho); + moduleRegistry.setModule("MorphoFlashloanModule", address(morphoFlashloanModule)); + + flashloanModule = new AaveV3FlashloanModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3FlashloanModule", address(flashloanModule)); + callbackHandler = new AaveV3CallbackHandler(); moduleRegistry.setModule("AaveV3CallbackHandler", address(callbackHandler)); - emodeModule = new AaveV3EmodeModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + + morphoCallbackHandler = new MorphoCallbackHandler(); + moduleRegistry.setModule("MorphoCallbackHandler", address(morphoCallbackHandler)); + + emodeModule = new AaveV3EmodeModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3EmodeModule", address(emodeModule)); - supplyModule = new AaveV3SupplyModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + + supplyModule = new AaveV3SupplyModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3SupplyModule", address(supplyModule)); - withdrawModule = new AaveV3WithdrawModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + + withdrawModule = new AaveV3WithdrawModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3WithdrawModule", address(withdrawModule)); - borrowModule = new AaveV3BorrowModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + + borrowModule = new AaveV3BorrowModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3BorrowModule", address(borrowModule)); - repayModule = new AaveV3RepayModule(AAVE_V3_POOL_ADDRESSES_PROVIDER); + + repayModule = new AaveV3RepayModule(environment.poolAddressesProvider); moduleRegistry.setModule("AaveV3RepayModule", address(repayModule)); + dexModule = new UniversalDexModule(); moduleRegistry.setModule("UniversalDexModule", address(dexModule)); + depositManagerCallbackHandler = new DepositManagerCallbackHandler(); moduleRegistry.setModule("DepositManagerCallbackHandler", address(depositManagerCallbackHandler)); + withdrawManagerCallbackHandler = new WithdrawManagerCallbackHandler(); moduleRegistry.setModule("WithdrawManagerCallbackHandler", address(withdrawManagerCallbackHandler)); - unwrapModule = new UnwrapModule(XTZ); + + unwrapModule = new UnwrapModule(environment.vaultAsset); moduleRegistry.setModule("UnwrapModule", address(unwrapModule)); - wrapModule = new WrapModule(XTZ); + + wrapModule = new WrapModule(environment.vaultAsset); moduleRegistry.setModule("WrapModule", address(wrapModule)); + vaultSupplyModule = new VaultSupplyModule(); + moduleRegistry.setModule("VaultSupplyModule", address(vaultSupplyModule)); + + vaultWithdrawModule = new VaultWithdrawModule(); + moduleRegistry.setModule("VaultWithdrawModule", address(vaultWithdrawModule)); + vm.label(address(flashloanModule), "flashloanModule"); vm.label(address(callbackHandler), "callbackHandler"); vm.label(address(emodeModule), "emodeModule"); @@ -154,20 +208,36 @@ contract TestBase is Test { vm.label(address(dexModule), "dexModule"); vm.label(address(depositManagerCallbackHandler), "depositManagerCallbackHandler"); vm.label(address(withdrawManagerCallbackHandler), "withdrawManagerCallbackHandler"); + vm.label(address(vaultSupplyModule), "vaultSupplyModule"); + vm.label(address(vaultWithdrawModule), "vaultWithdrawModule"); + vm.label(address(merklModule), "merklModule"); } - function _deployAccountant(address vault) internal { - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - - DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = DataTypes - .AaveV3AccountantPluginModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets - }); + function _deployHyperliquidStakeModule() internal { + if (environment.chainId != 999) return; + + wrapModule = new WrapModule(environment.vaultAsset); + moduleRegistry.setModule("WrapModule", address(wrapModule)); + unwrapModule = new UnwrapModule(environment.vaultAsset); + moduleRegistry.setModule("UnwrapModule", address(unwrapModule)); + hyperliquidStakeModule = new HyperliquidStakeModule(overseer_hyperevm); + moduleRegistry.setModule("HyperliquidStakeModule", address(hyperliquidStakeModule)); + kinetiqStakeModule = new KinetiqStakeModule(stakingManager_hyperevm); + moduleRegistry.setModule("KinetiqStakeModule", address(kinetiqStakeModule)); + hyperbeatStakingModule = new HyperbeatStakingModule(stakingCore_hyperevm); + moduleRegistry.setModule("HyperbeatStakingModule", address(hyperbeatStakingModule)); + } + + function _deployAccountant(address vault, address[] memory lendAssets, address[] memory borrowAssets) + internal + returns (UniversalAccountant) + { + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: lendAssets, + borrowAssets: borrowAssets + }); address accountantPlugin = address(new AaveV3AccountantPlugin(accountantPluginInitData)); address[] memory registeredAccountants = new address[](1); @@ -175,9 +245,7 @@ contract TestBase is Test { // deploy accountant DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ - registeredAccountants: registeredAccountants, - performanceFee: uint16(PERFORMANCE_FEE), - vault: address(vault) + registeredAccountants: registeredAccountants, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); address accountantImplementation = address(new UniversalAccountant()); @@ -188,9 +256,10 @@ contract TestBase is Test { ); accountant = UniversalAccountant(address(proxy)); + return accountant; } - function _deployDepositManager(address vault) internal { + function _deployDepositManager(address vault) internal returns (DepositManager) { DepositManager depositManagerImplementation = new DepositManager(); TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(depositManagerImplementation), @@ -199,9 +268,10 @@ contract TestBase is Test { ); depositManager = DepositManager(address(proxy)); + return depositManager; } - function _deployWithdrawManager(address vault) internal { + function _deployWithdrawManager(address vault) internal returns (WithdrawManager) { WithdrawManager withdrawManagerImplementation = new WithdrawManager(); TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(withdrawManagerImplementation), @@ -209,18 +279,19 @@ contract TestBase is Test { abi.encodeWithSelector(WithdrawManager.initialize.selector, vault) ); withdrawManager = WithdrawManager(address(proxy)); + return withdrawManager; } function _deployPreliquidationFallbackHandler(address vault) internal { preliquidationFallbackHandler = new AaveV3PreliquidationFallbackHandler( - AAVE_V3_POOL_ADDRESSES_PROVIDER, + environment.poolAddressesProvider, vault, 2, 8, DataTypes.AaveV3PreliquidationParamsInit({ id: id, - lendReserve: ST_XTZ, - borrowReserve: XTZ, + lendReserve: environment.lendAssets[0], + borrowReserve: environment.borrowAssets[0], preLltv: PRE_LLTV, preCF1: PRE_CF1, preCF2: PRE_CF2, diff --git a/test/core/TestEnv.sol b/test/core/TestEnv.sol new file mode 100644 index 0000000..bb90e59 --- /dev/null +++ b/test/core/TestEnv.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; + +abstract contract TestEnv is Test { + struct TestEnvironment { + uint256 chainId; + string chainName; + address vaultAsset; + uint8 vaultAssetDecimals; + address[] lendAssets; + address[] borrowAssets; + address poolAddressesProvider; + address poolDataProvider; + address priceOracle; + address pool; + address vaultAssetWhale; + address poolConfigurator; + address poolAdmin; + address router; + address stablecoin; + address stablecoinWhale; + address morpho; + uint8 emodeCategory; + address distributor; + } + + // etlk chain + address public constant ST_XTZ = 0x01F07f4d78d47A64F4C3B2b65f513f15Be6E1854; + address public constant WXTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; + address public constant XTZ = 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb; + address public constant LBTC = 0xecAc9C5F704e954931349Da37F60E39f515c11c1; + address public constant WBTC = 0xbFc94CD2B1E55999Cfc7347a9313e88702B83d0F; + address public constant USDC_ETLK = 0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9; + address public constant USDT_ETLK = 0x2C03058C8AFC06713be23e58D2febC8337dbfE6A; + address public constant USDT_ETLK_WHALE = 0x998098A1B2E95e2b8f15360676428EdFd976861f; + address public constant USDC_ETLK_WHALE = 0xd03bfdF9B26DB1e6764724d914d7c3d18106a9Fb; + + // eth mainnet chain + address public constant USDe = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3; + address public constant USDeWhale = 0x4F5923Fc5FD4a93352581b38B7cD26943012DECF; + address public constant sUSDe = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497; + address public constant USDC_ETH = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public constant USDC_ETH_Whale = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c; + address public constant USDT_ETH = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + + // hyperevm + address public constant WHYPE = 0x5555555555555555555555555555555555555555; + address public constant WHYPE_WHALE = 0x008ae222661B6A42e3A097bd7AAC15412829106b; + address public constant ST_HYPE = 0xfFaa4a3D97fE9107Cef8a3F48c069F577Ff76cC1; + address public constant WST_HYPE = 0x94e8396e0869c9F2200760aF0621aFd240E1CF38; + address public constant K_HYPE = 0xfD739d4e423301CE9385c1fb8850539D657C296D; + address public constant BE_HYPE = 0xd8FC8F0b03eBA61F64D08B0bef69d80916E5DdA9; + address public stakingManager_hyperevm = 0x393D0B87Ed38fc779FD9611144aE649BA6082109; + address public stakingCore_hyperevm = 0xCeaD893b162D38e714D82d06a7fe0b0dc3c38E0b; + address public overseer_hyperevm = 0xB96f07367e69e86d6e9C3F29215885104813eeAE; + + // address public constant + uint256 public constant PERFORMANCE_FEE = 2000; // 20% + TestEnvironment[] internal testEnvironments; + uint256 public constant INTEREST_RATE_MODE = 2; // variable rate + + function setUp() public virtual { + // etherlink xtz + testEnvironments.push( + TestEnvironment({ + chainId: 42793, + chainName: "etherlink", + vaultAsset: WXTZ, + vaultAssetDecimals: 18, + lendAssets: _singleAddressArray(ST_XTZ), + borrowAssets: _singleAddressArray(WXTZ), + poolAddressesProvider: 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC, + poolDataProvider: 0x99e8269dDD5c7Af0F1B3973A591b47E8E001BCac, + priceOracle: 0xeCF313dE38aA85EF618D06D1A602bAa917D62525, + pool: 0x3bD16D195786fb2F509f2E2D7F69920262EF114D, + vaultAssetWhale: 0x008ae222661B6A42e3A097bd7AAC15412829106b, + poolConfigurator: 0x30F6880Bb1cF780a49eB4Ef64E64585780AAe060, + poolAdmin: 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f, + router: 0xbfe9C246A5EdB4F021C8910155EC93e7CfDaB7a0, + stablecoin: 0x2C03058C8AFC06713be23e58D2febC8337dbfE6A, + stablecoinWhale: 0x998098A1B2E95e2b8f15360676428EdFd976861f, + morpho: 0x0000000000000000000000000000000000000000, + emodeCategory: 3, + distributor: 0x0000000000000000000000000000000000000000 + }) + ); + + // etherlink btc + testEnvironments.push( + TestEnvironment({ + chainId: 42793, + chainName: "etherlink", + vaultAsset: WBTC, + vaultAssetDecimals: 8, + lendAssets: _singleAddressArray(LBTC), + borrowAssets: _singleAddressArray(WBTC), + poolAddressesProvider: 0x5ccF60c7E10547c5389E9cBFf543E5D0Db9F4feC, + poolDataProvider: 0x99e8269dDD5c7Af0F1B3973A591b47E8E001BCac, + priceOracle: 0xeCF313dE38aA85EF618D06D1A602bAa917D62525, + pool: 0x3bD16D195786fb2F509f2E2D7F69920262EF114D, + vaultAssetWhale: 0xfCA0802cb10b3b134a91e07f03965f63eF4B23eA, + poolConfigurator: 0x30F6880Bb1cF780a49eB4Ef64E64585780AAe060, + poolAdmin: 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f, + router: 0xbfe9C246A5EdB4F021C8910155EC93e7CfDaB7a0, + stablecoin: 0x2C03058C8AFC06713be23e58D2febC8337dbfE6A, + stablecoinWhale: 0x998098A1B2E95e2b8f15360676428EdFd976861f, + morpho: 0x0000000000000000000000000000000000000000, + emodeCategory: 2, + distributor: 0x0000000000000000000000000000000000000000 + }) + ); + + // eth mainnet ethena + testEnvironments.push( + TestEnvironment({ + chainId: 1, + chainName: "mainnet", + vaultAsset: USDe, + vaultAssetDecimals: 18, + lendAssets: _twoAddressArray(USDe, sUSDe), + borrowAssets: _twoAddressArray(USDC_ETH, USDT_ETH), + poolAddressesProvider: 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e, + poolDataProvider: 0x0a16f2FCC0D44FaE41cc54e079281D84A363bECD, + priceOracle: 0x54586bE62E3c3580375aE3723C145253060Ca0C2, + pool: 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2, + vaultAssetWhale: USDeWhale, + poolConfigurator: 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27, + poolAdmin: 0x72B8fD3eb0c08275b8B60F96aAb0C8a50Cb80EcA, + router: 0xE592427A0AEce92De3Edee1F18E0157C05861564, + stablecoin: USDC_ETH, + stablecoinWhale: USDC_ETH_Whale, + morpho: 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb, + emodeCategory: 2, + distributor: 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae + }) + ); + + // hyperevm + testEnvironments.push( + TestEnvironment({ + chainId: 999, + chainName: "hyperevm", + vaultAsset: WHYPE, + vaultAssetDecimals: 18, + lendAssets: _singleAddressArray(ST_HYPE), + borrowAssets: _singleAddressArray(WHYPE), + poolAddressesProvider: 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e, // dummy values as they are not used yet + poolDataProvider: 0x0a16f2FCC0D44FaE41cc54e079281D84A363bECD, + priceOracle: 0x54586bE62E3c3580375aE3723C145253060Ca0C2, + pool: 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2, + vaultAssetWhale: WHYPE_WHALE, + poolConfigurator: 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27, + poolAdmin: 0x72B8fD3eb0c08275b8B60F96aAb0C8a50Cb80EcA, + router: address(0), + stablecoin: USDC_ETH, + stablecoinWhale: USDC_ETH_Whale, + morpho: 0x0000000000000000000000000000000000000000, + emodeCategory: 1, + distributor: 0x0000000000000000000000000000000000000000 + }) + ); + } + + function _singleAddressArray(address a) internal pure returns (address[] memory) { + address[] memory array = new address[](1); + array[0] = a; + return array; + } + + function _twoAddressArray(address a, address b) internal pure returns (address[] memory) { + address[] memory array = new address[](2); + array[0] = a; + array[1] = b; + return array; + } +} diff --git a/test/core/integration/AavePreliquidation.t.sol b/test/core/integration/AavePreliquidation.t.sol index 71cec45..69aa2f9 100644 --- a/test/core/integration/AavePreliquidation.t.sol +++ b/test/core/integration/AavePreliquidation.t.sol @@ -5,8 +5,9 @@ pragma solidity ^0.8.13; import {IntegrationBase} from "./IntegrationBase.sol"; import {console} from "forge-std/console.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; -import {AaveV3PreliquidationFallbackHandler} from - "../../../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; +import { + AaveV3PreliquidationFallbackHandler +} from "../../../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; import {console} from "forge-std/console.sol"; import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -17,7 +18,8 @@ contract AavePreliquidationTest is IntegrationBase { function setUp() public override { super.setUp(); - DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: 3}); + DataTypes.AaveV3EmodeParams memory params = + DataTypes.AaveV3EmodeParams({emodeCategory: environment.emodeCategory}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -54,8 +56,8 @@ contract AavePreliquidationTest is IntegrationBase { DataTypes.AaveV3PreliquidationParams memory params = AaveV3PreliquidationFallbackHandler(address(superloop)) .preliquidationParams(id, DataTypes.CallType.DELEGATECALL); - assertEq(params.lendReserve, ST_XTZ); - assertEq(params.borrowReserve, XTZ); + assertEq(params.lendReserve, environment.lendAssets[0]); + assertEq(params.borrowReserve, environment.borrowAssets[0]); assertEq(params.Lltv, LLTV); assertEq(params.preLltv, PRE_LLTV); assertEq(params.preCF1, PRE_CF1); @@ -67,34 +69,37 @@ contract AavePreliquidationTest is IntegrationBase { function test_aavePreliquidation() public { _createPartialDepositWithResolution(true); - (,, uint256 currentVariableDebt,,,,,,) = - IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER).getUserReserveData(XTZ, address(superloop)); - (uint256 currentATokenBalance,,,,,,,,) = - IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER).getUserReserveData(ST_XTZ, address(superloop)); + (,, uint256 currentVariableDebt,,,,,,) = IPoolDataProvider(environment.poolAddressesProvider) + .getUserReserveData(environment.borrowAssets[0], address(superloop)); + (uint256 currentATokenBalance,,,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.lendAssets[0], address(superloop)); address liquidator = makeAddr("liquidator"); - deal(XTZ, liquidator, 1000 * XTZ_SCALE); + deal(environment.borrowAssets[0], liquidator, 1000 * environment.vaultAssetDecimals); - uint256 userBalanceXTZBefore = IERC20(XTZ).balanceOf(liquidator); - uint256 userBalanceSTXTZBefore = IERC20(ST_XTZ).balanceOf(liquidator); + uint256 userBalanceXTZBefore = IERC20(environment.borrowAssets[0]).balanceOf(liquidator); + uint256 userBalanceSTXTZBefore = IERC20(environment.lendAssets[0]).balanceOf(liquidator); vm.startPrank(liquidator); - IERC20(XTZ).approve(address(superloop), 1000 * XTZ_SCALE); + IERC20(environment.borrowAssets[0]).approve(address(superloop), 1000 * environment.vaultAssetDecimals); - AaveV3PreliquidationFallbackHandler(address(superloop)).preliquidate( - id, - DataTypes.CallType.DELEGATECALL, - DataTypes.AaveV3ExecutePreliquidationParams({user: address(superloop), debtToCover: currentVariableDebt}) - ); + AaveV3PreliquidationFallbackHandler(address(superloop)) + .preliquidate( + id, + DataTypes.CallType.DELEGATECALL, + DataTypes.AaveV3ExecutePreliquidationParams({ + user: address(superloop), debtToCover: currentVariableDebt + }) + ); vm.stopPrank(); - (,, uint256 currentVariableDebtAfter,,,,,,) = - IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER).getUserReserveData(XTZ, address(superloop)); - (uint256 currentATokenBalanceAfter,,,,,,,,) = - IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER).getUserReserveData(ST_XTZ, address(superloop)); + (,, uint256 currentVariableDebtAfter,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.borrowAssets[0], address(superloop)); + (uint256 currentATokenBalanceAfter,,,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.lendAssets[0], address(superloop)); - uint256 userBalanceXTZAfter = IERC20(XTZ).balanceOf(liquidator); - uint256 userBalanceSTXTZAfter = IERC20(ST_XTZ).balanceOf(liquidator); + uint256 userBalanceXTZAfter = IERC20(environment.borrowAssets[0]).balanceOf(liquidator); + uint256 userBalanceSTXTZAfter = IERC20(environment.lendAssets[0]).balanceOf(liquidator); uint256 debtRepaid = currentVariableDebt - currentVariableDebtAfter; uint256 collateralWithdrawn = currentATokenBalance - currentATokenBalanceAfter; @@ -104,9 +109,9 @@ contract AavePreliquidationTest is IntegrationBase { assertApproxEqRel(debtRepaid, userBalanceXTZDiff, 1e18); assertApproxEqRel(collateralWithdrawn, userBalanceSTXTZDiff, 1e18); - IAaveOracle oracle = IAaveOracle(IPoolAddressesProvider(AAVE_V3_POOL_ADDRESSES_PROVIDER).getPriceOracle()); - uint256 stXtzPrice = oracle.getAssetPrice(ST_XTZ); - uint256 xtzPrice = oracle.getAssetPrice(XTZ); + IAaveOracle oracle = IAaveOracle(IPoolAddressesProvider(environment.poolAddressesProvider).getPriceOracle()); + uint256 stXtzPrice = oracle.getAssetPrice(environment.lendAssets[0]); + uint256 xtzPrice = oracle.getAssetPrice(environment.borrowAssets[0]); uint256 debtRepaidUsd = (userBalanceXTZDiff * xtzPrice) / (10 ** 18); uint256 collateralWithdrawnUsd = (userBalanceSTXTZDiff * stXtzPrice) / (10 ** 6); diff --git a/test/core/integration/DepositManager.t.sol b/test/core/integration/DepositManager.t.sol index 4340426..a8c8623 100644 --- a/test/core/integration/DepositManager.t.sol +++ b/test/core/integration/DepositManager.t.sol @@ -8,6 +8,7 @@ import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {DepositManager} from "../../../src/core/DepositManager/DepositManager.sol"; import {console} from "forge-std/Test.sol"; import {Errors} from "../../../src/common/Errors.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; contract DepositManagerTest is IntegrationBase { bool depositAll = false; @@ -16,7 +17,8 @@ contract DepositManagerTest is IntegrationBase { function setUp() public override { super.setUp(); - DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: 3}); + DataTypes.AaveV3EmodeParams memory params = + DataTypes.AaveV3EmodeParams({emodeCategory: environment.emodeCategory}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -31,7 +33,7 @@ contract DepositManagerTest is IntegrationBase { function test_initialize() public view { assertEq(depositManager.vault(), address(superloop)); - assertEq(depositManager.asset(), XTZ); + assertEq(depositManager.asset(), environment.vaultAsset); assertEq(depositManager.nextDepositRequestId(), 1); } @@ -46,27 +48,40 @@ contract DepositManagerTest is IntegrationBase { function test_resolveDepositRequestResolution() public { uint256 exchangeRateBefore = superloop.convertToAssets(ONE_SHARE); - uint256 depositAmount = 100 * XTZ_SCALE; + uint256 depositAmount = 100 * 10 ** environment.vaultAssetDecimals; _makeDepositRequest(depositAmount, user1, true); // build the operate call - uint256 supplyAmount = 150 * STXTZ_SCALE; - uint256 borrowAmount = 60 * XTZ_SCALE; + uint256 supplyAmount = 150 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(); // USDe supply + uint256 borrowAmount = 60 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // USDC borrow + uint256 flashLoanAmount = supplyAmount - depositAmount; uint256 swapAmount = borrowAmount + depositAmount; uint256 supplyAmountWithPremium = supplyAmount + (supplyAmount * 1) / 10000; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _supplyCall(ST_XTZ, supplyAmount); - moduleExecutionData[1] = _borrowCall(XTZ, borrowAmount); - - moduleExecutionData[2] = - _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); + moduleExecutionData[0] = _supplyCall(environment.lendAssets[0], supplyAmount); + moduleExecutionData[1] = _borrowCall(environment.borrowAssets[0], borrowAmount); + + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmount, + flashLoanAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(ST_XTZ, supplyAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.lendAssets[0], supplyAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); - finalExecutionData[0] = _resolveDepositRequestsCall(XTZ, depositAmount, abi.encode(intermediateExecutionData)); + finalExecutionData[0] = + _resolveDepositRequestsCall(environment.vaultAsset, depositAmount, abi.encode(intermediateExecutionData)); vm.prank(admin); superloop.operate(finalExecutionData); @@ -77,7 +92,7 @@ contract DepositManagerTest is IntegrationBase { assertEq(user1Shares, depositRequest1.sharesMinted); // deposit maanger should not have any xtz now, resolution ptr must have increased, pendingDepsotAmout should be 0 - assertEq(IERC20(XTZ).balanceOf(address(depositManager)), 0); + assertEq(IERC20(environment.vaultAsset).balanceOf(address(depositManager)), 0); assertEq(depositManager.totalPendingDeposits(), 0); assertEq(depositManager.resolutionIdPointer(), 2); @@ -94,21 +109,22 @@ contract DepositManagerTest is IntegrationBase { _createPartialDepositWithResolution(depositAll); // i should be able to do an instant deposit of 0.001 xtz + uint256 scale = 10 ** environment.vaultAssetDecimals; uint256 user1SharesBalanceBefore = superloop.balanceOf(user1); - deal(XTZ, user1, XTZ_SCALE); + deal(environment.vaultAsset, user1, scale); vm.startPrank(user1); - IERC20(XTZ).approve(address(superloop), XTZ_SCALE); - superloop.deposit(XTZ_SCALE / 1000, user1); + IERC20(environment.vaultAsset).approve(address(superloop), scale); + superloop.deposit(scale / 1000, user1); uint256 user1SharesBalanceAfter = superloop.balanceOf(user1); assertTrue(user1SharesBalanceAfter > user1SharesBalanceBefore); // i should not be able to do an instant deposit of 100 xtz due to cash reserve - deal(XTZ, user1, 100 * XTZ_SCALE); + deal(environment.vaultAsset, user1, 100 * scale); vm.startPrank(user1); - IERC20(XTZ).approve(address(superloop), 100 * XTZ_SCALE); + IERC20(environment.vaultAsset).approve(address(superloop), 100 * scale); vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); - superloop.deposit(100 * XTZ_SCALE, user1); + superloop.deposit(100 * scale, user1); vm.stopPrank(); } @@ -116,26 +132,43 @@ contract DepositManagerTest is IntegrationBase { _createPartialDepositWithResolution(depositAll); // 300 worth of depostits, 150 pending // try to operate again + uint256 vaultTokenScale = 10 ** environment.vaultAssetDecimals; + uint256 lendTokenScale = 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(); + uint256 borrowTokenScale = 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); - uint256 depositAmount_secondBatch = 90 * XTZ_SCALE; - uint256 supplyAmount = 180 * STXTZ_SCALE; - uint256 borrowAmount = 110 * XTZ_SCALE; + uint256 depositAmount_secondBatch = 90 * vaultTokenScale; + uint256 supplyAmount = 180 * lendTokenScale; + uint256 borrowAmount = 110 * borrowTokenScale; uint256 swapAmount = borrowAmount + depositAmount_secondBatch; uint256 supplyAmountWithPremium = supplyAmount + (supplyAmount * 1) / 10000; - DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _supplyCall(ST_XTZ, supplyAmount); - moduleExecutionData[1] = _borrowCall(XTZ, borrowAmount); + uint256 flashLoanAmount = supplyAmount - depositAmount_secondBatch; - moduleExecutionData[2] = - _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); + moduleExecutionData[0] = _supplyCall(environment.lendAssets[0], supplyAmount); + moduleExecutionData[1] = _borrowCall(environment.borrowAssets[0], borrowAmount); + + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmount, + flashLoanAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(ST_XTZ, supplyAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.lendAssets[0], supplyAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); - finalExecutionData[0] = - _resolveDepositRequestsCall(XTZ, depositAmount_secondBatch, abi.encode(intermediateExecutionData)); + finalExecutionData[0] = _resolveDepositRequestsCall( + environment.vaultAsset, depositAmount_secondBatch, abi.encode(intermediateExecutionData) + ); uint256 exchangeRateBefore = superloop.convertToAssets(ONE_SHARE); DataTypes.DepositRequestData memory depositRequest2_before = depositManager.depositRequest(2); @@ -153,7 +186,7 @@ contract DepositManagerTest is IntegrationBase { assertTrue(superloop.balanceOf(user3) > 0); // pending deposits should be 150 xtz - assertEq(depositManager.totalPendingDeposits(), 60 * XTZ_SCALE); + assertEq(depositManager.totalPendingDeposits(), 60 * vaultTokenScale); // resolution id pointer should be 2 assertEq(depositManager.resolutionIdPointer(), 3); @@ -161,32 +194,50 @@ contract DepositManagerTest is IntegrationBase { // deposit request 2 should be partially processed DataTypes.DepositRequestData memory depositRequest3 = depositManager.depositRequest(3); assertEq(uint256(depositRequest3.state), uint256(DataTypes.RequestProcessingState.PARTIALLY_PROCESSED)); - assertEq(depositRequest3.amountProcessed, 40 * XTZ_SCALE); + assertEq(depositRequest3.amountProcessed, 40 * vaultTokenScale); // deposit another batch, with 60 xtz resolving a request partially - uint256 depositAmount_thirdBatch = 60 * XTZ_SCALE; - uint256 supplyAmountSecondBatch = 120 * STXTZ_SCALE; - uint256 borrowAmountSecondBatch = 75 * XTZ_SCALE; + uint256 depositAmount_thirdBatch = 60 * vaultTokenScale; + uint256 supplyAmountSecondBatch = 120 * lendTokenScale; + uint256 borrowAmountSecondBatch = 75 * borrowTokenScale; uint256 swapAmountSecondBatch = borrowAmountSecondBatch + depositAmount_thirdBatch; uint256 supplyAmountWithPremiumSecondBatch = supplyAmountSecondBatch + (supplyAmountSecondBatch * 1) / 10000; DataTypes.ModuleExecutionData[] memory moduleExecutionDataSecondBatch = new DataTypes.ModuleExecutionData[](3); - moduleExecutionDataSecondBatch[0] = _supplyCall(ST_XTZ, supplyAmountSecondBatch); - moduleExecutionDataSecondBatch[1] = _borrowCall(XTZ, borrowAmountSecondBatch); - - moduleExecutionDataSecondBatch[2] = _swapCallExactOutCurve( - XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmountSecondBatch, supplyAmountWithPremiumSecondBatch, XTZ_STXTZ_SWAP - ); + moduleExecutionDataSecondBatch[0] = _supplyCall(environment.lendAssets[0], supplyAmountSecondBatch); + moduleExecutionDataSecondBatch[1] = _borrowCall(environment.borrowAssets[0], borrowAmountSecondBatch); + + uint256 flashLoanAmountSecondBatch = supplyAmountSecondBatch - depositAmount_thirdBatch; + + moduleExecutionDataSecondBatch[2] = USE_MORPHO + ? _swapCallExactOut( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmountSecondBatch, + flashLoanAmountSecondBatch, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmountSecondBatch, supplyAmountWithPremiumSecondBatch, XTZ_STXTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionDataSecondBatch = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionDataSecondBatch[0] = - _flashloanCall(ST_XTZ, supplyAmountSecondBatch, abi.encode(moduleExecutionDataSecondBatch)); + intermediateExecutionDataSecondBatch[0] = USE_MORPHO + ? _morphoFlashloanCall( + environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionDataSecondBatch) + ) + : _flashloanCall( + environment.lendAssets[0], supplyAmountSecondBatch, abi.encode(moduleExecutionDataSecondBatch) + ); DataTypes.ModuleExecutionData[] memory finalExecutionDataSecondBatch = new DataTypes.ModuleExecutionData[](1); - finalExecutionDataSecondBatch[0] = - _resolveDepositRequestsCall(XTZ, depositAmount_thirdBatch, abi.encode(intermediateExecutionDataSecondBatch)); + finalExecutionDataSecondBatch[0] = _resolveDepositRequestsCall( + environment.lendAssets[0], depositAmount_thirdBatch, abi.encode(intermediateExecutionDataSecondBatch) + ); uint256 exchangeRateBeforeSecondBatch = superloop.convertToAssets(ONE_SHARE); @@ -208,6 +259,10 @@ contract DepositManagerTest is IntegrationBase { } function test_resolveDepositRequestWithCancellation() public { + uint256 vaultTokenScale = 10 ** IERC20Metadata(environment.vaultAsset).decimals(); + uint256 lendTokenScale = 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(); + uint256 borrowTokenScale = 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); + _createPartialDepositWithResolution(depositAll); // 300 worth of depostits, 150 pending // user1 should not be able to cancel deposit request 1 because it's already processed @@ -217,41 +272,61 @@ contract DepositManagerTest is IntegrationBase { vm.stopPrank(); // cancel deposit request 2 => it should be partially cancelled and user 2 should get back the remaining amount - uint256 user2BalanceBefore = IERC20(XTZ).balanceOf(user2); + uint256 user2BalanceBefore = IERC20(environment.vaultAsset).balanceOf(user2); vm.startPrank(user2); depositManager.cancelDepositRequest(2); vm.stopPrank(); // user 2 should get back the remaining amount - uint256 user2BalanceAfter = IERC20(XTZ).balanceOf(user2); - assertEq(user2BalanceAfter - user2BalanceBefore, 50 * XTZ_SCALE); + uint256 user2BalanceAfter = IERC20(environment.vaultAsset).balanceOf(user2); + assertEq(user2BalanceAfter - user2BalanceBefore, 50 * vaultTokenScale); // deposit request 2 should be partially cancelled DataTypes.DepositRequestData memory depositRequest = depositManager.depositRequest(2); assertEq(uint256(depositRequest.state), uint256(DataTypes.RequestProcessingState.PARTIALLY_CANCELLED)); - assertEq(depositRequest.amountProcessed, 50 * XTZ_SCALE); + assertEq(depositRequest.amountProcessed, 50 * vaultTokenScale); // pending deposits should be 100 xtz - assertEq(depositManager.totalPendingDeposits(), 100 * XTZ_SCALE); + assertEq(depositManager.totalPendingDeposits(), 100 * vaultTokenScale); // try to operate again with 75 xtz => request 3 should be partially processed - uint256 depositAmount_secondBatch = 75 * XTZ_SCALE; - uint256 supplyAmount = 150 * STXTZ_SCALE; - uint256 borrowAmount = 85 * XTZ_SCALE; + uint256 depositAmount_secondBatch = 75 * vaultTokenScale; + uint256 supplyAmount = 150 * lendTokenScale; + uint256 borrowAmount = 85 * borrowTokenScale; uint256 swapAmount = borrowAmount + depositAmount_secondBatch; uint256 supplyAmountWithPremium = supplyAmount + (supplyAmount * 1) / 10000; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _supplyCall(ST_XTZ, supplyAmount); - moduleExecutionData[1] = _borrowCall(XTZ, borrowAmount); - - moduleExecutionData[2] = - _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); + moduleExecutionData[0] = _supplyCall(environment.lendAssets[0], supplyAmount); + moduleExecutionData[1] = _borrowCall(environment.borrowAssets[0], borrowAmount); + uint256 flashLoanAmount = supplyAmount - depositAmount_secondBatch; + + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmount, + flashLoanAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + environment.borrowAssets[0], + environment.lendAssets[0], + XTZ_STXTZ_POOL, + swapAmount, + supplyAmountWithPremium, + XTZ_STXTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(ST_XTZ, supplyAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.lendAssets[0], supplyAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); - finalExecutionData[0] = - _resolveDepositRequestsCall(XTZ, depositAmount_secondBatch, abi.encode(intermediateExecutionData)); + finalExecutionData[0] = _resolveDepositRequestsCall( + environment.vaultAsset, depositAmount_secondBatch, abi.encode(intermediateExecutionData) + ); uint256 exchangeRateBefore = superloop.convertToAssets(ONE_SHARE); @@ -278,13 +353,13 @@ contract DepositManagerTest is IntegrationBase { //////////////// Make sure new deposits are working as expected //////////////// // user1 and user2 should be able to request new deposits - _makeDepositRequest(100 * XTZ_SCALE, user1, true); - _makeDepositRequest(100 * XTZ_SCALE, user2, true); + _makeDepositRequest(100 * vaultTokenScale, user1, true); + _makeDepositRequest(100 * vaultTokenScale, user2, true); // user3 should not be able to request new deposits because one request is still under process vm.expectRevert(bytes(Errors.DEPOSIT_REQUEST_ACTIVE)); vm.startPrank(user3); - depositManager.requestDeposit(100 * XTZ_SCALE, address(0)); + depositManager.requestDeposit(100 * vaultTokenScale, address(0)); vm.stopPrank(); } } diff --git a/test/core/integration/EthenaLoop.t.sol b/test/core/integration/EthenaLoop.t.sol new file mode 100644 index 0000000..b1ab8ef --- /dev/null +++ b/test/core/integration/EthenaLoop.t.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {IntegrationBase} from "./IntegrationBase.sol"; +import {DataTypes} from "../../../src/common/DataTypes.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {console} from "forge-std/console.sol"; +import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; +import {IPoolAddressesProvider} from "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol"; +import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; + +contract EthenaLoopTest is IntegrationBase { + DataTypes.WithdrawRequestType requestType = DataTypes.WithdrawRequestType.INSTANT; + + function setUp() public override { + super.setUp(); + + DataTypes.AaveV3EmodeParams memory params = + DataTypes.AaveV3EmodeParams({emodeCategory: environment.emodeCategory}); + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(emodeModule), + data: abi.encodeWithSelector(emodeModule.execute.selector, params) + }); + + vm.prank(admin); + superloop.operate(moduleExecutionData); + } + + function test_initialize() public view { + assertEq(depositManager.vault(), address(superloop)); + assertEq(depositManager.asset(), environment.vaultAsset); + assertEq(depositManager.nextDepositRequestId(), 1); + assertEq(withdrawManager.vault(), address(superloop)); + assertEq(withdrawManager.asset(), environment.vaultAsset); + assertEq(withdrawManager.nextWithdrawRequestId(requestType), 1); + assertEq(withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.INSTANT), 1); + assertEq(withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.PRIORITY), 1); + assertEq(withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.DEFERRED), 1); + } + + function test_depositAndFullLoop() public { + uint256 vaultAssetScale = 10 ** environment.vaultAssetDecimals; + uint256 borrowTokenScale = 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); + + uint256 depositAmountUnscaled = 100; + uint256 depositAmount = depositAmountUnscaled * vaultAssetScale; + uint256 leverage = 5; + + // deposit 100 USDe + deal(environment.vaultAsset, user1, depositAmount); + vm.startPrank(user1); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); + depositManager.requestDeposit(depositAmount, address(0)); + vm.stopPrank(); + + uint256 borrowAmount = depositAmountUnscaled * borrowTokenScale * (leverage - 1) + 1 * borrowTokenScale; // +1 to cover the slippage if any + uint256 flashLoanAmount = depositAmount * (leverage - 1); + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](5); + // lend, borrow and swap to USDe + moduleExecutionData[0] = _stakeCall(environment.lendAssets[1], (depositAmount * leverage) / 2); + moduleExecutionData[1] = _supplyCall(environment.lendAssets[0], type(uint256).max); + moduleExecutionData[2] = _supplyCall(environment.lendAssets[1], type(uint256).max); + moduleExecutionData[3] = _borrowCall(environment.borrowAssets[0], borrowAmount); + moduleExecutionData[4] = _swapCallExactIn( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmount, + flashLoanAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ); + + DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); + intermediateExecutionData[0] = + _morphoFlashloanCall(environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionData)); + + DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); + finalExecutionData[0] = + _resolveDepositRequestsCall(environment.vaultAsset, depositAmount, abi.encode(intermediateExecutionData)); + + vm.prank(admin); + superloop.operate(finalExecutionData); + + uint256 totalAssets = superloop.totalAssets(); + uint256 borrowBalance = IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)); + uint256 lendBalance = IERC20(environment.lendAssets[0]).balanceOf(address(superloop)); + uint256 stakedBalance = IERC20(environment.lendAssets[1]).balanceOf(address(superloop)); + + assertApproxEqAbs(totalAssets, depositAmount, 1 * vaultAssetScale); + assertEq(borrowBalance, 0); + + console.log("totalAssets", totalAssets); + console.log("lendBalance", lendBalance); + console.log("stakedBalance", stakedBalance); + + (uint256 currentSupply,,,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.lendAssets[0], address(superloop)); + (uint256 currentStakedSupply,,,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.lendAssets[1], address(superloop)); + + (,, uint256 currentBorrowBalance,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.borrowAssets[0], address(superloop)); + + console.log("currentSupply", currentSupply); + console.log("currentStakedSupply", currentStakedSupply); + console.log("currentBorrowBalance", currentBorrowBalance); + } +} diff --git a/test/core/integration/InstantWithdraw.t.sol b/test/core/integration/InstantWithdraw.t.sol index bb49f6c..5d18335 100644 --- a/test/core/integration/InstantWithdraw.t.sol +++ b/test/core/integration/InstantWithdraw.t.sol @@ -17,16 +17,18 @@ contract InstantWithdrawTest is IntegrationBase { function test_instantWithdraw() public { _seed(); + uint256 vaultScale = 10 ** environment.vaultAssetDecimals; + uint256 userSharesBalanceBefore = superloop.balanceOf(admin); - uint256 userTokenBalanceBefore = IERC20(XTZ).balanceOf(admin); + uint256 userTokenBalanceBefore = IERC20(environment.vaultAsset).balanceOf(admin); uint256 treasurySharesBalanceBefore = superloop.balanceOf(treasury); vm.startPrank(admin); - uint256 instantWithdrawAmount = 10 * XTZ_SCALE; + uint256 instantWithdrawAmount = 10 * vaultScale; superloop.withdraw(instantWithdrawAmount, admin, admin); uint256 userSharesBalanceAfter = superloop.balanceOf(admin); - uint256 userTokenBalanceAfter = IERC20(XTZ).balanceOf(admin); + uint256 userTokenBalanceAfter = IERC20(environment.vaultAsset).balanceOf(admin); uint256 treasurySharesBalanceAfter = superloop.balanceOf(treasury); uint256 instantWithdrawFee = (instantWithdrawAmount * INSTANT_WITHDRAW_FEE) / 10_000; @@ -39,7 +41,7 @@ contract InstantWithdrawTest is IntegrationBase { _seed(); uint256 userSharesBalanceBefore = superloop.balanceOf(admin); - uint256 userTokenBalanceBefore = IERC20(XTZ).balanceOf(admin); + uint256 userTokenBalanceBefore = IERC20(environment.vaultAsset).balanceOf(admin); uint256 treasurySharesBalanceBefore = superloop.balanceOf(treasury); vm.startPrank(admin); @@ -47,7 +49,7 @@ contract InstantWithdrawTest is IntegrationBase { superloop.redeem(instantRedeemAmount, admin, admin); uint256 userSharesBalanceAfter = superloop.balanceOf(admin); - uint256 userTokenBalanceAfter = IERC20(XTZ).balanceOf(admin); + uint256 userTokenBalanceAfter = IERC20(environment.vaultAsset).balanceOf(admin); uint256 treasurySharesBalanceAfter = superloop.balanceOf(treasury); uint256 instantRedeemFee = (instantRedeemAmount * INSTANT_WITHDRAW_FEE) / 10_000; @@ -57,11 +59,12 @@ contract InstantWithdrawTest is IntegrationBase { } function _seed() internal { - uint256 seedAmount = 100 * XTZ_SCALE; - deal(XTZ, admin, seedAmount); + uint256 vaultScale = 10 ** environment.vaultAssetDecimals; + uint256 seedAmount = 100 * vaultScale; + deal(environment.vaultAsset, admin, seedAmount); vm.startPrank(admin); - IERC20(XTZ).approve(address(superloop), seedAmount); + IERC20(environment.vaultAsset).approve(address(superloop), seedAmount); superloop.seed(seedAmount); vm.stopPrank(); diff --git a/test/core/integration/IntegrationBase.sol b/test/core/integration/IntegrationBase.sol index 0dc4aa0..be2edda 100644 --- a/test/core/integration/IntegrationBase.sol +++ b/test/core/integration/IntegrationBase.sol @@ -5,8 +5,9 @@ pragma solidity ^0.8.13; import {TestBase} from "../TestBase.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -14,6 +15,9 @@ import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigur import {IRouter} from "../../../src/mock/MockIRouter.sol"; import {ICurvePool} from "../../../src/mock/ICurvePool.sol"; import {console} from "forge-std/console.sol"; +import {IMorphoFlashLoanCallback} from "morpho-blue/interfaces/IMorphoCallbacks.sol"; +import {IUniPool} from "../../../src/mock/IUniPool.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; abstract contract IntegrationBase is TestBase { struct CURVE_IJ { @@ -30,6 +34,10 @@ abstract contract IntegrationBase is TestBase { uint24 public constant XTZ_STXTZ_POOL_FEE = 100; // 0.01% address public constant XTZ_STXTZ_POOL = 0x74d80eE400D3026FDd2520265cC98300710b25D4; + uint24 public constant USDC_USDE_POOL_FEE = 100; // 0.01% + address public constant USDE_USDC_POOL = 0xE6D7EbB9f1a9519dc06D557e03C522d53520e76A; + uint160 constant MIN_SQRT_RATIO = 4295128739; + uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; uint256 public constant ONE_SHARE = 10 ** 20; @@ -41,6 +49,9 @@ abstract contract IntegrationBase is TestBase { CURVE_IJ public XTZ_STXTZ_SWAP; CURVE_IJ public STXTZ_XTZ_SWAP; + bool public USDC_USDE_SWAP = false; + bool public USDE_USDC_SWAP = true; + function setUp() public virtual override { super.setUp(); @@ -50,7 +61,7 @@ abstract contract IntegrationBase is TestBase { vm.startPrank(admin); _deployModules(); - address[] memory modules = new address[](9); + address[] memory modules = new address[](12); modules[0] = address(dexModule); modules[1] = address(flashloanModule); modules[2] = address(callbackHandler); @@ -60,12 +71,15 @@ abstract contract IntegrationBase is TestBase { modules[6] = address(borrowModule); modules[7] = address(repayModule); modules[8] = address(depositManagerCallbackHandler); + modules[9] = address(morphoFlashloanModule); + modules[10] = address(morphoCallbackHandler); + modules[11] = address(vaultSupplyModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: INSTANT_WITHDRAW_FEE, superloopModuleRegistry: address(moduleRegistry), @@ -88,17 +102,22 @@ abstract contract IntegrationBase is TestBase { ); superloop = Superloop(payable(address(proxy))); - _deployAccountant(address(superloop)); - _deployWithdrawManager(address(superloop)); - _deployDepositManager(address(superloop)); + accountant = _deployAccountant(address(superloop), environment.lendAssets, environment.borrowAssets); + + withdrawManager = _deployWithdrawManager(address(superloop)); + + depositManager = _deployDepositManager(address(superloop)); - bytes32 key = keccak256(abi.encodePacked(POOL, IFlashLoanSimpleReceiver.executeOperation.selector)); + bytes32 key = keccak256(abi.encodePacked(environment.pool, IFlashLoanSimpleReceiver.executeOperation.selector)); + bytes32 morphoKey = + keccak256(abi.encodePacked(environment.morpho, IMorphoFlashLoanCallback.onMorphoFlashLoan.selector)); bytes32 depositKey = keccak256(abi.encodePacked(address(depositManager), depositManagerCallbackHandler.executeDeposit.selector)); bytes32 withdrawKey = keccak256( abi.encodePacked(address(withdrawManager), withdrawManagerCallbackHandler.executeWithdraw.selector) ); superloop.setCallbackHandler(key, address(callbackHandler)); + superloop.setCallbackHandler(morphoKey, address(morphoCallbackHandler)); superloop.setCallbackHandler(depositKey, address(depositManagerCallbackHandler)); superloop.setCallbackHandler(withdrawKey, address(withdrawManagerCallbackHandler)); @@ -125,16 +144,9 @@ abstract contract IntegrationBase is TestBase { vm.label(user2, "user2"); vm.label(user3, "user3"); - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(user1, 100 * 10 ** 18); - IERC20(XTZ).transfer(user2, 100 * 10 ** 18); - IERC20(XTZ).transfer(user3, 100 * 10 ** 18); - vm.stopPrank(); - - vm.startPrank(POOL_ADMIN); - IPoolConfigurator(POOL_CONFIGURATOR).setReserveFlashLoaning(ST_XTZ, true); - IPoolConfigurator(POOL_CONFIGURATOR).setSupplyCap(ST_XTZ, 10000000); - vm.stopPrank(); + deal(environment.vaultAsset, user1, 100 * 10 ** environment.vaultAssetDecimals); + deal(environment.vaultAsset, user2, 100 * 10 ** environment.vaultAssetDecimals); + deal(environment.vaultAsset, user3, 100 * 10 ** environment.vaultAssetDecimals); } function _resolveDepositRequestsCall(address asset, uint256 amount, bytes memory data) @@ -157,9 +169,7 @@ abstract contract IntegrationBase is TestBase { returns (DataTypes.ModuleExecutionData memory) { DataTypes.ResolveWithdrawRequestsData memory resolveWithdrawRequestsData = DataTypes.ResolveWithdrawRequestsData({ - shares: shares, - requestType: requestType, - callbackExecutionData: data + shares: shares, requestType: requestType, callbackExecutionData: data }); return DataTypes.ModuleExecutionData({ executionType: DataTypes.CallType.CALL, @@ -174,10 +184,7 @@ abstract contract IntegrationBase is TestBase { returns (DataTypes.ModuleExecutionData memory) { DataTypes.AaveV3FlashloanParams memory flashloanParams = DataTypes.AaveV3FlashloanParams({ - asset: asset, - amount: amount, - referralCode: 0, - callbackExecutionData: data + asset: asset, amount: amount, referralCode: 0, callbackExecutionData: data }); return DataTypes.ModuleExecutionData({ executionType: DataTypes.CallType.DELEGATECALL, @@ -186,6 +193,20 @@ abstract contract IntegrationBase is TestBase { }); } + function _morphoFlashloanCall(address asset, uint256 amount, bytes memory data) + internal + view + returns (DataTypes.ModuleExecutionData memory) + { + DataTypes.MorphoFlashloanParams memory flashloanParams = + DataTypes.MorphoFlashloanParams({asset: asset, amount: amount, callbackExecutionData: data}); + return DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(morphoFlashloanModule), + data: abi.encodeWithSelector(morphoFlashloanModule.execute.selector, flashloanParams) + }); + } + function _supplyCall(address asset, uint256 amount) internal view returns (DataTypes.ModuleExecutionData memory) { DataTypes.AaveV3ActionParams memory supplyParams = DataTypes.AaveV3ActionParams({asset: asset, amount: amount}); return DataTypes.ModuleExecutionData({ @@ -210,12 +231,12 @@ abstract contract IntegrationBase is TestBase { uint256 swapAmount, uint256 amountOut, address router, - uint24 fee + uint24 fee, + uint256 deadline ) internal view returns (DataTypes.ModuleExecutionData memory) { DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](2); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: tokenIn, - data: abi.encodeWithSelector(IERC20.approve.selector, router, swapAmount) + target: tokenIn, data: abi.encodeWithSelector(IERC20.approve.selector, router, swapAmount) }); swapParamsData[1] = DataTypes.ExecuteSwapParamsData({ target: router, @@ -228,7 +249,8 @@ abstract contract IntegrationBase is TestBase { recipient: address(superloop), amountOut: amountOut, amountInMaximum: swapAmount, - sqrtPriceLimitX96: 0 + sqrtPriceLimitX96: 0, + deadline: deadline }) ) }); @@ -254,12 +276,12 @@ abstract contract IntegrationBase is TestBase { uint256 withdrawAmount, uint256 amountOut, address router, - uint24 fee + uint24 fee, + uint256 deadline ) internal view returns (DataTypes.ModuleExecutionData memory) { DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](2); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: tokenIn, - data: abi.encodeWithSelector(IERC20.approve.selector, router, withdrawAmount) + target: tokenIn, data: abi.encodeWithSelector(IERC20.approve.selector, router, withdrawAmount) }); swapParamsData[1] = DataTypes.ExecuteSwapParamsData({ target: router, @@ -272,7 +294,8 @@ abstract contract IntegrationBase is TestBase { recipient: address(superloop), amountIn: withdrawAmount, amountOutMinimum: amountOut, - sqrtPriceLimitX96: 0 + sqrtPriceLimitX96: 0, + deadline: deadline }) ) }); @@ -303,8 +326,7 @@ abstract contract IntegrationBase is TestBase { ) internal view returns (DataTypes.ModuleExecutionData memory) { DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](2); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: tokenIn, - data: abi.encodeWithSelector(IERC20.approve.selector, pool, amountIn) + target: tokenIn, data: abi.encodeWithSelector(IERC20.approve.selector, pool, amountIn) }); swapParamsData[1] = DataTypes.ExecuteSwapParamsData({ target: pool, @@ -336,11 +358,7 @@ abstract contract IntegrationBase is TestBase { }); } - function _withdrawCall(address asset, uint256 amount) - internal - view - returns (DataTypes.ModuleExecutionData memory) - { + function _withdrawCall(address asset, uint256 amount) internal view returns (DataTypes.ModuleExecutionData memory) { DataTypes.AaveV3ActionParams memory withdrawParams = DataTypes.AaveV3ActionParams({asset: asset, amount: amount}); return DataTypes.ModuleExecutionData({ @@ -356,8 +374,11 @@ abstract contract IntegrationBase is TestBase { * @notice request 1 is fully processed, request 2 is partially processed, request 3 is unprocessed. */ function _createPartialDepositWithResolution(bool depositAll) internal returns (uint256, uint256) { + uint256 vaultTokenScale = 10 ** IERC20Metadata(environment.vaultAsset).decimals(); + uint256 lendTokenScale = 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(); + uint256 borrowTokenScale = 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); uint256 depositAmountUnscaled = 100; - uint256 depositAmount = depositAmountUnscaled * XTZ_SCALE; + uint256 depositAmount = depositAmountUnscaled * vaultTokenScale; _makeDepositRequest(depositAmount, user1, true); _makeDepositRequest(depositAmount, user2, true); _makeDepositRequest(depositAmount, user3, true); @@ -366,26 +387,45 @@ abstract contract IntegrationBase is TestBase { uint256 supplyAmountUnscaled = (3 * depositAmountUnscaledBatch); // 3x uint256 borrowAmountUnscaled = (3 * supplyAmountUnscaled) / 4; - uint256 depositAmountBatch = depositAmountUnscaledBatch * XTZ_SCALE; + uint256 depositAmountBatch = depositAmountUnscaledBatch * lendTokenScale; // build the operate call - uint256 supplyAmount = supplyAmountUnscaled * STXTZ_SCALE; - uint256 borrowAmount = borrowAmountUnscaled * XTZ_SCALE; + uint256 supplyAmount = supplyAmountUnscaled * lendTokenScale; + uint256 borrowAmount = borrowAmountUnscaled * borrowTokenScale; uint256 swapAmount = borrowAmount + depositAmountBatch; uint256 supplyAmountWithPremium = supplyAmount + (supplyAmount * 1) / 10000; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _supplyCall(ST_XTZ, supplyAmount); - moduleExecutionData[1] = _borrowCall(XTZ, borrowAmount); - - moduleExecutionData[2] = - _swapCallExactOutCurve(XTZ, ST_XTZ, XTZ_STXTZ_POOL, swapAmount, supplyAmountWithPremium, XTZ_STXTZ_SWAP); + moduleExecutionData[0] = _supplyCall(environment.lendAssets[0], supplyAmount); + moduleExecutionData[1] = _borrowCall(environment.borrowAssets[0], borrowAmount); + uint256 flashLoanAmount = supplyAmount - depositAmountBatch; + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.borrowAssets[0], + environment.lendAssets[0], + borrowAmount, + flashLoanAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + environment.borrowAssets[0], + environment.lendAssets[0], + XTZ_STXTZ_POOL, + swapAmount, + supplyAmountWithPremium, + XTZ_STXTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(ST_XTZ, supplyAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.lendAssets[0], flashLoanAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.lendAssets[0], supplyAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); - finalExecutionData[0] = - _resolveDepositRequestsCall(XTZ, depositAmountBatch, abi.encode(intermediateExecutionData)); + finalExecutionData[0] = _resolveDepositRequestsCall( + environment.vaultAsset, depositAmountBatch, abi.encode(intermediateExecutionData) + ); uint256 exchangeRateBefore = superloop.convertToAssets(ONE_SHARE); @@ -405,9 +445,9 @@ abstract contract IntegrationBase is TestBase { uint256 user3SharesAfter = superloop.balanceOf(user3); // deposit manager should have 150 xtz now - assertEq(IERC20(XTZ).balanceOf(address(depositManager)), 150 * XTZ_SCALE); + assertEq(IERC20(environment.vaultAsset).balanceOf(address(depositManager)), 150 * vaultTokenScale); // pending deposits should be 150 xtz - assertEq(depositManager.totalPendingDeposits(), 150 * XTZ_SCALE); + assertEq(depositManager.totalPendingDeposits(), 150 * vaultTokenScale); // resolution id pointer should be 2 assertEq(depositManager.resolutionIdPointer(), 2); @@ -415,12 +455,12 @@ abstract contract IntegrationBase is TestBase { // deposit request 1 should be fully processed DataTypes.DepositRequestData memory depositRequest1 = depositManager.depositRequest(1); assertEq(uint256(depositRequest1.state), uint256(DataTypes.RequestProcessingState.FULLY_PROCESSED)); - assertEq(depositRequest1.amountProcessed, 100 * XTZ_SCALE); + assertEq(depositRequest1.amountProcessed, 100 * vaultTokenScale); // deposit request 2 should be partially processed DataTypes.DepositRequestData memory depositRequest2 = depositManager.depositRequest(2); assertEq(uint256(depositRequest2.state), uint256(DataTypes.RequestProcessingState.PARTIALLY_PROCESSED)); - assertEq(depositRequest2.amountProcessed, 50 * XTZ_SCALE); + assertEq(depositRequest2.amountProcessed, 50 * vaultTokenScale); // deposit request 3 should be unprocessed DataTypes.DepositRequestData memory depositRequest3 = depositManager.depositRequest(3); @@ -440,11 +480,11 @@ abstract contract IntegrationBase is TestBase { function _makeDepositRequest(uint256 depositAmount, address user, bool _deal) internal { if (_deal) { - deal(XTZ, user, depositAmount); + deal(environment.vaultAsset, user, depositAmount); } vm.startPrank(user); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); depositManager.requestDeposit(depositAmount, address(0)); vm.stopPrank(); } @@ -470,6 +510,10 @@ abstract contract IntegrationBase is TestBase { internal returns (uint256, uint256, uint256) { + uint256 vaultTokenScale = 10 ** IERC20Metadata(environment.vaultAsset).decimals(); + uint256 lendTokenScale = 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(); + uint256 borrowTokenScale = 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); + (uint256 supplyAmountUnscaled, uint256 borrowAmountUnscaled) = _createPartialDepositWithResolution(true); // make 3 withdraw requests @@ -482,20 +526,33 @@ abstract contract IntegrationBase is TestBase { // resolve 1st fully, and 2nd partially // repay half of borrow amount - uint256 repayAmount = (borrowAmountUnscaled * XTZ_SCALE) / 2; - uint256 withdrawAmount = ((supplyAmountUnscaled + 10) * STXTZ_SCALE) / 2; + uint256 repayAmount = (borrowAmountUnscaled * borrowTokenScale) / 2; + uint256 withdrawAmount = ((supplyAmountUnscaled + 10) * lendTokenScale) / 2; uint256 repayAmountWithPremium = repayAmount + (repayAmount * 1) / 10000; uint256 totalShares = superloop.totalSupply(); uint256 sharesToResolve = totalShares / 2; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _repayCall(XTZ, repayAmount); - moduleExecutionData[1] = _withdrawCall(ST_XTZ, withdrawAmount); - moduleExecutionData[2] = - _swapCallExactOutCurve(ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP); + moduleExecutionData[0] = _repayCall(environment.borrowAssets[0], repayAmount); + moduleExecutionData[1] = _withdrawCall(environment.lendAssets[0], withdrawAmount); + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.lendAssets[0], + environment.borrowAssets[0], + withdrawAmount, + repayAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(XTZ, repayAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); finalExecutionData[0] = @@ -532,4 +589,18 @@ abstract contract IntegrationBase is TestBase { return (totalShares - sharesToResolve, repayAmount, withdrawAmount); } + + function _stakeCall(address stakingVault, uint256 amount) + internal + view + returns (DataTypes.ModuleExecutionData memory) + { + DataTypes.VaultActionParams memory vaultActionParams = + DataTypes.VaultActionParams({vault: stakingVault, amount: amount}); + return DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(vaultSupplyModule), + data: abi.encodeWithSelector(vaultSupplyModule.execute.selector, vaultActionParams) + }); + } } diff --git a/test/core/integration/Migration.t.sol b/test/core/integration/Migration.t.sol index c35f0c7..e1eb10d 100644 --- a/test/core/integration/Migration.t.sol +++ b/test/core/integration/Migration.t.sol @@ -35,61 +35,57 @@ contract MigrationTest is IntegrationBase { address DEX_MODULE = 0x38F5efC1267F6103c9b0FE802E1731E245f09Cd0; address oldVault = 0xe24e5DEbA01Ab0B5D78A0093442De0864832803E; + address oldVaultBtc = 0xC557529dd252e5a02E6C653b0B88984aFa3c8199; - // currently there are 115 holder who need to be migrated to the new vault - address[] public ALL_HOLDERS = [ - 0x8fb2FB82fF44da85E43d0A71CA992DbBC3243c5a, + // currently there are 119 holder who need to be migrated to the new vault + address[] public ALL_HOLDERS_XTZ = [ + 0x2B38f0De645E05Ec410AE86Ef479a1e42E45Bd12, 0xb3d1aCA9bF004d0da560930Cf9277c604eDf2283, 0x11fC7853944570C1F9D9EBE7Ac24e2FeFddf0314, 0xced306BfCB4a057aE3905063fD601774F9F18730, + 0x8fb2FB82fF44da85E43d0A71CA992DbBC3243c5a, 0x5d8809340760b1bB54642BE91Bb5A2871C0d7a10, 0x40F832B71D2C525A9aa4b4908Ec511Ed93c8a308, - 0x52f86e43dAbdDee24A35227598bF4569FE59034D, + 0xde5017f32C3AB32dd0286891A11F5d5D54C13728, 0x4f2421553E571627C6801521316732693016d9cF, - 0x953D1668BC03e0EE9145A7c4F79956b73db90B67, 0x6EA314366871459Dbc3e1A2Eb422e23B538c7ADC, + 0x953D1668BC03e0EE9145A7c4F79956b73db90B67, 0x46C9CCC6857E33a8209706BB5043700b4608Dda8, 0x67Efd0FfD493Fa5C55f2a19033557557e3A5197C, 0xbE00FA424243C56c7CF5792aFBd6b8Dc4d6CBE4E, 0xEC23a0C9D4107cAD33271c4F1955a21f4c041208, - 0x07ABc6225724a3f29e09173f585AacDE7A701dB3, + 0x3d72971b117EE8EF445D138557151f66CB0D408F, + 0xc2E97Ae6Ca9aAFB19CE0B8bCd1F4C50285db2377, 0x5EdF9DeC5b003AF80B65e129Ab17DBE64106a8e3, + 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f, 0x664324F8B7430b4C22Dc036234Dd752A4755Fda7, - 0x3d72971b117EE8EF445D138557151f66CB0D408F, + 0x07ABc6225724a3f29e09173f585AacDE7A701dB3, 0xB35722e595F2B974c2B1D14A80Fab5d0c60c2fa3, - 0x93e3A2a50e7A6091B877Fc939e0a3f43954811d8, 0x37b14bF02B1D01E196E3439d1689fEf845E9314A, + 0x93e3A2a50e7A6091B877Fc939e0a3f43954811d8, 0xd9aeb979cbe3a85F2d6fca3723f846Af8CA56E05, 0x4A2cFAa5B24850493298DCb8969fe11FEeFfc366, - 0xc2E97Ae6Ca9aAFB19CE0B8bCd1F4C50285db2377, - 0x68f6609d45A9dA001A88f7A3b9ec236Acf27e1f5, 0x1C6Aed70006d8d65c6407Fa9b27C9359C1572f40, 0x92c34B736B832d33209497f3904C4423d11E2a8a, 0xCAf9131ADF841cC8A3f8C14cBc94Aa16cF0a2faA, 0x9214835f82752E0574F3828A4400C36Ba386Df82, 0xF09E6d5EE9b5B7FC84412260FD6E6D70dCadcd9C, 0x8b529eF78046008f9d1FbC91c7407030De96EE32, - 0x669bd328f6C494949Ed9fB2dc8021557A6Dd005f, 0xa77E705d7166750F53F60ca7e246BAFBE40f5c42, 0x4fb30f8CcE1F80FC9CC45F7F626069be7549aF59, 0xc210a4D56D99E095b126664fDCE545b369ED3FD0, 0x03adFaA573aC1a9b19D2b8F79a5aAFFb9c2A0532, - 0xb6c82dCC296b055cEB6DBF1440d1379Bb4Dc1131, 0x79bc970B69CC8fb31406C409692eD19612E39731, - 0x96b29b8dD6240144e203F9f45D55F8FE6fC466aB, 0xb9b4800282BDC0DD2284fD321cf6800a24a7B8B0, 0x06e477F88ae55721B72f0F2c545328eDe998803e, 0xc25404122C1776689A6fD620d0F816Cb9e36e5dD, 0x2192013589F926637cB288004db2f1dadb71D390, 0xb0DebcB643eE79f19Ef659Bd01D0FAC12d058604, - 0x2E6Ff365367c9DB7C4a6140d37394aB8CF25C8A2, + 0x09e63186b4F94FafcCC60a42D3f77b4A10995674, 0x521e6f07bDfa2fF8D072887F0ef3bB908a3D9e2a, - 0x432d741Ae42175a2515121BcB917e7FE6d21Cf2e, + 0x341979e95a3e2969EaDa286b635d6e29F7a76a71, 0xAD89C778C7dBC0C7546cE7Fdf6A1d87C369A7892, - 0x8Fb2B9010780c418fbf84733ca3F0F5623EC2e7a, - 0x8B714cB45683733fD4Dc423FE6BF9A1d9F8DDD2C, 0x4987f13D50AC3246E589433349095492e820f8fd, - 0xa4F2F6D9BAB9532d2A5B9B41504274E7FD0fEf4f, 0x22472e7cA126b25fA031DeA6A7E0a7fbD9B2cdfd, 0x595e187831CB1ebAf3e4c09423eef3E74DfDaD1E, 0xaD96ACD2e6574682AEBf8f25774FA643bcaa3B5a, @@ -97,17 +93,12 @@ contract MigrationTest is IntegrationBase { 0xc51101BAbC9Cc75118fBCe5187914e45841A7f01, 0x09505A42D693D828B78CC87ed64DB9A0348Ca5e6, 0xdD543DC0efeA305A4c595305C784166fbe87897B, - 0xb1f607F3B753bEf4756a30cfC235629A0c2d1E57, - 0x0E9B063789909565CEdA1Fba162474405A151E66, - 0x341979e95a3e2969EaDa286b635d6e29F7a76a71, - 0x117AD17Bf596D6337931f41141F34275Eab16129, 0xbb85bFF98F145af30BA935Ae4DeD97C7A5Ce9bF2, 0xfd005e01FAa45d3CD3fD4641FBCd9FFa1d26C703, 0xaB14C744e5316475dd2384E6Bb95F9eCF4480bD5, 0x595f5e9cF36dF0AAf12Ae27f1b3dC18622Fc5748, 0xF5AC5943D16fC865824910033B756519DC396682, 0xa0c507E52359160a334d9f1AeAA459b8BD39568a, - 0x007EA857fdbE8D19997b507a21B4c377e3B21D82, 0x0E16cAA0E000cD3B68b4128C6aE62E846cb5b431, 0x603c081e1d72063Cd691CAD3Bd2FEd68eBE8922a, 0x5c53414E1f15D7668c2b9EC0A92482A64845f5f6, @@ -123,43 +114,97 @@ contract MigrationTest is IntegrationBase { 0xE3c3cD192Bdf289558A0bA645bc2c2c822F7EB06, 0x18514a31077b33f32BDbeA52443f2C0EEb094Be1, 0x0E9852b16AE49C99B84b0241E3C6F4a5692C6b05, + 0x2E6Ff365367c9DB7C4a6140d37394aB8CF25C8A2, 0x49400E8A2d270669ED7cfc4Fd5A2804c0A87e1BB, 0xDDc91d503Eeb7C3CB78559fB998C0a23354821ee, 0x0C5Cd230e9EBF5A707CF1f822Dfc49664304DfD2, 0x92751712B2F6ADE4AD1F35a837591A66394C6799, - 0x00ab2b9D924aaDCb60126A4050B68e8531A6A5a6, 0x57Fe7D8A10F5C4aC2Ca953a5B0a6b4BBfEC187C9, 0x5413E315A8c242132aC5DD626B0eceE616e5023D, + 0xf458B8FB16D661bC1E260991EBa9b1FFBC8bA39b, 0xC6Ad105483F89de96A3E423980fB550465A05D9b, 0x26C43439031301a85e19868647DF46177869c4C8, 0x9cB4a586bbdE7528DB256c3FCE08845255d892D9, + 0xb1f607F3B753bEf4756a30cfC235629A0c2d1E57, 0x1CbB1E8274E44287517A34De6AF3CF97dfD9E0Cb, 0x2b3F539dAf59126F6F566adD0dEDc19959305e9d, 0x2eCe359621497F4C9053c30912e9ecB7e74718c6, 0x1A50D1AeD031eE6B22b37c84eD8f3f3487C5Ca47, + 0x117AD17Bf596D6337931f41141F34275Eab16129, 0x5BD39fcEE33D45C5c5f615e09D2AA785Aa0c36C6, 0x698230D8445C21E99f4CC81F8F733d205a661cD3, 0x10b453C5379877d6b55B71D73Af7D8Fbc69eeF91, 0x13deFCe8Be050f902f8eE06CB7f0E743e3e8b705, 0xFF5A5bBC1b0f124974165b2190f460438d7CD220, 0x7F5B2c355bFC0C6F3aE2D394534C29D3609FF542, + 0x007EA857fdbE8D19997b507a21B4c377e3B21D82, 0x97F4aa12F637BAe9c07cA492eE281534b4F3BB50, 0x23b3511474c960960E4C030e572dADE633Cfc0F6, 0x7cA50563448C1f87cF131D96dEF7124f995cf3aC, 0xA8617c6BdE242B213bA283c93aCf034dfcbE66fA, + 0x68f6609d45A9dA001A88f7A3b9ec236Acf27e1f5, 0x37ae94b377B6D6522BCED1a448D144C69B6bfa9a, - 0xC44125EA298770DFA6B4a27A599CF014f19A573d, + 0x8Fb2B9010780c418fbf84733ca3F0F5623EC2e7a, 0x5F4BD8107FF5EB8fD25480C938662bBC11da27B8, 0x57453361422BfBE5332eF75CBE1C38242c5deEEA, + 0x96b29b8dD6240144e203F9f45D55F8FE6fC466aB, + 0x00ab2b9D924aaDCb60126A4050B68e8531A6A5a6, 0x431588Aff8ea1BEcB1d8188D87195Aa95678BA0A, + 0x0E9B063789909565CEdA1Fba162474405A151E66, 0x5952f70FEF1CbC26856d149646D4A8F97E923eE7, 0x894d222eCeC91B9dFFf8bcBD164B7a72DF7469aD, + 0x8B714cB45683733fD4Dc423FE6BF9A1d9F8DDD2C, 0x5E9b31FA592913C2aD2356ed843F66057c81455D, 0x58F1eFd2fBf415CFC0Ce30A13c36A2642690eDA0, 0x84e47C7E20Ee8e8d6195A99788226EB488f5Fad7, 0xCce7AcFb0d4760df15a1f87C92324B166e9C0638 ]; + address[] public ALL_HOLDERS_BTC = [ + 0x9D5132Dd45CdaCd1De3a7b7f1f13da7F025fF726, + 0xa7CBb758849979CEc43FD146fe01EB2BF560202C, + 0x6bA9c7d86eBD6A20817d482C42C9dE2806EeFB7e, + 0x9D0214C3dB28875fe2b9eb9cD9d4F71E7817890A, + 0x2B38f0De645E05Ec410AE86Ef479a1e42E45Bd12, + 0x8fb2FB82fF44da85E43d0A71CA992DbBC3243c5a, + 0xced306BfCB4a057aE3905063fD601774F9F18730, + 0x0C1856Cb7444cc8596c706185b54535F45C2623B, + 0x11fC7853944570C1F9D9EBE7Ac24e2FeFddf0314, + 0xb3d1aCA9bF004d0da560930Cf9277c604eDf2283, + 0xB438Dbaae78225eEfcDBB04E207A8eFB06036a2c, + 0x997b96BAD648c39226281Bc002f9857274E42A01, + 0xd52929B69680A6f74D2eB9c8F1ef482f37b1b32B, + 0x706FC1a8e457De0cf52e7679C2922aEF7F7a397e, + 0x3497818F50f79A11236B941dDAd35Da68A57864E, + 0xB35722e595F2B974c2B1D14A80Fab5d0c60c2fa3, + 0xA9f7b86FCC86EA1b913dDcaB8c9514a8e677666E, + 0x7077e395C9FA4E480366A5DC5792d2504d78dffF, + 0x664324F8B7430b4C22Dc036234Dd752A4755Fda7, + 0xD22ADba687aAd7DD9CEC3b18D970C581734783bA, + 0x5BC81274740A73D33ec8539182c326b8b58004C2, + 0x02F73B8e29dD6Ec38Bc5D8B3826051DC562c3060, + 0x4e90Da1dDCD5B0Fb295b6A69251b5D04D5bCCA7a, + 0x150a0D516C0ceFa39f980Da57d5422E91A94238b, + 0x4fb30f8CcE1F80FC9CC45F7F626069be7549aF59, + 0xF382ce5457Bba6113e082DD638b6671Cb2277B1f, + 0xb95f08B2eda7B23Ce412A6dE82eA5f5100335A3A, + 0x5b6Da8BC8696Aa4D20030151345ec652cf1eC727, + 0x0396816A361e2353a91Fde8438600a9353b34ce7, + 0xA1ebf2043e76446eFA2724bD1Ec18321776096FF, + 0x9214835f82752E0574F3828A4400C36Ba386Df82, + 0xb0DebcB643eE79f19Ef659Bd01D0FAC12d058604, + 0x3d72971b117EE8EF445D138557151f66CB0D408F, + 0x09e63186b4F94FafcCC60a42D3f77b4A10995674, + 0xc210a4D56D99E095b126664fDCE545b369ED3FD0, + 0xEC23a0C9D4107cAD33271c4F1955a21f4c041208, + 0x03adFaA573aC1a9b19D2b8F79a5aAFFb9c2A0532, + 0x6a58EBB35664A171A1B070DE48ee93278A63c168, + 0x521e6f07bDfa2fF8D072887F0ef3bB908a3D9e2a, + 0xF5AC5943D16fC865824910033B756519DC396682, + 0xBD73cF5baf12F120Ee3f6C4ad82df9a12649e578, + 0x44119A62EA645242234cAF408c4c20513E660EBb + ]; + function setUp() public override { super.setUp(); @@ -172,8 +217,17 @@ contract MigrationTest is IntegrationBase { data: abi.encodeWithSelector(emodeModule.execute.selector, params) }); + DataTypes.AaveV3EmodeParams memory paramsBtc = DataTypes.AaveV3EmodeParams({emodeCategory: 2}); + DataTypes.ModuleExecutionData[] memory moduleExecutionDataBtc = new DataTypes.ModuleExecutionData[](1); + moduleExecutionDataBtc[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(emodeModule), + data: abi.encodeWithSelector(emodeModule.execute.selector, paramsBtc) + }); + vm.startPrank(admin); superloop.operate(moduleExecutionData); + superloopBtc.operate(moduleExecutionDataBtc); // register the above modules moduleRegistry.setModule("REPAY_MODULE", REPAY_MODULE); @@ -187,92 +241,210 @@ contract MigrationTest is IntegrationBase { superloop.setRegisteredModule(DEPOSIT_MODULE, true); superloop.setRegisteredModule(BORROW_MODULE, true); superloop.setRegisteredModule(DEX_MODULE, true); + + superloopBtc.setRegisteredModule(REPAY_MODULE, true); + superloopBtc.setRegisteredModule(WITHDRAW_MODULE, true); + superloopBtc.setRegisteredModule(DEPOSIT_MODULE, true); + superloopBtc.setRegisteredModule(BORROW_MODULE, true); + superloopBtc.setRegisteredModule(DEX_MODULE, true); vm.stopPrank(); vm.label(oldVault, "oldVault"); + vm.label(oldVaultBtc, "oldVaultBtc"); vm.label(address(superloop), "newVault"); + vm.label(address(superloopBtc), "newVaultBtc"); vm.label(REPAY_MODULE, "REPAY_MODULE_OLD"); vm.label(WITHDRAW_MODULE, "WITHDRAW_MODULE_OLD"); vm.label(DEPOSIT_MODULE, "DEPOSIT_MODULE_OLD"); vm.label(BORROW_MODULE, "BORROW_MODULE_OLD"); vm.label(DEX_MODULE, "DEX_MODULE_OLD"); + + // TODO: do deposit and withdraw for both } - function test_migration() public { + // function test_migration() public { + // // deploy migration helper + // MigrationHelper migrationHelper = new MigrationHelper( + // AAVE_V3_POOL_ADDRESSES_PROVIDER, + // REPAY_MODULE, + // WITHDRAW_MODULE, + // DEPOSIT_MODULE, + // BORROW_MODULE, + // DEX_MODULE, + // USDC + // ); + // vm.label(address(migrationHelper), "migrationHelper"); + + // // set migration helper contract as priveledged address in old vault + // address oldVaultAdmin = ISuperloop(oldVault).vaultAdmin(); + // vm.prank(oldVaultAdmin); + // ISuperloop(oldVault).setPrivilegedAddress(address(migrationHelper), true); + + // // set migration helper contract as deposit manager and vault operator in new vault + // vm.startPrank(admin); + // superloop.setDepositManagerModule(address(migrationHelper)); + // superloop.setVaultOperator(address(migrationHelper)); + + // // set migration helper as vault in new accountant module + // accountant.setVault(address(migrationHelper)); + // accountant.transferOwnership(address(migrationHelper)); + // vm.stopPrank(); + + // address[] memory users = ALL_HOLDERS_XTZ; + + // // old balances + // uint256 oldVaultXTZBalance = IERC20(XTZ).balanceOf(oldVault); + // uint256 oldVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(oldVault); + // (uint256 oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, oldVault); + // (,, uint256 oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, oldVault); + + // console.log("OLD VAULT STATE INITIAL"); + // console.log("oldVaultXTZBalance", oldVaultXTZBalance); + // console.log("oldVaultSTXTZBalance", oldVaultSTXTZBalance); + // console.log("oldVaultLendBalance", oldVaultLendBalance); + // console.log("oldVaultBorrowBalance", oldVaultBorrowBalance); + // console.log("old vault total supply", ISuperloop(oldVault).totalSupply()); + // console.log("old vault total assets", ISuperloop(oldVault).totalAssets()); + + // assertEq(Ownable(address(accountant)).owner(), address(migrationHelper)); + // assertEq(accountant.vault(), address(migrationHelper)); + + // uint256 maxSharesDelta = 100; + + // // start recording gas + // uint256 startGas = gasleft(); + // uint256 batches = 4; + // migrationHelper.migrate(oldVault, address(superloop), users, ST_XTZ, XTZ, batches, maxSharesDelta, 5); + + // console.log("migration complete"); + + // uint256 endGas = gasleft(); + // uint256 gasUsed = startGas - endGas; + // console.log("gas used", gasUsed); + // uint256 newVaultXTZBalance = IERC20(XTZ).balanceOf(address(superloop)); + // uint256 newVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(address(superloop)); + // (uint256 newVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, address(superloop)); + // (,, uint256 newVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); + // uint256 newVaultLastRealizedFeeExchangeRate = + // IAccountantModule(ISuperloop(address(superloop)).accountant()).lastRealizedFeeExchangeRate(); + // uint256 oldVaultLastRealizedFeeExchangeRate = + // IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate(); + + // console.log("NEW VAULT STATE AFTER MIGRATION"); + // console.log("newVaultXTZBalance", newVaultXTZBalance); + // console.log("newVaultSTXTZBalance", newVaultSTXTZBalance); + // console.log("newVaultLendBalance", newVaultLendBalance); + // console.log("newVaultBorrowBalance", newVaultBorrowBalance); + // console.log("new vault total supply", ISuperloop(address(superloop)).totalSupply()); + // console.log("new vault total assets", ISuperloop(address(superloop)).totalAssets()); + // console.log( + // "new vault last realized fee exchange rate", + // IAccountantModule(ISuperloop(address(superloop)).accountant()).lastRealizedFeeExchangeRate() + // ); + + // // assert balances of each of the users are the same + + // for (uint256 i = 0; i < users.length; i++) { + // assertEq(ISuperloop(address(superloop)).balanceOf(users[i]), ISuperloop(oldVault).balanceOf(users[i])); + // } + + // assertEq(newVaultLastRealizedFeeExchangeRate, oldVaultLastRealizedFeeExchangeRate); + // assertEq(Ownable(address(accountant)).owner(), address(admin)); + // assertEq(accountant.vault(), address(superloop)); + + // oldVaultXTZBalance = IERC20(XTZ).balanceOf(oldVault); + // oldVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(oldVault); + // (oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, oldVault); + // (,, oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, oldVault); + + // console.log("OLD VAULT STATE AFTER MIGRATION"); + // console.log("oldVaultXTZBalance", oldVaultXTZBalance); + // console.log("oldVaultSTXTZBalance", oldVaultSTXTZBalance); + // console.log("oldVaultLendBalance", oldVaultLendBalance); + // console.log("oldVaultBorrowBalance", oldVaultBorrowBalance); + // console.log("old vault total supply", ISuperloop(oldVault).totalSupply()); + // console.log("old vault total assets", ISuperloop(oldVault).totalAssets()); + // console.log( + // "old vault last realized fee exchange rate", + // IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate() + // ); + // } + + function test_migrationBtc() public { // deploy migration helper MigrationHelper migrationHelper = new MigrationHelper( - AAVE_V3_POOL_ADDRESSES_PROVIDER, + environment.poolAddressesProvider, REPAY_MODULE, WITHDRAW_MODULE, DEPOSIT_MODULE, BORROW_MODULE, DEX_MODULE, - USDC + environment.stablecoin ); vm.label(address(migrationHelper), "migrationHelper"); // set migration helper contract as priveledged address in old vault - address oldVaultAdmin = ISuperloop(oldVault).vaultAdmin(); + address oldVaultAdmin = ISuperloop(oldVaultBtc).vaultAdmin(); vm.prank(oldVaultAdmin); - ISuperloop(oldVault).setPrivilegedAddress(address(migrationHelper), true); + ISuperloop(oldVaultBtc).setPrivilegedAddress(address(migrationHelper), true); // set migration helper contract as deposit manager and vault operator in new vault vm.startPrank(admin); - superloop.setDepositManagerModule(address(migrationHelper)); - superloop.setVaultOperator(address(migrationHelper)); + superloopBtc.setDepositManagerModule(address(migrationHelper)); + superloopBtc.setVaultOperator(address(migrationHelper)); // set migration helper as vault in new accountant module - accountant.setVault(address(migrationHelper)); - accountant.transferOwnership(address(migrationHelper)); + accountantBtc.setVault(address(migrationHelper)); + accountantBtc.transferOwnership(address(migrationHelper)); vm.stopPrank(); - address[] memory users = ALL_HOLDERS; + address[] memory users = ALL_HOLDERS_BTC; // old balances - uint256 oldVaultXTZBalance = IERC20(XTZ).balanceOf(oldVault); - uint256 oldVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(oldVault); - (uint256 oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, oldVault); - (,, uint256 oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, oldVault); + uint256 oldVaultBTCBalance = IERC20(WBTC).balanceOf(oldVaultBtc); + uint256 oldVaultLBTCBalance = IERC20(LBTC).balanceOf(oldVaultBtc); + (uint256 oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(LBTC, oldVaultBtc); + (,, uint256 oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(WBTC, oldVaultBtc); console.log("OLD VAULT STATE INITIAL"); - console.log("oldVaultXTZBalance", oldVaultXTZBalance); - console.log("oldVaultSTXTZBalance", oldVaultSTXTZBalance); + console.log("oldVaultBTCBalance", oldVaultBTCBalance); + console.log("oldVaultLBTCBalance", oldVaultLBTCBalance); console.log("oldVaultLendBalance", oldVaultLendBalance); console.log("oldVaultBorrowBalance", oldVaultBorrowBalance); - console.log("old vault total supply", ISuperloop(oldVault).totalSupply()); - console.log("old vault total assets", ISuperloop(oldVault).totalAssets()); + console.log("old vault total supply", ISuperloop(oldVaultBtc).totalSupply()); + console.log("old vault total assets", ISuperloop(oldVaultBtc).totalAssets()); - assertEq(Ownable(address(accountant)).owner(), address(migrationHelper)); - assertEq(accountant.vault(), address(migrationHelper)); + assertEq(Ownable(address(accountantBtc)).owner(), address(migrationHelper)); + assertEq(accountantBtc.vault(), address(migrationHelper)); uint256 maxSharesDelta = 100; // start recording gas uint256 startGas = gasleft(); uint256 batches = 4; - migrationHelper.migrate(oldVault, address(superloop), users, ST_XTZ, XTZ, batches, maxSharesDelta); + migrationHelper.migrate(oldVaultBtc, address(superloopBtc), users, LBTC, WBTC, batches, maxSharesDelta, 5); console.log("migration complete"); uint256 endGas = gasleft(); uint256 gasUsed = startGas - endGas; console.log("gas used", gasUsed); - uint256 newVaultXTZBalance = IERC20(XTZ).balanceOf(address(superloop)); - uint256 newVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(address(superloop)); - (uint256 newVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, address(superloop)); - (,, uint256 newVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); + uint256 newVaultBTCBalance = IERC20(WBTC).balanceOf(address(superloopBtc)); + uint256 newVaultLBTCBalance = IERC20(LBTC).balanceOf(address(superloopBtc)); + (uint256 newVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(LBTC, address(superloopBtc)); + (,, uint256 newVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(WBTC, address(superloopBtc)); uint256 newVaultLastRealizedFeeExchangeRate = - IAccountantModule(ISuperloop(address(superloop)).accountant()).lastRealizedFeeExchangeRate(); + IAccountantModule(ISuperloop(address(superloopBtc)).accountant()).lastRealizedFeeExchangeRate(); uint256 oldVaultLastRealizedFeeExchangeRate = - IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate(); + IAccountantModule(ISuperloopLegacy(oldVaultBtc).accountantModule()).lastRealizedFeeExchangeRate(); console.log("NEW VAULT STATE AFTER MIGRATION"); - console.log("newVaultXTZBalance", newVaultXTZBalance); - console.log("newVaultSTXTZBalance", newVaultSTXTZBalance); + console.log("newVaultXTZBalance", newVaultBTCBalance); + console.log("newVaultLBTCBalance", newVaultLBTCBalance); console.log("newVaultLendBalance", newVaultLendBalance); console.log("newVaultBorrowBalance", newVaultBorrowBalance); - console.log("new vault total supply", ISuperloop(address(superloop)).totalSupply()); - console.log("new vault total assets", ISuperloop(address(superloop)).totalAssets()); + console.log("new vault total supply", ISuperloop(address(superloopBtc)).totalSupply()); + console.log("new vault total assets", ISuperloop(address(superloopBtc)).totalAssets()); console.log( "new vault last realized fee exchange rate", IAccountantModule(ISuperloop(address(superloop)).accountant()).lastRealizedFeeExchangeRate() @@ -281,30 +453,30 @@ contract MigrationTest is IntegrationBase { // assert balances of each of the users are the same for (uint256 i = 0; i < users.length; i++) { - assertEq(ISuperloop(address(superloop)).balanceOf(users[i]), ISuperloop(oldVault).balanceOf(users[i])); + assertEq(ISuperloop(address(superloopBtc)).balanceOf(users[i]), ISuperloop(oldVaultBtc).balanceOf(users[i])); } assertEq(newVaultLastRealizedFeeExchangeRate, oldVaultLastRealizedFeeExchangeRate); - assertEq(Ownable(address(accountant)).owner(), address(admin)); - assertEq(accountant.vault(), address(superloop)); + assertEq(Ownable(address(accountantBtc)).owner(), address(admin)); + assertEq(accountantBtc.vault(), address(superloopBtc)); console.log("gas used", gasUsed); - oldVaultXTZBalance = IERC20(XTZ).balanceOf(oldVault); - oldVaultSTXTZBalance = IERC20(ST_XTZ).balanceOf(oldVault); - (oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, oldVault); - (,, oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(XTZ, oldVault); + oldVaultBTCBalance = IERC20(WBTC).balanceOf(oldVaultBtc); + oldVaultLBTCBalance = IERC20(LBTC).balanceOf(oldVaultBtc); + (oldVaultLendBalance,,,,,,,,) = poolDataProvider.getUserReserveData(LBTC, oldVaultBtc); + (,, oldVaultBorrowBalance,,,,,,) = poolDataProvider.getUserReserveData(WBTC, oldVaultBtc); console.log("OLD VAULT STATE AFTER MIGRATION"); - console.log("oldVaultXTZBalance", oldVaultXTZBalance); - console.log("oldVaultSTXTZBalance", oldVaultSTXTZBalance); + console.log("oldVaultBTCBalance", oldVaultBTCBalance); + console.log("oldVaultLBTCBalance", oldVaultLBTCBalance); console.log("oldVaultLendBalance", oldVaultLendBalance); console.log("oldVaultBorrowBalance", oldVaultBorrowBalance); - console.log("old vault total supply", ISuperloop(oldVault).totalSupply()); - console.log("old vault total assets", ISuperloop(oldVault).totalAssets()); + console.log("old vault total supply", ISuperloop(oldVaultBtc).totalSupply()); + console.log("old vault total assets", ISuperloop(oldVaultBtc).totalAssets()); console.log( "old vault last realized fee exchange rate", - IAccountantModule(ISuperloopLegacy(oldVault).accountantModule()).lastRealizedFeeExchangeRate() + IAccountantModule(ISuperloopLegacy(oldVaultBtc).accountantModule()).lastRealizedFeeExchangeRate() ); } } diff --git a/test/core/integration/Seed.t.sol b/test/core/integration/Seed.t.sol index e56bf44..8a4170b 100644 --- a/test/core/integration/Seed.t.sol +++ b/test/core/integration/Seed.t.sol @@ -15,29 +15,31 @@ contract SeedTest is IntegrationBase { } function test_seed() public { - uint256 seedAmount = 10 * XTZ_SCALE; - deal(XTZ, admin, seedAmount); + uint256 vaultScale = 10 ** environment.vaultAssetDecimals; + uint256 seedAmount = 10 * vaultScale; + deal(environment.vaultAsset, admin, seedAmount); vm.startPrank(admin); - IERC20(XTZ).approve(address(superloop), seedAmount); + IERC20(environment.vaultAsset).approve(address(superloop), seedAmount); superloop.seed(seedAmount); vm.stopPrank(); uint256 totalSupply = superloop.totalSupply(); - assertApproxEqAbs(totalSupply, 10 * (XTZ_SCALE) * 100, 100); + assertApproxEqAbs(totalSupply, 10 * (vaultScale) * 100, 100); assertEq(superloop.totalAssets(), seedAmount); assertEq(superloop.balanceOf(admin), totalSupply); } function test_seed_failing_cases() public { - uint256 seedAmount = 10 * XTZ_SCALE; - deal(XTZ, user1, seedAmount); - deal(XTZ, admin, seedAmount); + uint256 vaultScale = 10 ** environment.vaultAssetDecimals; + uint256 seedAmount = 10 * vaultScale; + deal(environment.vaultAsset, user1, seedAmount); + deal(environment.vaultAsset, admin, seedAmount); // should rever if not called by admin vm.startPrank(user1); - IERC20(XTZ).approve(address(superloop), seedAmount); + IERC20(environment.vaultAsset).approve(address(superloop), seedAmount); vm.expectRevert(bytes(Errors.CALLER_NOT_VAULT_ADMIN)); superloop.seed(seedAmount); vm.stopPrank(); @@ -50,7 +52,7 @@ contract SeedTest is IntegrationBase { // should seed vm.startPrank(admin); - IERC20(XTZ).approve(address(superloop), seedAmount); + IERC20(environment.vaultAsset).approve(address(superloop), seedAmount); superloop.seed(seedAmount); vm.stopPrank(); diff --git a/test/core/integration/UniversalAccountant.t.sol b/test/core/integration/UniversalAccountant.t.sol new file mode 100644 index 0000000..e035644 --- /dev/null +++ b/test/core/integration/UniversalAccountant.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {TestBase} from "../TestBase.sol"; +import {UniversalAccountant} from "../../../src/core/Accountant/universalAccountant/UniversalAccountant.sol"; +import {AaveV3AccountantPlugin} from "../../../src/plugins/Accountant/AaveV3AccountantPlugin.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {DataTypes} from "../../../src/common/DataTypes.sol"; +import {console} from "forge-std/console.sol"; +import {MockVault} from "../../../src/mock/MockVault.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol"; +import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; +import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; +import {IPoolAddressesProvider} from "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol"; + +contract UniversalAccountantTest is TestBase { + UniversalAccountant public accountantImplementation; + AaveV3AccountantPlugin public accountantPlugin; + + MockVault public vault; + + function setUp() public override { + super.setUp(); + + vault = new MockVault(IERC20(environment.vaultAsset), "Mock Vault", "mVLT"); + + // deploy accountant plugin + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets + }); + accountantPlugin = new AaveV3AccountantPlugin(accountantPluginInitData); + + address[] memory registeredAccountants = new address[](1); + registeredAccountants[0] = address(accountantPlugin); + + // deploy accountant + DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ + registeredAccountants: registeredAccountants, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) + }); + + accountantImplementation = new UniversalAccountant(); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(accountantImplementation), + address(this), + abi.encodeWithSelector(UniversalAccountant.initialize.selector, initData) + ); + + accountant = UniversalAccountant(address(proxy)); + } + + function test_UniversalAccountWithMutlipleTokens() public { + // create lend posiitions + vm.startPrank(address(vault)); + for (uint256 i = 0; i < environment.lendAssets.length; i++) { + uint8 decimals = IERC20Metadata(environment.lendAssets[i]).decimals(); + deal(environment.lendAssets[i], address(vault), 1000 * 10 ** decimals); + IERC20(environment.lendAssets[i]).approve(environment.pool, 1000 * 10 ** decimals); + IPool(environment.pool).supply(environment.lendAssets[i], 1000 * 10 ** decimals, address(vault), 0); + } + + // create borrow positions + for (uint256 i = 0; i < environment.borrowAssets.length; i++) { + uint8 decimals = IERC20Metadata(environment.borrowAssets[i]).decimals(); + IPool(environment.pool) + .borrow(environment.borrowAssets[i], 500 * 10 ** decimals, INTEREST_RATE_MODE, 0, address(vault)); + } + vm.stopPrank(); + + for (uint256 i = 0; i < environment.lendAssets.length; i++) { + (uint256 currentSupply,,,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.lendAssets[i], address(vault)); + assertApproxEqAbs(currentSupply, 1000 * 10 ** IERC20Metadata(environment.lendAssets[i]).decimals(), 2); + } + for (uint256 i = 0; i < environment.borrowAssets.length; i++) { + (,, uint256 currentBorrow,,,,,,) = IPoolDataProvider(environment.poolDataProvider) + .getUserReserveData(environment.borrowAssets[i], address(vault)); + + assertApproxEqAbs(currentBorrow, 500 * 10 ** IERC20Metadata(environment.borrowAssets[i]).decimals(), 2); + } + + // fund the vault for some tokens + deal(environment.vaultAsset, address(vault), 1000 * 10 ** environment.vaultAssetDecimals); + uint256 totalAssets = accountant.getTotalAssets(); + assert(totalAssets > (1200 + 1000) * 10 ** environment.vaultAssetDecimals); // aprox 1200 for lend and borrow positions and 1000 for the vault asset + } +} diff --git a/test/core/integration/WithdrawManager.t.sol b/test/core/integration/WithdrawManager.t.sol index 4303ce2..269da96 100644 --- a/test/core/integration/WithdrawManager.t.sol +++ b/test/core/integration/WithdrawManager.t.sol @@ -15,7 +15,8 @@ contract WithdrawManagerTest is IntegrationBase { function setUp() public override { super.setUp(); - DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: 3}); + DataTypes.AaveV3EmodeParams memory params = + DataTypes.AaveV3EmodeParams({emodeCategory: environment.emodeCategory}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -30,7 +31,7 @@ contract WithdrawManagerTest is IntegrationBase { function test_initialize() public view { assertEq(withdrawManager.vault(), address(superloop)); - assertEq(withdrawManager.asset(), XTZ); + assertEq(withdrawManager.asset(), environment.vaultAsset); assertEq(withdrawManager.nextWithdrawRequestId(requestType), 1); assertEq(withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.INSTANT), 1); assertEq(withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.PRIORITY), 1); @@ -57,21 +58,36 @@ contract WithdrawManagerTest is IntegrationBase { // assert claimable and sharesProcessed for 1st and 2nd requests (uint256 sharesLeftToResolve,,) = _createPartialWithdrawWithResolution(requestType); - (,, uint256 repayAmount,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - (uint256 withdrawAmount,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, address(superloop)); + (,, uint256 repayAmount,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + (uint256 withdrawAmount,,,,,,,,) = + poolDataProvider.getUserReserveData(environment.lendAssets[0], address(superloop)); // resolve 2nd fuly and 3rd partially. I will unwind the entire amount but claim only partial amount uint256 repayAmountWithPremium = repayAmount + (repayAmount * 1) / 10000; uint256 sharesToResolve = sharesLeftToResolve / 2; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _repayCall(XTZ, repayAmount); - moduleExecutionData[1] = _withdrawCall(ST_XTZ, withdrawAmount); - moduleExecutionData[2] = - _swapCallExactOutCurve(ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP); + moduleExecutionData[0] = _repayCall(environment.borrowAssets[0], repayAmount); + moduleExecutionData[1] = _withdrawCall(environment.lendAssets[0], withdrawAmount); + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.lendAssets[0], + environment.borrowAssets[0], + withdrawAmount, + repayAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(XTZ, repayAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); finalExecutionData[0] = @@ -111,12 +127,12 @@ contract WithdrawManagerTest is IntegrationBase { vm.stopPrank(); // req 2 is partially processed, hence it should be partially cancelled and user 2 should get back the remaining shares + claimable amount - uint256 user2BalanceBefore = IERC20(XTZ).balanceOf(user2); + uint256 user2BalanceBefore = IERC20(environment.vaultAsset).balanceOf(user2); uint256 user2ShareBalanceBefore = superloop.balanceOf(user2); vm.startPrank(user2); withdrawManager.cancelWithdrawRequest(2, requestType); vm.stopPrank(); - uint256 user2BalanceAfter = IERC20(XTZ).balanceOf(user2); + uint256 user2BalanceAfter = IERC20(environment.vaultAsset).balanceOf(user2); uint256 user2ShareBalanceAfter = superloop.balanceOf(user2); DataTypes.WithdrawRequestData memory withdrawRequest2 = withdrawManager.withdrawRequest(2, requestType); @@ -133,21 +149,36 @@ contract WithdrawManagerTest is IntegrationBase { uint256 totalPendingWithdraws = withdrawManager.totalPendingWithdraws(requestType); assertApproxEqRel(totalPendingWithdraws, sharesLeftToResolve - withdrawRequest2.sharesProcessed, 1e18); - (,, uint256 repayAmount,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - (uint256 withdrawAmount,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, address(superloop)); + (,, uint256 repayAmount,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + (uint256 withdrawAmount,,,,,,,,) = + poolDataProvider.getUserReserveData(environment.lendAssets[0], address(superloop)); // unwind the remaining position uint256 repayAmountWithPremium = repayAmount + (repayAmount * 1) / 10000; uint256 sharesToResolve = totalPendingWithdraws; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _repayCall(XTZ, repayAmount); - moduleExecutionData[1] = _withdrawCall(ST_XTZ, withdrawAmount); - moduleExecutionData[2] = - _swapCallExactOutCurve(ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP); + moduleExecutionData[0] = _repayCall(environment.borrowAssets[0], repayAmount); + moduleExecutionData[1] = _withdrawCall(environment.lendAssets[0], withdrawAmount); + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.lendAssets[0], + environment.borrowAssets[0], + withdrawAmount, + repayAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(XTZ, repayAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); finalExecutionData[0] = @@ -196,12 +227,12 @@ contract WithdrawManagerTest is IntegrationBase { _createPartialWithdrawWithResolution(requestType); // should be able to withdraw if fully processed - uint256 user1BalanceBefore = IERC20(XTZ).balanceOf(user1); + uint256 user1BalanceBefore = IERC20(environment.vaultAsset).balanceOf(user1); uint256 user1ShareBalanceBefore = superloop.balanceOf(user1); vm.startPrank(user1); withdrawManager.withdraw(requestType); vm.stopPrank(); - uint256 user1BalanceAfter = IERC20(XTZ).balanceOf(user1); + uint256 user1BalanceAfter = IERC20(environment.vaultAsset).balanceOf(user1); uint256 user1ShareBalanceAfter = superloop.balanceOf(user1); DataTypes.WithdrawRequestData memory withdrawRequest1 = withdrawManager.withdrawRequest(1, requestType); assertEq(uint256(withdrawRequest1.state), uint256(DataTypes.RequestProcessingState.FULLY_PROCESSED)); @@ -217,12 +248,12 @@ contract WithdrawManagerTest is IntegrationBase { vm.stopPrank(); // should be able to withdraw partially processed - uint256 user2BalanceBefore = IERC20(XTZ).balanceOf(user2); + uint256 user2BalanceBefore = IERC20(environment.vaultAsset).balanceOf(user2); uint256 user2ShareBalanceBefore = superloop.balanceOf(user2); vm.startPrank(user2); withdrawManager.withdraw(requestType); vm.stopPrank(); - uint256 user2BalanceAfter = IERC20(XTZ).balanceOf(user2); + uint256 user2BalanceAfter = IERC20(environment.vaultAsset).balanceOf(user2); uint256 user2ShareBalanceAfter = superloop.balanceOf(user2); DataTypes.WithdrawRequestData memory withdrawRequest2 = withdrawManager.withdrawRequest(2, requestType); assertEq(uint256(withdrawRequest2.state), uint256(DataTypes.RequestProcessingState.PARTIALLY_PROCESSED)); @@ -237,7 +268,7 @@ contract WithdrawManagerTest is IntegrationBase { vm.stopPrank(); // should not be able to claim if cancelled - user2BalanceBefore = IERC20(XTZ).balanceOf(user2); + user2BalanceBefore = IERC20(environment.vaultAsset).balanceOf(user2); user2ShareBalanceBefore = superloop.balanceOf(user2); vm.startPrank(user2); withdrawManager.cancelWithdrawRequest(2, requestType); @@ -247,7 +278,7 @@ contract WithdrawManagerTest is IntegrationBase { vm.expectRevert(bytes(Errors.WITHDRAW_REQUEST_NOT_FOUND)); withdrawManager.withdraw(requestType); vm.stopPrank(); - user2BalanceAfter = IERC20(XTZ).balanceOf(user2); + user2BalanceAfter = IERC20(environment.vaultAsset).balanceOf(user2); user2ShareBalanceAfter = superloop.balanceOf(user2); assertEq(user2BalanceAfter - user2BalanceBefore, 0); assertEq( @@ -329,18 +360,33 @@ contract WithdrawManagerTest is IntegrationBase { } function _resolveAllRequests(uint256 sharesToResolve, DataTypes.WithdrawRequestType _requestType) internal { - (,, uint256 repayAmount,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - (uint256 withdrawAmount,,,,,,,,) = poolDataProvider.getUserReserveData(ST_XTZ, address(superloop)); + (,, uint256 repayAmount,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + (uint256 withdrawAmount,,,,,,,,) = + poolDataProvider.getUserReserveData(environment.lendAssets[0], address(superloop)); uint256 repayAmountWithPremium = repayAmount + (repayAmount * 1) / 10000; DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](3); - moduleExecutionData[0] = _repayCall(XTZ, repayAmount); - moduleExecutionData[1] = _withdrawCall(ST_XTZ, withdrawAmount); - moduleExecutionData[2] = - _swapCallExactOutCurve(ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP); + moduleExecutionData[0] = _repayCall(environment.borrowAssets[0], repayAmount); + moduleExecutionData[1] = _withdrawCall(environment.lendAssets[0], withdrawAmount); + moduleExecutionData[2] = USE_MORPHO + ? _swapCallExactOut( + environment.lendAssets[0], + environment.borrowAssets[0], + withdrawAmount, + repayAmount, + environment.router, + USDC_USDE_POOL_FEE, + block.timestamp + 100 + ) + : _swapCallExactOutCurve( + ST_XTZ, XTZ, XTZ_STXTZ_POOL, withdrawAmount, repayAmountWithPremium, STXTZ_XTZ_SWAP + ); DataTypes.ModuleExecutionData[] memory intermediateExecutionData = new DataTypes.ModuleExecutionData[](1); - intermediateExecutionData[0] = _flashloanCall(XTZ, repayAmount, abi.encode(moduleExecutionData)); + intermediateExecutionData[0] = USE_MORPHO + ? _morphoFlashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)) + : _flashloanCall(environment.borrowAssets[0], repayAmount, abi.encode(moduleExecutionData)); DataTypes.ModuleExecutionData[] memory finalExecutionData = new DataTypes.ModuleExecutionData[](1); finalExecutionData[0] = diff --git a/test/core/unit/AccountantAaveV3.t.sol b/test/core/unit/AccountantAaveV3.t.sol index 5aa156c..fcbd75b 100644 --- a/test/core/unit/AccountantAaveV3.t.sol +++ b/test/core/unit/AccountantAaveV3.t.sol @@ -15,6 +15,7 @@ import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProv import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; import {IPriceOracleGetter} from "aave-v3-core/contracts/interfaces/IPriceOracleGetter.sol"; +// TODO : revisit with mutli token setup for eth mainnet contract AccountantAaveV3Test is TestBase { AccountantAaveV3 public accountantAaveV3Implementation; ProxyAdmin public proxyAdmin; @@ -31,15 +32,10 @@ contract AccountantAaveV3Test is TestBase { asset = IERC20(XTZ); vault = new MockVault(asset, "Mock Vault", "mVLT"); - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - DataTypes.AaveV3AccountantModuleInitData memory initData = DataTypes.AaveV3AccountantModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets, + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); @@ -56,22 +52,17 @@ contract AccountantAaveV3Test is TestBase { accountantAaveV3 = AccountantAaveV3(address(proxy)); // Fund the accountant with XTZ from whale - vm.startPrank(XTZ_WHALE); + vm.startPrank(environment.vaultAssetWhale); asset.transfer(address(vault), INITIAL_WHALE_BALANCE); vm.stopPrank(); } function test_Initialize() public { // Test that initialization works correctly - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - DataTypes.AaveV3AccountantModuleInitData memory initData = DataTypes.AaveV3AccountantModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets, + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); @@ -93,15 +84,10 @@ contract AccountantAaveV3Test is TestBase { function test_InitializeRevertIfAlreadyInitialized() public { // Test that initialize reverts if called again - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - DataTypes.AaveV3AccountantModuleInitData memory initData = DataTypes.AaveV3AccountantModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets, + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); @@ -230,15 +216,10 @@ contract AccountantAaveV3Test is TestBase { AccountantAaveV3 newContract = new AccountantAaveV3(); // Should revert if we try to initialize directly - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - DataTypes.AaveV3AccountantModuleInitData memory initData = DataTypes.AaveV3AccountantModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets, + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); @@ -286,7 +267,7 @@ contract AccountantAaveV3Test is TestBase { address[] memory borrowAssets = new address[](0); DataTypes.AaveV3AccountantModuleInitData memory initData = DataTypes.AaveV3AccountantModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, + poolAddressesProvider: environment.poolAddressesProvider, lendAssets: lendAssets, borrowAssets: borrowAssets, performanceFee: uint16(PERFORMANCE_FEE), @@ -311,28 +292,32 @@ contract AccountantAaveV3Test is TestBase { function _enableMockingPoolDataProvider() internal { vm.mockCall( - AAVE_V3_POOL_DATA_PROVIDER, - abi.encodeWithSelector(IPoolDataProvider.getUserReserveData.selector, ST_XTZ, address(vault)), + environment.poolDataProvider, + abi.encodeWithSelector( + IPoolDataProvider.getUserReserveData.selector, environment.lendAssets[0], address(vault) + ), abi.encode(1000 * 10 ** 6, 0, 0, 0, 0, 0, 0, 0, 0) ); vm.mockCall( - AAVE_V3_POOL_DATA_PROVIDER, - abi.encodeWithSelector(IPoolDataProvider.getUserReserveData.selector, XTZ, address(vault)), + environment.poolDataProvider, + abi.encodeWithSelector( + IPoolDataProvider.getUserReserveData.selector, environment.borrowAssets[0], address(vault) + ), abi.encode(0, 0, 800 ether, 0, 0, 0, 0, 0, 0) ); } function _enableMockingPriceOracle(uint256 stXtzPrice, uint256 xtzPrice) internal { vm.mockCall( - AAVE_V3_PRICE_ORACLE, - abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, ST_XTZ), + environment.priceOracle, + abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, environment.lendAssets[0]), abi.encode(stXtzPrice) ); vm.mockCall( - AAVE_V3_PRICE_ORACLE, - abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, XTZ), + environment.priceOracle, + abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, environment.borrowAssets[0]), abi.encode(xtzPrice) ); } diff --git a/test/core/unit/DepositManager.t.sol b/test/core/unit/DepositManager.t.sol index acc4b57..b3cf221 100644 --- a/test/core/unit/DepositManager.t.sol +++ b/test/core/unit/DepositManager.t.sol @@ -6,8 +6,9 @@ import {TestBase} from "../TestBase.sol"; import {console} from "forge-std/console.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Errors} from "../../../src/common/Errors.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -36,10 +37,10 @@ contract DepositManagerTest is TestBase { modules[4] = address(dexModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -62,7 +63,7 @@ contract DepositManagerTest is TestBase { ); superloop = Superloop(payable(address(proxy))); - _deployAccountant(address(superloop)); + _deployAccountant(address(superloop), environment.lendAssets, environment.borrowAssets); _deployWithdrawManager(address(superloop)); _deployDepositManager(address(superloop)); @@ -83,7 +84,7 @@ contract DepositManagerTest is TestBase { uint256 nextDepositRequestId = depositManager.nextDepositRequestId(); assertEq(vault, address(superloop)); - assertEq(asset, XTZ); + assertEq(asset, environment.vaultAsset); assertEq(nextDepositRequestId, 1); } @@ -93,10 +94,10 @@ contract DepositManagerTest is TestBase { uint256 depositAmount = 1000 * 10 ** 18; // Give user1 some XTZ tokens - deal(XTZ, user1, depositAmount); + deal(environment.vaultAsset, user1, depositAmount); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); // Expect the DepositRequested event vm.expectEmit(true, true, true, true); @@ -121,18 +122,18 @@ contract DepositManagerTest is TestBase { assertEq(depositManager.totalPendingDeposits(), depositAmount); // Verify tokens were transferred to deposit manager - assertEq(IERC20(XTZ).balanceOf(address(depositManager)), depositAmount); - assertEq(IERC20(XTZ).balanceOf(user1), 0); + assertEq(IERC20(environment.vaultAsset).balanceOf(address(depositManager)), depositAmount); + assertEq(IERC20(environment.vaultAsset).balanceOf(user1), 0); } function test_requestDeposit_OnBehalfOf() public { uint256 depositAmount = 1000 * 10 ** 18; // Give user1 some XTZ tokens - deal(XTZ, user1, depositAmount); + deal(environment.vaultAsset, user1, depositAmount); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); // Expect the DepositRequested event with user2 as the beneficiary vm.expectEmit(true, true, true, true); @@ -170,10 +171,10 @@ contract DepositManagerTest is TestBase { uint256 depositAmount = 200000 * 10 ** 18; // Exceeds supply cap of 100000 * 10**18 // Give user1 enough XTZ tokens - deal(XTZ, user1, depositAmount); + deal(environment.vaultAsset, user1, depositAmount); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); vm.expectRevert(bytes(Errors.SUPPLY_CAP_EXCEEDED)); depositManager.requestDeposit(depositAmount, address(0)); @@ -184,10 +185,10 @@ contract DepositManagerTest is TestBase { uint256 depositAmount = 1000 * 10 ** 18; // Give user1 some XTZ tokens - deal(XTZ, user1, depositAmount * 2); + deal(environment.vaultAsset, user1, depositAmount * 2); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount * 2); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount * 2); // Make first deposit request depositManager.requestDeposit(depositAmount, address(0)); @@ -203,7 +204,7 @@ contract DepositManagerTest is TestBase { // Don't give user1 any XTZ tokens vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); vm.expectRevert(); depositManager.requestDeposit(depositAmount, address(0)); @@ -216,13 +217,13 @@ contract DepositManagerTest is TestBase { uint256 depositAmount = 1000 * 10 ** 18; // Give user1 some XTZ tokens and make a deposit request - deal(XTZ, user1, depositAmount); + deal(environment.vaultAsset, user1, depositAmount); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); depositManager.requestDeposit(depositAmount, address(0)); - uint256 userBalanceBefore = IERC20(XTZ).balanceOf(user1); + uint256 userBalanceBefore = IERC20(environment.vaultAsset).balanceOf(user1); // Expect the DepositRequestCancelled event vm.expectEmit(true, true, true, true); @@ -244,18 +245,18 @@ contract DepositManagerTest is TestBase { assertEq(depositManager.totalPendingDeposits(), 0); // Verify tokens were refunded to user - assertEq(IERC20(XTZ).balanceOf(user1), userBalanceBefore + depositAmount); - assertEq(IERC20(XTZ).balanceOf(address(depositManager)), 0); + assertEq(IERC20(environment.vaultAsset).balanceOf(user1), userBalanceBefore + depositAmount); + assertEq(IERC20(environment.vaultAsset).balanceOf(address(depositManager)), 0); } function test_cancelDepositRequest_WrongOwner() public { uint256 depositAmount = 1000 * 10 ** 18; // Give user1 some XTZ tokens and make a deposit request - deal(XTZ, user1, depositAmount); + deal(environment.vaultAsset, user1, depositAmount); vm.startPrank(user1); - IERC20(XTZ).approve(address(depositManager), depositAmount); + IERC20(environment.vaultAsset).approve(address(depositManager), depositAmount); depositManager.requestDeposit(depositAmount, address(0)); vm.stopPrank(); diff --git a/test/core/unit/ModuleRegistry.t.sol b/test/core/unit/ModuleRegistry.t.sol index 386504c..e208b19 100644 --- a/test/core/unit/ModuleRegistry.t.sol +++ b/test/core/unit/ModuleRegistry.t.sol @@ -7,6 +7,7 @@ import {SuperloopModuleRegistry} from "../../../src/core/ModuleRegistry/ModuleRe import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Errors} from "../../../src/common/Errors.sol"; +// TODO : revisit with mutli token setup for eth mainnet contract ModuleRegistryTest is Test { SuperloopModuleRegistry public moduleRegistry; diff --git a/test/core/unit/Superloop.t.sol b/test/core/unit/Superloop.t.sol index 9038243..d571f30 100644 --- a/test/core/unit/Superloop.t.sol +++ b/test/core/unit/Superloop.t.sol @@ -34,10 +34,10 @@ contract SuperloopTest is TestBase { modules[4] = address(dexModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -60,7 +60,7 @@ contract SuperloopTest is TestBase { ); superloop = Superloop(payable(address(proxy))); - _deployAccountant(address(superloop)); + _deployAccountant(address(superloop), environment.lendAssets, environment.borrowAssets); _deployWithdrawManager(address(superloop)); superloop.setAccountantModule(address(accountantAaveV3)); @@ -81,7 +81,7 @@ contract SuperloopTest is TestBase { modules[0] = address(supplyModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, + asset: environment.vaultAsset, name: "Test Vault", symbol: "TEST", supplyCap: 1000 * 10 ** 18, @@ -112,7 +112,7 @@ contract SuperloopTest is TestBase { // Test that the contract is properly initialized assertEq(newSuperloop.name(), "Test Vault"); assertEq(newSuperloop.symbol(), "TEST"); - assertEq(newSuperloop.asset(), XTZ); + assertEq(newSuperloop.asset(), environment.vaultAsset); } function test_InitializeRevertIfAlreadyInitialized() public { @@ -121,7 +121,7 @@ contract SuperloopTest is TestBase { modules[0] = address(supplyModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, + asset: environment.vaultAsset, name: "Test Vault", symbol: "TEST", supplyCap: 1000 * 10 ** 18, @@ -148,7 +148,7 @@ contract SuperloopTest is TestBase { modules[0] = address(0x123); // Invalid module DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, + asset: environment.vaultAsset, name: "Test Vault", symbol: "TEST", supplyCap: 1000 * 10 ** 18, @@ -185,7 +185,7 @@ contract SuperloopTest is TestBase { modules[0] = address(supplyModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, + asset: environment.vaultAsset, name: "Test Vault", symbol: "TEST", supplyCap: 1000 * 10 ** 18, @@ -427,28 +427,28 @@ contract SuperloopTest is TestBase { // ============ Integration Tests ============ function test_skimRevertIfInvalidAsset() public { - deal(XTZ, address(superloop), 1000 * 10 ** 18); + deal(environment.vaultAsset, address(superloop), 1000 * 10 ** 18); vm.expectRevert(bytes(Errors.INVALID_SKIM_ASSET)); vm.prank(admin); - superloop.skim(XTZ); + superloop.skim(environment.vaultAsset); } function test_skim() public { - deal(ST_XTZ, address(superloop), 1000 * 10 ** 18); + deal(environment.borrowAssets[0], address(superloop), 1000 * 10 ** 18); vm.prank(admin); - superloop.skim(ST_XTZ); + superloop.skim(environment.borrowAssets[0]); - assertEq(IERC20(ST_XTZ).balanceOf(treasury), 1000 * 10 ** 18); - assertEq(IERC20(ST_XTZ).balanceOf(address(superloop)), 0); + assertEq(IERC20(environment.borrowAssets[0]).balanceOf(treasury), 1000 * 10 ** 18); + assertEq(IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)), 0); } function _seed() internal { vm.startPrank(admin); - deal(XTZ, admin, 1000 * 10 ** 18); - IERC20(XTZ).approve(address(superloop), type(uint256).max); + deal(environment.vaultAsset, admin, 1000 * 10 ** 18); + IERC20(environment.vaultAsset).approve(address(superloop), type(uint256).max); superloop.deposit(100 * 10 ** 18, admin); vm.stopPrank(); } diff --git a/test/core/unit/UniversalAccountant.t.sol b/test/core/unit/UniversalAccountant.t.sol index fd8e70a..d738b1c 100644 --- a/test/core/unit/UniversalAccountant.t.sol +++ b/test/core/unit/UniversalAccountant.t.sol @@ -16,6 +16,7 @@ import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; import {IPriceOracleGetter} from "aave-v3-core/contracts/interfaces/IPriceOracleGetter.sol"; import {AaveV3AccountantPlugin} from "../../../src/plugins/Accountant/AaveV3AccountantPlugin.sol"; +// TODO : revisit with mutli token setup for eth mainnet contract AccountantAaveV3Test is TestBase { UniversalAccountant public accountantImplementation; AaveV3AccountantPlugin public accountantPlugin; @@ -29,21 +30,16 @@ contract AccountantAaveV3Test is TestBase { function setUp() public override { super.setUp(); - asset = IERC20(XTZ); + asset = IERC20(environment.vaultAsset); vault = new MockVault(asset, "Mock Vault", "mVLT"); - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - // deploy accountant plugin - DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = DataTypes - .AaveV3AccountantPluginModuleInitData({ - poolAddressesProvider: AAVE_V3_POOL_ADDRESSES_PROVIDER, - lendAssets: lendAssets, - borrowAssets: borrowAssets - }); + DataTypes.AaveV3AccountantPluginModuleInitData memory accountantPluginInitData = + DataTypes.AaveV3AccountantPluginModuleInitData({ + poolAddressesProvider: environment.poolAddressesProvider, + lendAssets: environment.lendAssets, + borrowAssets: environment.borrowAssets + }); accountantPlugin = new AaveV3AccountantPlugin(accountantPluginInitData); address[] memory registeredAccountants = new address[](1); @@ -51,9 +47,7 @@ contract AccountantAaveV3Test is TestBase { // deploy accountant DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ - registeredAccountants: registeredAccountants, - performanceFee: uint16(PERFORMANCE_FEE), - vault: address(vault) + registeredAccountants: registeredAccountants, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); accountantImplementation = new UniversalAccountant(); @@ -66,7 +60,7 @@ contract AccountantAaveV3Test is TestBase { accountant = UniversalAccountant(address(proxy)); // Fund the accountant with XTZ from whale - vm.startPrank(XTZ_WHALE); + vm.startPrank(environment.vaultAssetWhale); asset.transfer(address(vault), INITIAL_WHALE_BALANCE); vm.stopPrank(); } @@ -80,18 +74,11 @@ contract AccountantAaveV3Test is TestBase { function test_InitializeRevertIfAlreadyInitialized() public { // Test that initialize reverts if called again - address[] memory lendAssets = new address[](1); - lendAssets[0] = ST_XTZ; - address[] memory borrowAssets = new address[](1); - borrowAssets[0] = XTZ; - address[] memory registeredAccountants = new address[](1); registeredAccountants[0] = address(accountantPlugin); DataTypes.UniversalAccountantModuleInitData memory initData = DataTypes.UniversalAccountantModuleInitData({ - registeredAccountants: registeredAccountants, - performanceFee: uint16(PERFORMANCE_FEE), - vault: address(vault) + registeredAccountants: registeredAccountants, performanceFee: uint16(PERFORMANCE_FEE), vault: address(vault) }); vm.expectRevert(); @@ -239,28 +226,32 @@ contract AccountantAaveV3Test is TestBase { function _enableMockingPoolDataProvider() internal { vm.mockCall( - AAVE_V3_POOL_DATA_PROVIDER, - abi.encodeWithSelector(IPoolDataProvider.getUserReserveData.selector, ST_XTZ, address(vault)), + environment.poolDataProvider, + abi.encodeWithSelector( + IPoolDataProvider.getUserReserveData.selector, environment.lendAssets[0], address(vault) + ), abi.encode(1000 * 10 ** 6, 0, 0, 0, 0, 0, 0, 0, 0) ); vm.mockCall( - AAVE_V3_POOL_DATA_PROVIDER, - abi.encodeWithSelector(IPoolDataProvider.getUserReserveData.selector, XTZ, address(vault)), + environment.poolDataProvider, + abi.encodeWithSelector( + IPoolDataProvider.getUserReserveData.selector, environment.borrowAssets[0], address(vault) + ), abi.encode(0, 0, 800 ether, 0, 0, 0, 0, 0, 0) ); } function _enableMockingPriceOracle(uint256 stXtzPrice, uint256 xtzPrice) internal { vm.mockCall( - AAVE_V3_PRICE_ORACLE, - abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, ST_XTZ), + environment.priceOracle, + abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, environment.lendAssets[0]), abi.encode(stXtzPrice) ); vm.mockCall( - AAVE_V3_PRICE_ORACLE, - abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, XTZ), + environment.priceOracle, + abi.encodeWithSelector(IPriceOracleGetter.getAssetPrice.selector, environment.borrowAssets[0]), abi.encode(xtzPrice) ); } diff --git a/test/core/unit/WithdrawManager.t.sol b/test/core/unit/WithdrawManager.t.sol index d030caa..6801b8a 100644 --- a/test/core/unit/WithdrawManager.t.sol +++ b/test/core/unit/WithdrawManager.t.sol @@ -6,8 +6,9 @@ import {TestBase} from "../TestBase.sol"; import {console} from "forge-std/console.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Errors} from "../../../src/common/Errors.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -36,10 +37,10 @@ contract WithdrawManagerTest is TestBase { modules[4] = address(dexModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -62,7 +63,7 @@ contract WithdrawManagerTest is TestBase { ); superloop = Superloop(payable(address(proxy))); - _deployAccountant(address(superloop)); + _deployAccountant(address(superloop), environment.lendAssets, environment.borrowAssets); _deployWithdrawManager(address(superloop)); _deployDepositManager(address(superloop)); @@ -86,7 +87,7 @@ contract WithdrawManagerTest is TestBase { uint256 nextDeferredRequestId = withdrawManager.nextWithdrawRequestId(DataTypes.WithdrawRequestType.DEFERRED); assertEq(vault, address(superloop)); - assertEq(asset, XTZ); + assertEq(asset, environment.vaultAsset); assertEq(nextGeneralRequestId, 1); assertEq(nextInstantRequestId, 1); assertEq(nextPriorityRequestId, 1); @@ -347,9 +348,7 @@ contract WithdrawManagerTest is TestBase { function test_resolveWithdrawRequests_OnlyVault() public { DataTypes.ResolveWithdrawRequestsData memory data = DataTypes.ResolveWithdrawRequestsData({ - shares: 1000 * 10 ** 18, - requestType: DataTypes.WithdrawRequestType.GENERAL, - callbackExecutionData: "" + shares: 1000 * 10 ** 18, requestType: DataTypes.WithdrawRequestType.GENERAL, callbackExecutionData: "" }); vm.startPrank(user1); @@ -360,9 +359,7 @@ contract WithdrawManagerTest is TestBase { function test_resolveWithdrawRequests_ZeroShares() public { DataTypes.ResolveWithdrawRequestsData memory data = DataTypes.ResolveWithdrawRequestsData({ - shares: 0, - requestType: DataTypes.WithdrawRequestType.GENERAL, - callbackExecutionData: "" + shares: 0, requestType: DataTypes.WithdrawRequestType.GENERAL, callbackExecutionData: "" }); vm.startPrank(address(superloop)); @@ -373,9 +370,7 @@ contract WithdrawManagerTest is TestBase { function test_resolveWithdrawRequests_ExceedsPending() public { DataTypes.ResolveWithdrawRequestsData memory data = DataTypes.ResolveWithdrawRequestsData({ - shares: 1000 * 10 ** 18, - requestType: DataTypes.WithdrawRequestType.GENERAL, - callbackExecutionData: "" + shares: 1000 * 10 ** 18, requestType: DataTypes.WithdrawRequestType.GENERAL, callbackExecutionData: "" }); vm.startPrank(address(superloop)); diff --git a/test/helpers/VaultRouter.t.sol b/test/helpers/VaultRouter.t.sol index ca1fb24..bd9a646 100644 --- a/test/helpers/VaultRouter.t.sol +++ b/test/helpers/VaultRouter.t.sol @@ -1,602 +1,603 @@ +// TODO : Update tests for periphery contract // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {VaultRouter} from "../../src/helpers/VaultRouter.sol"; -import {DataTypes} from "../../src/common/DataTypes.sol"; -import {Errors} from "../../src/common/Errors.sol"; -import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; -import {MockUniversalDexModule} from "../../src/mock/MockUniversalDexModule.sol"; -import {MockAsset} from "../../src/mock/MockAsset.sol"; -import {MockVault} from "../../src/mock/MockVault.sol"; -import {MockDepositManager} from "../../src/mock/MockDepositManager.sol"; - -/** - * @title VaultRouterTest - * @author Superlend - * @notice Comprehensive unit tests for the VaultRouter contract - */ -contract VaultRouterTest is Test { - VaultRouter public vaultRouter; - MockUniversalDexModule public mockDexModule; - MockAsset public mockToken; - MockVault public mockVault; - MockDepositManager public mockDepositManager; - - address public owner; - address public user; - address public nonOwner; - - uint256 public constant INITIAL_SUPPLY = 1000000 * 10 ** 18; - uint256 public constant DEPOSIT_AMOUNT = 1000 * 10 ** 18; - uint256 public constant SWAP_OUTPUT = 950 * 10 ** 18; // 5% slippage - - event VaultWhitelisted(address indexed vault, bool isWhitelisted); - event TokenWhitelisted(address indexed token, bool isWhitelisted); - event DepositManagerWhitelisted(address indexed depositManager, bool isWhitelisted); - - function setUp() public { - owner = makeAddr("owner"); - user = makeAddr("user"); - nonOwner = makeAddr("nonOwner"); - - // Deploy mock contracts - mockToken = new MockAsset(); - mockVault = new MockVault(IERC20(address(mockToken)), "Mock Vault", "MV"); - mockDepositManager = new MockDepositManager(); - mockDexModule = new MockUniversalDexModule(SWAP_OUTPUT); - - // Setup initial arrays - address[] memory supportedVaults = new address[](1); - supportedVaults[0] = address(mockVault); - - address[] memory supportedTokens = new address[](1); - supportedTokens[0] = address(mockToken); - - address[] memory supportedDepositManagers = new address[](1); - supportedDepositManagers[0] = address(mockDepositManager); - - // Deploy VaultRouter - vm.prank(owner); - vaultRouter = - new VaultRouter(supportedVaults, supportedTokens, address(mockDexModule), supportedDepositManagers); - - // Transfer tokens to user for testing - mockToken.transfer(user, DEPOSIT_AMOUNT * 10); - - // Label addresses for better debugging - vm.label(owner, "owner"); - vm.label(user, "user"); - vm.label(nonOwner, "nonOwner"); - vm.label(address(mockToken), "mockToken"); - vm.label(address(mockVault), "mockVault"); - vm.label(address(mockDepositManager), "mockDepositManager"); - vm.label(address(mockDexModule), "mockDexModule"); - vm.label(address(vaultRouter), "vaultRouter"); - } - - // ============ Constructor Tests ============ - - function test_Constructor_InitializesCorrectly() public view { - assertTrue(vaultRouter.supportedVaults(address(mockVault))); - assertTrue(vaultRouter.supportedTokens(address(mockToken))); - assertTrue(vaultRouter.supportedDepositManagers(address(mockDepositManager))); - assertEq(address(vaultRouter.universalDexModule()), address(mockDexModule)); - assertEq(vaultRouter.owner(), owner); - } - - function test_Constructor_EmitsEvents() public { - address[] memory vaults = new address[](1); - vaults[0] = address(0x123); - address[] memory tokens = new address[](1); - tokens[0] = address(0x456); - address[] memory depositManagers = new address[](1); - depositManagers[0] = address(0x789); - - vm.expectEmit(true, false, false, true); - emit VaultWhitelisted(address(0x123), true); - - vm.expectEmit(true, false, false, true); - emit TokenWhitelisted(address(0x456), true); - - vm.expectEmit(true, false, false, true); - emit DepositManagerWhitelisted(address(0x789), true); - - vm.prank(owner); - new VaultRouter(vaults, tokens, address(mockDexModule), depositManagers); - } - - // ============ Deposit Tests ============ - - function test_DepositWithToken_InstantDeposit_Success() public { - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - uint256 userBalanceBefore = mockToken.balanceOf(user); - uint256 vaultBalanceBefore = mockToken.balanceOf(address(mockVault)); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - - assertEq(mockToken.balanceOf(user), userBalanceBefore - amountIn); - assertEq(mockToken.balanceOf(address(mockVault)), vaultBalanceBefore + amountIn); - } - - function test_DepositWithToken_RequestedDeposit_Success() public { - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - uint256 userBalanceBefore = mockToken.balanceOf(user); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.REQUESTED, - swapParams - ); - vm.stopPrank(); - - assertEq(mockToken.balanceOf(user), userBalanceBefore - amountIn); - // For requested deposits, tokens are transferred to the deposit manager - // The mock deposit manager doesn't actually hold tokens, so we just check the shares - } - - function test_DepositWithToken_WithSwap_Success() public { - // Create a different token for the vault - MockAsset vaultToken = new MockAsset(); - MockVault swapVault = new MockVault(IERC20(address(vaultToken)), "Swap Vault", "SV"); - - // Whitelist the new vault and token - vm.startPrank(owner); - vaultRouter.whitelistVault(address(swapVault), true); - vaultRouter.whitelistToken(address(vaultToken), true); - vm.stopPrank(); - - // Transfer vault tokens to the VaultRouter to simulate the swap output - vaultToken.transfer(address(vaultRouter), SWAP_OUTPUT); - - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(vaultToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - uint256 userBalanceBefore = mockToken.balanceOf(user); - uint256 vaultBalanceBefore = vaultToken.balanceOf(address(swapVault)); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(swapVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - - assertEq(mockToken.balanceOf(user), userBalanceBefore); // no diff should be there because unswapped tokens are returned to the user - assertEq(vaultToken.balanceOf(address(swapVault)), vaultBalanceBefore + SWAP_OUTPUT); - } - - // ============ Access Control Tests ============ - - function test_DepositWithToken_VaultNotWhitelisted_Reverts() public { - address unwhitelistedVault = makeAddr("unwhitelistedVault"); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: DEPOSIT_AMOUNT, - maxAmountIn: DEPOSIT_AMOUNT, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); - vm.expectRevert(abi.encodePacked(Errors.VAULT_NOT_WHITELISTED)); - vaultRouter.depositWithToken( - unwhitelistedVault, - address(mockDepositManager), - address(mockToken), - DEPOSIT_AMOUNT, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - function test_DepositWithToken_TokenNotWhitelisted_Reverts() public { - MockAsset unwhitelistedToken = new MockAsset(); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(unwhitelistedToken), - tokenOut: address(unwhitelistedToken), - amountIn: DEPOSIT_AMOUNT, - maxAmountIn: DEPOSIT_AMOUNT, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - unwhitelistedToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); - vm.expectRevert(abi.encodePacked(Errors.TOKEN_NOT_WHITELISTED)); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(unwhitelistedToken), - DEPOSIT_AMOUNT, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - function test_DepositWithToken_DepositManagerNotWhitelisted_Reverts() public { - address unwhitelistedDepositManager = makeAddr("unwhitelistedDepositManager"); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: DEPOSIT_AMOUNT, - maxAmountIn: DEPOSIT_AMOUNT, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); - vm.expectRevert(abi.encodePacked(Errors.DEPOSIT_MANAGER_NOT_WHITELISTED)); - vaultRouter.depositWithToken( - address(mockVault), - unwhitelistedDepositManager, - address(mockToken), - DEPOSIT_AMOUNT, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - // ============ Whitelist Management Tests ============ - - function test_WhitelistVault_OnlyOwner_Success() public { - address newVault = makeAddr("newVault"); - - vm.expectEmit(true, false, false, true); - emit VaultWhitelisted(newVault, true); - - vm.prank(owner); - vaultRouter.whitelistVault(newVault, true); - - assertTrue(vaultRouter.supportedVaults(newVault)); - } - - function test_WhitelistVault_NonOwner_Reverts() public { - address newVault = makeAddr("newVault"); - - vm.prank(nonOwner); - vm.expectRevert(); - vaultRouter.whitelistVault(newVault, true); - } - - function test_WhitelistToken_OnlyOwner_Success() public { - address newToken = makeAddr("newToken"); - - vm.expectEmit(true, false, false, true); - emit TokenWhitelisted(newToken, true); - - vm.prank(owner); - vaultRouter.whitelistToken(newToken, true); - - assertTrue(vaultRouter.supportedTokens(newToken)); - } - - function test_WhitelistToken_NonOwner_Reverts() public { - address newToken = makeAddr("newToken"); - - vm.prank(nonOwner); - vm.expectRevert(); - vaultRouter.whitelistToken(newToken, true); - } - - function test_WhitelistDepositManager_OnlyOwner_Success() public { - address newDepositManager = makeAddr("newDepositManager"); - - vm.expectEmit(true, false, false, true); - emit DepositManagerWhitelisted(newDepositManager, true); - - vm.prank(owner); - vaultRouter.whitelistDepositManager(newDepositManager, true); - - assertTrue(vaultRouter.supportedDepositManagers(newDepositManager)); - } - - function test_WhitelistDepositManager_NonOwner_Reverts() public { - address newDepositManager = makeAddr("newDepositManager"); - - vm.prank(nonOwner); - vm.expectRevert(); - vaultRouter.whitelistDepositManager(newDepositManager, true); - } - - function test_WhitelistVault_RemoveFromWhitelist_Success() public { - // First add to whitelist - vm.prank(owner); - vaultRouter.whitelistVault(address(mockVault), false); - - assertFalse(vaultRouter.supportedVaults(address(mockVault))); - } - - function test_WhitelistToken_RemoveFromWhitelist_Success() public { - // First add to whitelist - vm.prank(owner); - vaultRouter.whitelistToken(address(mockToken), false); - - assertFalse(vaultRouter.supportedTokens(address(mockToken))); - } - - function test_WhitelistDepositManager_RemoveFromWhitelist_Success() public { - // First add to whitelist - vm.prank(owner); - vaultRouter.whitelistDepositManager(address(mockDepositManager), false); - - assertFalse(vaultRouter.supportedDepositManagers(address(mockDepositManager))); - } - - // ============ Universal DEX Module Tests ============ - - function test_SetUniversalDexModule_OnlyOwner_Success() public { - MockUniversalDexModule newDexModule = new MockUniversalDexModule(1000); - - vm.prank(owner); - vaultRouter.setUniversalDexModule(address(newDexModule)); - - assertEq(address(vaultRouter.universalDexModule()), address(newDexModule)); - } - - function test_SetUniversalDexModule_NonOwner_Reverts() public { - MockUniversalDexModule newDexModule = new MockUniversalDexModule(1000); - - vm.prank(nonOwner); - vm.expectRevert(); - vaultRouter.setUniversalDexModule(address(newDexModule)); - } - - // ============ Edge Cases and Error Conditions ============ - - function test_DepositWithToken_InsufficientAllowance_Reverts() public { - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: DEPOSIT_AMOUNT, - maxAmountIn: DEPOSIT_AMOUNT, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - // Don't approve tokens - vm.expectRevert(); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - DEPOSIT_AMOUNT, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - function test_DepositWithToken_InsufficientBalance_Reverts() public { - uint256 excessiveAmount = mockToken.balanceOf(user) + 1; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: excessiveAmount, - maxAmountIn: excessiveAmount, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), excessiveAmount); - vm.expectRevert(); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - excessiveAmount, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - function test_DepositWithToken_SwapFails_Reverts() public { - // Create a different token for the vault - MockAsset vaultToken = new MockAsset(); - MockVault swapVault = new MockVault(IERC20(address(vaultToken)), "Swap Vault", "SV"); - - // Whitelist the new vault and token - vm.startPrank(owner); - vaultRouter.whitelistVault(address(swapVault), true); - vaultRouter.whitelistToken(address(vaultToken), true); - vm.stopPrank(); - - // Transfer vault tokens to the VaultRouter to simulate the swap output - vaultToken.transfer(address(vaultRouter), SWAP_OUTPUT); - - // Make the DEX module revert - mockDexModule.setShouldRevert(true); - - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(vaultToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vm.expectRevert("MockDexModule: execution failed"); - vaultRouter.depositWithToken( - address(swapVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - } - - // ============ Integration Tests ============ - - function test_FullDepositFlow_InstantDeposit() public { - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - // Record initial state - uint256 userTokenBalanceBefore = mockToken.balanceOf(user); - uint256 vaultTokenBalanceBefore = mockToken.balanceOf(address(mockVault)); - - // Execute deposit - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.INSTANT, - swapParams - ); - vm.stopPrank(); - - // Verify final state - assertEq(mockToken.balanceOf(user), userTokenBalanceBefore - amountIn); - assertEq(mockToken.balanceOf(address(mockVault)), vaultTokenBalanceBefore + amountIn); - } - - function test_FullDepositFlow_RequestedDeposit() public { - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - // Record initial state - uint256 userTokenBalanceBefore = mockToken.balanceOf(user); - - // Execute deposit - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.REQUESTED, - swapParams - ); - vm.stopPrank(); - - // Verify final state - assertEq(mockToken.balanceOf(user), userTokenBalanceBefore - amountIn); - // For requested deposits, tokens are transferred to the deposit manager - // The mock deposit manager doesn't actually hold tokens, so we just check the shares - } - - // ============ Gas Optimization Tests ============ - - function test_DepositWithToken_GasUsage() public { - uint256 amountIn = DEPOSIT_AMOUNT; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: address(mockToken), - tokenOut: address(mockToken), - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user); - mockToken.approve(address(vaultRouter), amountIn); - - uint256 gasStart = gasleft(); - vaultRouter.depositWithToken( - address(mockVault), - address(mockDepositManager), - address(mockToken), - amountIn, - DataTypes.DepositType.INSTANT, - swapParams - ); - uint256 gasUsed = gasStart - gasleft(); - - console.log("Gas used for instant deposit:", gasUsed); - vm.stopPrank(); - - // Gas usage should be reasonable (less than 200k gas) - assertLt(gasUsed, 200000); - } -} +// pragma solidity ^0.8.13; + +// import {Test, console} from "forge-std/Test.sol"; +// import {Vm} from "forge-std/Vm.sol"; +// import {VaultRouter} from "../../src/helpers/VaultRouter.sol"; +// import {DataTypes} from "../../src/common/DataTypes.sol"; +// import {Errors} from "../../src/common/Errors.sol"; +// import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +// import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol"; +// import {MockUniversalDexModule} from "../../src/mock/MockUniversalDexModule.sol"; +// import {MockAsset} from "../../src/mock/MockAsset.sol"; +// import {MockVault} from "../../src/mock/MockVault.sol"; +// import {MockDepositManager} from "../../src/mock/MockDepositManager.sol"; + +// /** +// * @title VaultRouterTest +// * @author Superlend +// * @notice Comprehensive unit tests for the VaultRouter contract +// */ +// contract VaultRouterTest is Test { +// VaultRouter public vaultRouter; +// MockUniversalDexModule public mockDexModule; +// MockAsset public mockToken; +// MockVault public mockVault; +// MockDepositManager public mockDepositManager; + +// address public owner; +// address public user; +// address public nonOwner; + +// uint256 public constant INITIAL_SUPPLY = 1000000 * 10 ** 18; +// uint256 public constant DEPOSIT_AMOUNT = 1000 * 10 ** 18; +// uint256 public constant SWAP_OUTPUT = 950 * 10 ** 18; // 5% slippage + +// event VaultWhitelisted(address indexed vault, bool isWhitelisted); +// event TokenWhitelisted(address indexed token, bool isWhitelisted); +// event DepositManagerWhitelisted(address indexed depositManager, bool isWhitelisted); + +// function setUp() public { +// owner = makeAddr("owner"); +// user = makeAddr("user"); +// nonOwner = makeAddr("nonOwner"); + +// // Deploy mock contracts +// mockToken = new MockAsset(); +// mockVault = new MockVault(IERC20(address(mockToken)), "Mock Vault", "MV"); +// mockDepositManager = new MockDepositManager(); +// mockDexModule = new MockUniversalDexModule(SWAP_OUTPUT); + +// // Setup initial arrays +// address[] memory supportedVaults = new address[](1); +// supportedVaults[0] = address(mockVault); + +// address[] memory supportedTokens = new address[](1); +// supportedTokens[0] = address(mockToken); + +// address[] memory supportedDepositManagers = new address[](1); +// supportedDepositManagers[0] = address(mockDepositManager); + +// // Deploy VaultRouter +// vm.prank(owner); +// vaultRouter = +// new VaultRouter(supportedVaults, supportedTokens, address(mockDexModule), supportedDepositManagers); + +// // Transfer tokens to user for testing +// mockToken.transfer(user, DEPOSIT_AMOUNT * 10); + +// // Label addresses for better debugging +// vm.label(owner, "owner"); +// vm.label(user, "user"); +// vm.label(nonOwner, "nonOwner"); +// vm.label(address(mockToken), "mockToken"); +// vm.label(address(mockVault), "mockVault"); +// vm.label(address(mockDepositManager), "mockDepositManager"); +// vm.label(address(mockDexModule), "mockDexModule"); +// vm.label(address(vaultRouter), "vaultRouter"); +// } + +// // ============ Constructor Tests ============ + +// function test_Constructor_InitializesCorrectly() public view { +// assertTrue(vaultRouter.supportedVaults(address(mockVault))); +// assertTrue(vaultRouter.supportedTokens(address(mockToken))); +// assertTrue(vaultRouter.supportedDepositManagers(address(mockDepositManager))); +// assertEq(address(vaultRouter.universalDexModule()), address(mockDexModule)); +// assertEq(vaultRouter.owner(), owner); +// } + +// function test_Constructor_EmitsEvents() public { +// address[] memory vaults = new address[](1); +// vaults[0] = address(0x123); +// address[] memory tokens = new address[](1); +// tokens[0] = address(0x456); +// address[] memory depositManagers = new address[](1); +// depositManagers[0] = address(0x789); + +// vm.expectEmit(true, false, false, true); +// emit VaultWhitelisted(address(0x123), true); + +// vm.expectEmit(true, false, false, true); +// emit TokenWhitelisted(address(0x456), true); + +// vm.expectEmit(true, false, false, true); +// emit DepositManagerWhitelisted(address(0x789), true); + +// vm.prank(owner); +// new VaultRouter(vaults, tokens, address(mockDexModule), depositManagers); +// } + +// // ============ Deposit Tests ============ + +// function test_DepositWithToken_InstantDeposit_Success() public { +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// uint256 userBalanceBefore = mockToken.balanceOf(user); +// uint256 vaultBalanceBefore = mockToken.balanceOf(address(mockVault)); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); + +// assertEq(mockToken.balanceOf(user), userBalanceBefore - amountIn); +// assertEq(mockToken.balanceOf(address(mockVault)), vaultBalanceBefore + amountIn); +// } + +// function test_DepositWithToken_RequestedDeposit_Success() public { +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// uint256 userBalanceBefore = mockToken.balanceOf(user); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.REQUESTED, +// swapParams +// ); +// vm.stopPrank(); + +// assertEq(mockToken.balanceOf(user), userBalanceBefore - amountIn); +// // For requested deposits, tokens are transferred to the deposit manager +// // The mock deposit manager doesn't actually hold tokens, so we just check the shares +// } + +// function test_DepositWithToken_WithSwap_Success() public { +// // Create a different token for the vault +// MockAsset vaultToken = new MockAsset(); +// MockVault swapVault = new MockVault(IERC20(address(vaultToken)), "Swap Vault", "SV"); + +// // Whitelist the new vault and token +// vm.startPrank(owner); +// vaultRouter.whitelistVault(address(swapVault), true); +// vaultRouter.whitelistToken(address(vaultToken), true); +// vm.stopPrank(); + +// // Transfer vault tokens to the VaultRouter to simulate the swap output +// vaultToken.transfer(address(vaultRouter), SWAP_OUTPUT); + +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(vaultToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// uint256 userBalanceBefore = mockToken.balanceOf(user); +// uint256 vaultBalanceBefore = vaultToken.balanceOf(address(swapVault)); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(swapVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); + +// assertEq(mockToken.balanceOf(user), userBalanceBefore); // no diff should be there because unswapped tokens are returned to the user +// assertEq(vaultToken.balanceOf(address(swapVault)), vaultBalanceBefore + SWAP_OUTPUT); +// } + +// // ============ Access Control Tests ============ + +// function test_DepositWithToken_VaultNotWhitelisted_Reverts() public { +// address unwhitelistedVault = makeAddr("unwhitelistedVault"); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: DEPOSIT_AMOUNT, +// maxAmountIn: DEPOSIT_AMOUNT, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); +// vm.expectRevert(abi.encodePacked(Errors.VAULT_NOT_WHITELISTED)); +// vaultRouter.depositWithToken( +// unwhitelistedVault, +// address(mockDepositManager), +// address(mockToken), +// DEPOSIT_AMOUNT, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// function test_DepositWithToken_TokenNotWhitelisted_Reverts() public { +// MockAsset unwhitelistedToken = new MockAsset(); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(unwhitelistedToken), +// tokenOut: address(unwhitelistedToken), +// amountIn: DEPOSIT_AMOUNT, +// maxAmountIn: DEPOSIT_AMOUNT, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// unwhitelistedToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); +// vm.expectRevert(abi.encodePacked(Errors.TOKEN_NOT_WHITELISTED)); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(unwhitelistedToken), +// DEPOSIT_AMOUNT, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// function test_DepositWithToken_DepositManagerNotWhitelisted_Reverts() public { +// address unwhitelistedDepositManager = makeAddr("unwhitelistedDepositManager"); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: DEPOSIT_AMOUNT, +// maxAmountIn: DEPOSIT_AMOUNT, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), DEPOSIT_AMOUNT); +// vm.expectRevert(abi.encodePacked(Errors.DEPOSIT_MANAGER_NOT_WHITELISTED)); +// vaultRouter.depositWithToken( +// address(mockVault), +// unwhitelistedDepositManager, +// address(mockToken), +// DEPOSIT_AMOUNT, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// // ============ Whitelist Management Tests ============ + +// function test_WhitelistVault_OnlyOwner_Success() public { +// address newVault = makeAddr("newVault"); + +// vm.expectEmit(true, false, false, true); +// emit VaultWhitelisted(newVault, true); + +// vm.prank(owner); +// vaultRouter.whitelistVault(newVault, true); + +// assertTrue(vaultRouter.supportedVaults(newVault)); +// } + +// function test_WhitelistVault_NonOwner_Reverts() public { +// address newVault = makeAddr("newVault"); + +// vm.prank(nonOwner); +// vm.expectRevert(); +// vaultRouter.whitelistVault(newVault, true); +// } + +// function test_WhitelistToken_OnlyOwner_Success() public { +// address newToken = makeAddr("newToken"); + +// vm.expectEmit(true, false, false, true); +// emit TokenWhitelisted(newToken, true); + +// vm.prank(owner); +// vaultRouter.whitelistToken(newToken, true); + +// assertTrue(vaultRouter.supportedTokens(newToken)); +// } + +// function test_WhitelistToken_NonOwner_Reverts() public { +// address newToken = makeAddr("newToken"); + +// vm.prank(nonOwner); +// vm.expectRevert(); +// vaultRouter.whitelistToken(newToken, true); +// } + +// function test_WhitelistDepositManager_OnlyOwner_Success() public { +// address newDepositManager = makeAddr("newDepositManager"); + +// vm.expectEmit(true, false, false, true); +// emit DepositManagerWhitelisted(newDepositManager, true); + +// vm.prank(owner); +// vaultRouter.whitelistDepositManager(newDepositManager, true); + +// assertTrue(vaultRouter.supportedDepositManagers(newDepositManager)); +// } + +// function test_WhitelistDepositManager_NonOwner_Reverts() public { +// address newDepositManager = makeAddr("newDepositManager"); + +// vm.prank(nonOwner); +// vm.expectRevert(); +// vaultRouter.whitelistDepositManager(newDepositManager, true); +// } + +// function test_WhitelistVault_RemoveFromWhitelist_Success() public { +// // First add to whitelist +// vm.prank(owner); +// vaultRouter.whitelistVault(address(mockVault), false); + +// assertFalse(vaultRouter.supportedVaults(address(mockVault))); +// } + +// function test_WhitelistToken_RemoveFromWhitelist_Success() public { +// // First add to whitelist +// vm.prank(owner); +// vaultRouter.whitelistToken(address(mockToken), false); + +// assertFalse(vaultRouter.supportedTokens(address(mockToken))); +// } + +// function test_WhitelistDepositManager_RemoveFromWhitelist_Success() public { +// // First add to whitelist +// vm.prank(owner); +// vaultRouter.whitelistDepositManager(address(mockDepositManager), false); + +// assertFalse(vaultRouter.supportedDepositManagers(address(mockDepositManager))); +// } + +// // ============ Universal DEX Module Tests ============ + +// function test_SetUniversalDexModule_OnlyOwner_Success() public { +// MockUniversalDexModule newDexModule = new MockUniversalDexModule(1000); + +// vm.prank(owner); +// vaultRouter.setUniversalDexModule(address(newDexModule)); + +// assertEq(address(vaultRouter.universalDexModule()), address(newDexModule)); +// } + +// function test_SetUniversalDexModule_NonOwner_Reverts() public { +// MockUniversalDexModule newDexModule = new MockUniversalDexModule(1000); + +// vm.prank(nonOwner); +// vm.expectRevert(); +// vaultRouter.setUniversalDexModule(address(newDexModule)); +// } + +// // ============ Edge Cases and Error Conditions ============ + +// function test_DepositWithToken_InsufficientAllowance_Reverts() public { +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: DEPOSIT_AMOUNT, +// maxAmountIn: DEPOSIT_AMOUNT, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// // Don't approve tokens +// vm.expectRevert(); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// DEPOSIT_AMOUNT, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// function test_DepositWithToken_InsufficientBalance_Reverts() public { +// uint256 excessiveAmount = mockToken.balanceOf(user) + 1; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: excessiveAmount, +// maxAmountIn: excessiveAmount, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), excessiveAmount); +// vm.expectRevert(); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// excessiveAmount, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// function test_DepositWithToken_SwapFails_Reverts() public { +// // Create a different token for the vault +// MockAsset vaultToken = new MockAsset(); +// MockVault swapVault = new MockVault(IERC20(address(vaultToken)), "Swap Vault", "SV"); + +// // Whitelist the new vault and token +// vm.startPrank(owner); +// vaultRouter.whitelistVault(address(swapVault), true); +// vaultRouter.whitelistToken(address(vaultToken), true); +// vm.stopPrank(); + +// // Transfer vault tokens to the VaultRouter to simulate the swap output +// vaultToken.transfer(address(vaultRouter), SWAP_OUTPUT); + +// // Make the DEX module revert +// mockDexModule.setShouldRevert(true); + +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(vaultToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vm.expectRevert("MockDexModule: execution failed"); +// vaultRouter.depositWithToken( +// address(swapVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); +// } + +// // ============ Integration Tests ============ + +// function test_FullDepositFlow_InstantDeposit() public { +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// // Record initial state +// uint256 userTokenBalanceBefore = mockToken.balanceOf(user); +// uint256 vaultTokenBalanceBefore = mockToken.balanceOf(address(mockVault)); + +// // Execute deposit +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// vm.stopPrank(); + +// // Verify final state +// assertEq(mockToken.balanceOf(user), userTokenBalanceBefore - amountIn); +// assertEq(mockToken.balanceOf(address(mockVault)), vaultTokenBalanceBefore + amountIn); +// } + +// function test_FullDepositFlow_RequestedDeposit() public { +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// // Record initial state +// uint256 userTokenBalanceBefore = mockToken.balanceOf(user); + +// // Execute deposit +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.REQUESTED, +// swapParams +// ); +// vm.stopPrank(); + +// // Verify final state +// assertEq(mockToken.balanceOf(user), userTokenBalanceBefore - amountIn); +// // For requested deposits, tokens are transferred to the deposit manager +// // The mock deposit manager doesn't actually hold tokens, so we just check the shares +// } + +// // ============ Gas Optimization Tests ============ + +// function test_DepositWithToken_GasUsage() public { +// uint256 amountIn = DEPOSIT_AMOUNT; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: address(mockToken), +// tokenOut: address(mockToken), +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user); +// mockToken.approve(address(vaultRouter), amountIn); + +// uint256 gasStart = gasleft(); +// vaultRouter.depositWithToken( +// address(mockVault), +// address(mockDepositManager), +// address(mockToken), +// amountIn, +// DataTypes.DepositType.INSTANT, +// swapParams +// ); +// uint256 gasUsed = gasStart - gasleft(); + +// console.log("Gas used for instant deposit:", gasUsed); +// vm.stopPrank(); + +// // Gas usage should be reasonable (less than 200k gas) +// assertLt(gasUsed, 200000); +// } +// } diff --git a/test/helpers/integration/VaultRouter.t.sol b/test/helpers/integration/VaultRouter.t.sol index fa4f7e3..083e305 100644 --- a/test/helpers/integration/VaultRouter.t.sol +++ b/test/helpers/integration/VaultRouter.t.sol @@ -1,218 +1,220 @@ +// TODO : Update tests for periphery contract + // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import {IntegrationBase} from "../../core/integration/IntegrationBase.sol"; -import {console} from "forge-std/console.sol"; -import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {VaultRouter} from "../../../src/helpers/VaultRouter.sol"; -import {IRouter} from "../../../src/mock/MockIRouter.sol"; -import {DataTypes} from "../../../src/common/DataTypes.sol"; -import {Errors} from "../../../src/common/Errors.sol"; - -// test to swap from usdc to wxtz then deposit via the vault router - -contract VaultRouterTest is IntegrationBase { - VaultRouter public vaultRouter; - - function setUp() public override { - super.setUp(); - - address[] memory supportedVaults = new address[](1); - supportedVaults[0] = address(superloop); - address[] memory supportedTokens = new address[](3); - supportedTokens[0] = XTZ; - supportedTokens[1] = ST_XTZ; - supportedTokens[2] = USDC; - - address[] memory supportedDepositManagers = new address[](1); - supportedDepositManagers[0] = address(depositManager); - - vm.startPrank(admin); - vaultRouter = new VaultRouter(supportedVaults, supportedTokens, address(dexModule), supportedDepositManagers); - vm.stopPrank(); - - vm.prank(USDC_WHALE); - IERC20(USDC).transfer(user1, 100 * 10 ** 6); - - vm.prank(XTZ_WHALE); - IERC20(XTZ).transfer(user1, 100 * 10 ** 18); - } - - function test_normalDeposit() public { - uint256 amountIn = 1 * 10 ** 18; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: XTZ, - tokenOut: XTZ, - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user1); - IERC20(XTZ).approve(address(vaultRouter), amountIn); - vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); - vaultRouter.depositWithToken( - address(superloop), address(depositManager), XTZ, amountIn, DataTypes.DepositType.INSTANT, swapParams - ); - } - - function test_swapAndDeposit() public { - uint256 amountIn = 1 * 10 ** 6; - - // approve vault router to spend USDC - DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); - - data[0] = DataTypes.ExecuteSwapParamsData({ - target: address(USDC), - data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) - }); - data[1] = DataTypes.ExecuteSwapParamsData({ - target: address(ROUTER), - data: abi.encodeWithSelector( - IRouter.exactInputSingle.selector, - IRouter.ExactInputSingleParams({ - tokenIn: USDC, - tokenOut: XTZ, - fee: 500, - recipient: address(dexModule), - amountIn: amountIn, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ) - }); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: USDC, - tokenOut: XTZ, - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: data - }); - - vm.startPrank(user1); - IERC20(USDC).approve(address(vaultRouter), amountIn); - vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); - vaultRouter.depositWithToken( - address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.INSTANT, swapParams - ); - } - - function test_depositRequest() public { - uint256 amountIn = 1 * 10 ** 18; - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: XTZ, - tokenOut: XTZ, - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: new DataTypes.ExecuteSwapParamsData[](0) - }); - - vm.startPrank(user1); - IERC20(XTZ).approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(superloop), address(depositManager), XTZ, amountIn, DataTypes.DepositType.REQUESTED, swapParams - ); - - DataTypes.DepositRequestData memory request = depositManager.depositRequest(1); - assertEq(request.amount, amountIn); - assertEq(request.user, user1); - } - - function test_depositRequestWithSwap() public { - uint256 amountIn = 1 * 10 ** 6; - - // approve vault router to spend USDC - DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); - - data[0] = DataTypes.ExecuteSwapParamsData({ - target: address(USDC), - data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) - }); - data[1] = DataTypes.ExecuteSwapParamsData({ - target: address(ROUTER), - data: abi.encodeWithSelector( - IRouter.exactInputSingle.selector, - IRouter.ExactInputSingleParams({ - tokenIn: USDC, - tokenOut: XTZ, - fee: 500, - recipient: address(dexModule), - amountIn: amountIn, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ) - }); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: USDC, - tokenOut: XTZ, - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: data - }); - - vm.startPrank(user1); - IERC20(USDC).approve(address(vaultRouter), amountIn); - vaultRouter.depositWithToken( - address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.REQUESTED, swapParams - ); - - DataTypes.DepositRequestData memory request = depositManager.depositRequest(1); - assertGt(request.amount, 0); - assertEq(request.user, user1); - } - - function test_frontrunningProtection() public { - uint256 amountIn = 1 * 10 ** 6; - - // approve vault router to spend USDC - DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); - - data[0] = DataTypes.ExecuteSwapParamsData({ - target: address(USDC), - data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) - }); - data[1] = DataTypes.ExecuteSwapParamsData({ - target: address(ROUTER), - data: abi.encodeWithSelector( - IRouter.exactInputSingle.selector, - IRouter.ExactInputSingleParams({ - tokenIn: USDC, - tokenOut: XTZ, - fee: 500, - recipient: address(dexModule), - amountIn: amountIn, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ) - }); - - DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ - tokenIn: USDC, - tokenOut: XTZ, - amountIn: amountIn, - maxAmountIn: amountIn, - minAmountOut: 0, - data: data - }); - - vm.prank(user1); - IERC20(USDC).approve(address(vaultRouter), amountIn); - - vm.prank(user2); - vm.expectRevert(); - vaultRouter.depositWithToken( - address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.INSTANT, swapParams - ); - } -} +// pragma solidity ^0.8.13; + +// import {IntegrationBase} from "../../core/integration/IntegrationBase.sol"; +// import {console} from "forge-std/console.sol"; +// import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +// import {VaultRouter} from "../../../src/helpers/VaultRouter.sol"; +// import {IRouter} from "../../../src/mock/MockIRouter.sol"; +// import {DataTypes} from "../../../src/common/DataTypes.sol"; +// import {Errors} from "../../../src/common/Errors.sol"; + +// // test to swap from usdc to wxtz then deposit via the vault router + +// contract VaultRouterTest is IntegrationBase { +// VaultRouter public vaultRouter; + +// function setUp() public override { +// super.setUp(); + +// address[] memory supportedVaults = new address[](1); +// supportedVaults[0] = address(superloop); +// address[] memory supportedTokens = new address[](3); +// supportedTokens[0] = XTZ; +// supportedTokens[1] = ST_XTZ; +// supportedTokens[2] = USDC; + +// address[] memory supportedDepositManagers = new address[](1); +// supportedDepositManagers[0] = address(depositManager); + +// vm.startPrank(admin); +// vaultRouter = new VaultRouter(supportedVaults, supportedTokens, address(dexModule), supportedDepositManagers); +// vm.stopPrank(); + +// vm.prank(USDC_WHALE); +// IERC20(USDC).transfer(user1, 100 * 10 ** 6); + +// vm.prank(XTZ_WHALE); +// IERC20(XTZ).transfer(user1, 100 * 10 ** 18); +// } + +// function test_normalDeposit() public { +// uint256 amountIn = 1 * 10 ** 18; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: XTZ, +// tokenOut: XTZ, +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user1); +// IERC20(XTZ).approve(address(vaultRouter), amountIn); +// vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); +// vaultRouter.depositWithToken( +// address(superloop), address(depositManager), XTZ, amountIn, DataTypes.DepositType.INSTANT, swapParams +// ); +// } + +// function test_swapAndDeposit() public { +// uint256 amountIn = 1 * 10 ** 6; + +// // approve vault router to spend USDC +// DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); + +// data[0] = DataTypes.ExecuteSwapParamsData({ +// target: address(USDC), +// data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) +// }); +// data[1] = DataTypes.ExecuteSwapParamsData({ +// target: address(ROUTER), +// data: abi.encodeWithSelector( +// IRouter.exactInputSingle.selector, +// IRouter.ExactInputSingleParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// fee: 500, +// recipient: address(dexModule), +// amountIn: amountIn, +// amountOutMinimum: 0, +// sqrtPriceLimitX96: 0 +// }) +// ) +// }); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: data +// }); + +// vm.startPrank(user1); +// IERC20(USDC).approve(address(vaultRouter), amountIn); +// vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); +// vaultRouter.depositWithToken( +// address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.INSTANT, swapParams +// ); +// } + +// function test_depositRequest() public { +// uint256 amountIn = 1 * 10 ** 18; + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: XTZ, +// tokenOut: XTZ, +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: new DataTypes.ExecuteSwapParamsData[](0) +// }); + +// vm.startPrank(user1); +// IERC20(XTZ).approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(superloop), address(depositManager), XTZ, amountIn, DataTypes.DepositType.REQUESTED, swapParams +// ); + +// DataTypes.DepositRequestData memory request = depositManager.depositRequest(1); +// assertEq(request.amount, amountIn); +// assertEq(request.user, user1); +// } + +// function test_depositRequestWithSwap() public { +// uint256 amountIn = 1 * 10 ** 6; + +// // approve vault router to spend USDC +// DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); + +// data[0] = DataTypes.ExecuteSwapParamsData({ +// target: address(USDC), +// data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) +// }); +// data[1] = DataTypes.ExecuteSwapParamsData({ +// target: address(ROUTER), +// data: abi.encodeWithSelector( +// IRouter.exactInputSingle.selector, +// IRouter.ExactInputSingleParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// fee: 500, +// recipient: address(dexModule), +// amountIn: amountIn, +// amountOutMinimum: 0, +// sqrtPriceLimitX96: 0 +// }) +// ) +// }); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: data +// }); + +// vm.startPrank(user1); +// IERC20(USDC).approve(address(vaultRouter), amountIn); +// vaultRouter.depositWithToken( +// address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.REQUESTED, swapParams +// ); + +// DataTypes.DepositRequestData memory request = depositManager.depositRequest(1); +// assertGt(request.amount, 0); +// assertEq(request.user, user1); +// } + +// function test_frontrunningProtection() public { +// uint256 amountIn = 1 * 10 ** 6; + +// // approve vault router to spend USDC +// DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); + +// data[0] = DataTypes.ExecuteSwapParamsData({ +// target: address(USDC), +// data: abi.encodeWithSelector(IERC20.approve.selector, address(ROUTER), amountIn) +// }); +// data[1] = DataTypes.ExecuteSwapParamsData({ +// target: address(ROUTER), +// data: abi.encodeWithSelector( +// IRouter.exactInputSingle.selector, +// IRouter.ExactInputSingleParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// fee: 500, +// recipient: address(dexModule), +// amountIn: amountIn, +// amountOutMinimum: 0, +// sqrtPriceLimitX96: 0 +// }) +// ) +// }); + +// DataTypes.ExecuteSwapParams memory swapParams = DataTypes.ExecuteSwapParams({ +// tokenIn: USDC, +// tokenOut: XTZ, +// amountIn: amountIn, +// maxAmountIn: amountIn, +// minAmountOut: 0, +// data: data +// }); + +// vm.prank(user1); +// IERC20(USDC).approve(address(vaultRouter), amountIn); + +// vm.prank(user2); +// vm.expectRevert(); +// vaultRouter.depositWithToken( +// address(superloop), address(depositManager), USDC, amountIn, DataTypes.DepositType.INSTANT, swapParams +// ); +// } +// } diff --git a/test/modules/aave/AaveV3BorrowModule.t.sol b/test/modules/aave/AaveV3BorrowModule.t.sol index 094d4b0..9e2c590 100644 --- a/test/modules/aave/AaveV3BorrowModule.t.sol +++ b/test/modules/aave/AaveV3BorrowModule.t.sol @@ -7,10 +7,12 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {console} from "forge-std/console.sol"; contract AaveV3BorrowModuleTest is TestBase { @@ -30,10 +32,10 @@ contract AaveV3BorrowModuleTest is TestBase { modules[2] = address(borrowModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -60,22 +62,17 @@ contract AaveV3BorrowModuleTest is TestBase { user = makeAddr("user"); vm.label(user, "user"); vm.label(address(superloop), "superloop"); - - vm.startPrank(POOL_ADMIN); - IPoolConfigurator(POOL_CONFIGURATOR).setReserveFlashLoaning(ST_XTZ, true); - IPoolConfigurator(POOL_CONFIGURATOR).setSupplyCap(ST_XTZ, 10000000); - vm.stopPrank(); } function test_BorrowBasicFlow() public { _supply(); // Arrange - uint256 borrowAmount = 5 * 10 ** 18; // 5 XTZ + uint256 borrowAmount = 5 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // 5 borrowAsset // Create withdraw params DataTypes.AaveV3ActionParams memory borrowParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: borrowAmount}); + DataTypes.AaveV3ActionParams({asset: environment.borrowAssets[0], amount: borrowAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -84,27 +81,29 @@ contract AaveV3BorrowModuleTest is TestBase { data: abi.encodeWithSelector(borrowModule.execute.selector, borrowParams) }); - (,, uint256 currentBorrow,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 currentBalance = IERC20(XTZ).balanceOf(address(superloop)); + (,, uint256 currentBorrow,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + uint256 currentBalance = IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)); vm.prank(admin); superloop.operate(moduleExecutionData); - (,, uint256 finalBorrow,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 finalBalance = IERC20(XTZ).balanceOf(address(superloop)); + (,, uint256 finalBorrow,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + uint256 finalBalance = IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)); assertTrue(finalBorrow > currentBorrow); assertTrue(finalBalance > currentBalance); } function _supply() internal { - vm.startPrank(STXTZ_WHALE); - IERC20(ST_XTZ).transfer(address(superloop), 1000 * 10 ** 6); + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); - uint256 supplyAmount = 10 * 10 ** 6; // 10 ST_XTZ + uint256 supplyAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset DataTypes.AaveV3ActionParams memory supplyParams = - DataTypes.AaveV3ActionParams({asset: ST_XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ diff --git a/test/modules/aave/AaveV3EmodeModule.t.sol b/test/modules/aave/AaveV3EmodeModule.t.sol index a0c6721..34ba553 100644 --- a/test/modules/aave/AaveV3EmodeModule.t.sol +++ b/test/modules/aave/AaveV3EmodeModule.t.sol @@ -5,8 +5,9 @@ pragma solidity ^0.8.13; import {TestBase} from "../../core/TestBase.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; import {console} from "forge-std/console.sol"; @@ -26,10 +27,10 @@ contract AaveV3EmodeModuleTest is TestBase { modules[0] = address(emodeModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -61,7 +62,7 @@ contract AaveV3EmodeModuleTest is TestBase { function test_EmodeBasicFlow() public { uint256 currentEmodeCategory = pool.getUserEMode(address(superloop)); - console.log("currentEmodeCategory", currentEmodeCategory); + assertEq(currentEmodeCategory, 0); DataTypes.AaveV3EmodeParams memory params = DataTypes.AaveV3EmodeParams({emodeCategory: 1}); diff --git a/test/modules/aave/AaveV3FlashloanModule.t.sol b/test/modules/aave/AaveV3FlashloanModule.t.sol index 7142450..e48f6ec 100644 --- a/test/modules/aave/AaveV3FlashloanModule.t.sol +++ b/test/modules/aave/AaveV3FlashloanModule.t.sol @@ -7,8 +7,9 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; contract AaveV3FlashloanModuleTest is TestBase { @@ -26,10 +27,10 @@ contract AaveV3FlashloanModuleTest is TestBase { modules[0] = address(flashloanModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -52,7 +53,7 @@ contract AaveV3FlashloanModuleTest is TestBase { ); superloop = Superloop(payable(address(proxy))); - bytes32 key = keccak256(abi.encodePacked(POOL, IFlashLoanSimpleReceiver.executeOperation.selector)); + bytes32 key = keccak256(abi.encodePacked(environment.pool, IFlashLoanSimpleReceiver.executeOperation.selector)); superloop.setCallbackHandler(key, address(callbackHandler)); vm.stopPrank(); user = makeAddr("user"); @@ -62,17 +63,17 @@ contract AaveV3FlashloanModuleTest is TestBase { } function test_FlashloanBasicFlow() public { - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(address(superloop), 10 * 10 ** 18); + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); // Arrange - uint256 flashloanAmount = 1000 * 10 ** 18; // 1000 XTZ + uint256 flashloanAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset uint16 referralCode = 0; // Create flashloan params DataTypes.AaveV3FlashloanParams memory flashloanParams = DataTypes.AaveV3FlashloanParams({ - asset: XTZ, + asset: environment.vaultAsset, amount: flashloanAmount, referralCode: referralCode, callbackExecutionData: "" // No additional execution data for simple return @@ -87,14 +88,18 @@ contract AaveV3FlashloanModuleTest is TestBase { }); // Record initial balances - uint256 initialXTZBalance = IERC20(XTZ).balanceOf(address(superloop)); + uint256 initialVaultAssetBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); // Act vm.prank(admin); superloop.operate(moduleExecutionData); // Assert - uint256 finalXTZBalance = IERC20(XTZ).balanceOf(address(superloop)); - assertLt(finalXTZBalance, initialXTZBalance, "Balance should decrease slightly after flashloan due to premium"); + uint256 finalVaultAssetBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); + assertLt( + finalVaultAssetBalance, + initialVaultAssetBalance, + "Balance should decrease slightly after flashloan due to premium" + ); } } diff --git a/test/modules/aave/AaveV3RepayModule.t.sol b/test/modules/aave/AaveV3RepayModule.t.sol index 8c44a02..7a0c2cf 100644 --- a/test/modules/aave/AaveV3RepayModule.t.sol +++ b/test/modules/aave/AaveV3RepayModule.t.sol @@ -7,10 +7,12 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {console} from "forge-std/console.sol"; contract AaveV3RepayModuleTest is TestBase { @@ -31,10 +33,10 @@ contract AaveV3RepayModuleTest is TestBase { modules[3] = address(repayModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -61,11 +63,6 @@ contract AaveV3RepayModuleTest is TestBase { user = makeAddr("user"); vm.label(user, "user"); vm.label(address(superloop), "superloop"); - - vm.startPrank(POOL_ADMIN); - IPoolConfigurator(POOL_CONFIGURATOR).setReserveFlashLoaning(ST_XTZ, true); - IPoolConfigurator(POOL_CONFIGURATOR).setSupplyCap(ST_XTZ, 10000000); - vm.stopPrank(); } function test_RepayBasicFlow() public { @@ -73,11 +70,11 @@ contract AaveV3RepayModuleTest is TestBase { _borrow(); // Arrange - uint256 repayAmount = 5 * 10 ** 18; // 1000 XTZ + uint256 repayAmount = 5 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // 5 borrowAsset // Create repay params DataTypes.AaveV3ActionParams memory repayParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: repayAmount}); + DataTypes.AaveV3ActionParams({asset: environment.borrowAssets[0], amount: repayAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -86,27 +83,29 @@ contract AaveV3RepayModuleTest is TestBase { data: abi.encodeWithSelector(repayModule.execute.selector, repayParams) }); - (,, uint256 currentBorrow,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 currentBalance = IERC20(XTZ).balanceOf(address(superloop)); + (,, uint256 currentBorrow,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + uint256 currentBalance = IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)); vm.prank(admin); superloop.operate(moduleExecutionData); - (,, uint256 finalBorrow,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 finalBalance = IERC20(XTZ).balanceOf(address(superloop)); + (,, uint256 finalBorrow,,,,,,) = + poolDataProvider.getUserReserveData(environment.borrowAssets[0], address(superloop)); + uint256 finalBalance = IERC20(environment.borrowAssets[0]).balanceOf(address(superloop)); assertTrue(finalBorrow < currentBorrow); assertTrue(finalBalance < currentBalance); } function _supply() internal { - vm.startPrank(STXTZ_WHALE); - IERC20(ST_XTZ).transfer(address(superloop), 1000 * 10 ** 6); + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); - uint256 supplyAmount = 10 * 10 ** 6; // 10 ST_XTZ + uint256 supplyAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset DataTypes.AaveV3ActionParams memory supplyParams = - DataTypes.AaveV3ActionParams({asset: ST_XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -120,11 +119,11 @@ contract AaveV3RepayModuleTest is TestBase { function _borrow() internal { // Arrange - uint256 borrowAmount = 5 * 10 ** 18; // 5 XTZ + uint256 borrowAmount = 5 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // 5 borrowAsset // Create withdraw params DataTypes.AaveV3ActionParams memory borrowParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: borrowAmount}); + DataTypes.AaveV3ActionParams({asset: environment.borrowAssets[0], amount: borrowAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ diff --git a/test/modules/aave/AaveV3SupplyModule.t.sol b/test/modules/aave/AaveV3SupplyModule.t.sol index 80f3b72..aa9a1b4 100644 --- a/test/modules/aave/AaveV3SupplyModule.t.sol +++ b/test/modules/aave/AaveV3SupplyModule.t.sol @@ -7,8 +7,9 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {console} from "forge-std/console.sol"; @@ -27,10 +28,10 @@ contract AaveV3SupplyModuleTest is TestBase { modules[0] = address(supplyModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -60,16 +61,16 @@ contract AaveV3SupplyModuleTest is TestBase { } function test_SupplyBasicFlow() public { - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(address(superloop), 1000 * 10 ** 18); + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); // Arrange - uint256 supplyAmount = 1000 * 10 ** 18; // 1000 XTZ + uint256 supplyAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset // Create supply params DataTypes.AaveV3ActionParams memory supplyParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); // Create module execution data DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); @@ -80,18 +81,19 @@ contract AaveV3SupplyModuleTest is TestBase { }); // current supply - (uint256 currentSupply,,,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 currentBalance = IERC20(XTZ).balanceOf(address(superloop)); + (uint256 currentSupply,,,,,,,,) = + poolDataProvider.getUserReserveData(environment.vaultAsset, address(superloop)); + uint256 currentBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); // Act vm.prank(admin); superloop.operate(moduleExecutionData); // Assert - (uint256 finalSupply,,,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 finalBalance = IERC20(XTZ).balanceOf(address(superloop)); + (uint256 finalSupply,,,,,,,,) = poolDataProvider.getUserReserveData(environment.vaultAsset, address(superloop)); + uint256 finalBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); - assertEq(finalSupply, currentSupply + supplyAmount); - assertEq(finalBalance, currentBalance - supplyAmount); + assertApproxEqAbs(finalSupply, currentSupply + supplyAmount, 1); + assertApproxEqAbs(finalBalance, currentBalance - supplyAmount, 1); } } diff --git a/test/modules/aave/AaveV3WithdrawModule.t.sol b/test/modules/aave/AaveV3WithdrawModule.t.sol index dafc868..c136d43 100644 --- a/test/modules/aave/AaveV3WithdrawModule.t.sol +++ b/test/modules/aave/AaveV3WithdrawModule.t.sol @@ -7,8 +7,9 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {console} from "forge-std/console.sol"; @@ -28,10 +29,10 @@ contract AaveV3WithdrawModuleTest is TestBase { modules[1] = address(withdrawModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -64,11 +65,11 @@ contract AaveV3WithdrawModuleTest is TestBase { _supply(); // Arrange - uint256 withdrawAmount = 500 * 10 ** 18; // 1000 XTZ + uint256 withdrawAmount = 500 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset // Create withdraw params DataTypes.AaveV3ActionParams memory withdrawParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: withdrawAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: withdrawAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -77,27 +78,28 @@ contract AaveV3WithdrawModuleTest is TestBase { data: abi.encodeWithSelector(withdrawModule.execute.selector, withdrawParams) }); - (uint256 currentSupply,,,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 currentBalance = IERC20(XTZ).balanceOf(address(superloop)); + (uint256 currentSupply,,,,,,,,) = + poolDataProvider.getUserReserveData(environment.vaultAsset, address(superloop)); + uint256 currentBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); vm.prank(admin); superloop.operate(moduleExecutionData); - (uint256 finalSupply,,,,,,,,) = poolDataProvider.getUserReserveData(XTZ, address(superloop)); - uint256 finalBalance = IERC20(XTZ).balanceOf(address(superloop)); + (uint256 finalSupply,,,,,,,,) = poolDataProvider.getUserReserveData(environment.vaultAsset, address(superloop)); + uint256 finalBalance = IERC20(environment.vaultAsset).balanceOf(address(superloop)); assertTrue(finalSupply <= currentSupply); assertTrue(finalBalance > currentBalance); } function _supply() internal { - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(address(superloop), 1000 * 10 ** 18); + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); - uint256 supplyAmount = 1000 * 10 ** 18; // 1000 XTZ + uint256 supplyAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset DataTypes.AaveV3ActionParams memory supplyParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ diff --git a/test/modules/dex/UniversalDexModule.t.sol b/test/modules/dex/UniversalDexModule.t.sol index 03cbc8c..853d4d3 100644 --- a/test/modules/dex/UniversalDexModule.t.sol +++ b/test/modules/dex/UniversalDexModule.t.sol @@ -9,10 +9,12 @@ import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {TestBase} from "../../core/TestBase.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IRouter} from "../../../src/mock/MockIRouter.sol"; +// not maintained, may fail contract UniversalDexModuleTest is TestBase { Superloop public superloopImplementation; ProxyAdmin public proxyAdmin; @@ -28,10 +30,10 @@ contract UniversalDexModuleTest is TestBase { modules[0] = address(dexModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -61,8 +63,8 @@ contract UniversalDexModuleTest is TestBase { } function test_executeSwap() public { - address tokenIn = USDT; - address tokenOut = USDC; + address tokenIn = USDT_ETLK; + address tokenOut = USDC_ETLK; uint256 amountIn = 1000 * 10 ** 6; uint256 maxAmountIn = 1000 * 10 ** 6; @@ -70,21 +72,21 @@ contract UniversalDexModuleTest is TestBase { DataTypes.ExecuteSwapParamsData[] memory swapParamsData = new DataTypes.ExecuteSwapParamsData[](2); swapParamsData[0] = DataTypes.ExecuteSwapParamsData({ - target: USDT, - data: abi.encodeWithSelector(IERC20.approve.selector, ROUTER, amountIn) + target: tokenIn, data: abi.encodeWithSelector(IERC20.approve.selector, environment.router, amountIn) }); swapParamsData[1] = DataTypes.ExecuteSwapParamsData({ - target: ROUTER, + target: environment.router, data: abi.encodeWithSelector( IRouter.exactInputSingle.selector, IRouter.ExactInputSingleParams({ - tokenIn: USDT, - tokenOut: USDC, + tokenIn: tokenIn, + tokenOut: tokenOut, fee: 100, recipient: address(superloop), amountIn: amountIn, amountOutMinimum: 0, - sqrtPriceLimitX96: 0 + sqrtPriceLimitX96: 0, + deadline: block.timestamp + 100 }) ) }); @@ -105,25 +107,25 @@ contract UniversalDexModuleTest is TestBase { data: abi.encodeWithSelector(dexModule.execute.selector, swapParams) }); - vm.startPrank(USDT_WHALE); - IERC20(USDT).transfer(address(superloop), amountIn); + vm.startPrank(USDT_ETLK_WHALE); + IERC20(tokenIn).transfer(address(superloop), amountIn); vm.stopPrank(); vm.startPrank(admin); superloop.operate(moduleExecutionData); vm.stopPrank(); - uint256 usdcBalance = IERC20(USDC).balanceOf(address(superloop)); + uint256 usdcBalance = IERC20(tokenOut).balanceOf(address(superloop)); assertGt(usdcBalance, 0); } function test_executeSwapAndExit() public { - vm.startPrank(USDT_WHALE); - IERC20(USDT).transfer(address(user), 1000 * 10 ** 6); - vm.stopPrank(); + address tokenIn = USDT_ETLK; + address tokenOut = USDC_ETLK; - address tokenIn = USDT; - address tokenOut = USDC; + vm.startPrank(USDT_ETLK_WHALE); + IERC20(tokenIn).transfer(address(user), 1000 * 10 ** 6); + vm.stopPrank(); uint256 amountIn = 1000 * 10 ** 6; uint256 maxAmountIn = 1000 * 10 ** 6; @@ -131,21 +133,21 @@ contract UniversalDexModuleTest is TestBase { DataTypes.ExecuteSwapParamsData[] memory data = new DataTypes.ExecuteSwapParamsData[](2); data[0] = DataTypes.ExecuteSwapParamsData({ - target: USDT, - data: abi.encodeWithSelector(IERC20.approve.selector, ROUTER, amountIn) + target: tokenIn, data: abi.encodeWithSelector(IERC20.approve.selector, environment.router, amountIn) }); data[1] = DataTypes.ExecuteSwapParamsData({ - target: ROUTER, + target: environment.router, data: abi.encodeWithSelector( IRouter.exactInputSingle.selector, IRouter.ExactInputSingleParams({ - tokenIn: USDT, - tokenOut: USDC, + tokenIn: tokenIn, + tokenOut: tokenOut, fee: 100, recipient: address(dexModule), amountIn: amountIn, amountOutMinimum: 0, - sqrtPriceLimitX96: 0 + sqrtPriceLimitX96: 0, + deadline: block.timestamp + 100 }) ) }); @@ -160,12 +162,12 @@ contract UniversalDexModuleTest is TestBase { }); vm.startPrank(user); - IERC20(USDT).approve(address(dexModule), amountIn); + IERC20(tokenIn).approve(address(dexModule), amountIn); uint256 amountOut = dexModule.executeAndExit(params, user); vm.stopPrank(); - uint256 usdcBalanceUser = IERC20(USDC).balanceOf(address(user)); + uint256 usdcBalanceUser = IERC20(tokenOut).balanceOf(address(user)); assertEq(usdcBalanceUser, amountOut); } } diff --git a/test/modules/fallback/AaveV3PreliquidationFallbackHandler.t.sol b/test/modules/fallback/AaveV3PreliquidationFallbackHandler.t.sol index 6a4ae79..6de8175 100644 --- a/test/modules/fallback/AaveV3PreliquidationFallbackHandler.t.sol +++ b/test/modules/fallback/AaveV3PreliquidationFallbackHandler.t.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.13; import {TestBase} from "../../core/TestBase.sol"; -import {AaveV3PreliquidationFallbackHandler} from - "../../../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; +import { + AaveV3PreliquidationFallbackHandler +} from "../../../src/modules/fallback/AaveV3PreliquidationFallbackHandler.sol"; import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Errors} from "../../../src/common/Errors.sol"; import {console} from "forge-std/console.sol"; @@ -11,7 +12,9 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IAaveOracle} from "aave-v3-core/contracts/interfaces/IAaveOracle.sol"; import {IPoolAddressesProvider} from "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +// not maintained right now. will be updated soon. contract AaveV3PreliquidationFallbackHandlerTest is TestBase { AaveV3PreliquidationFallbackHandler public preliquidation; @@ -20,14 +23,14 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { id = bytes32("1"); LLTV = (7800 * WAD) / BPS; preliquidation = new AaveV3PreliquidationFallbackHandler( - AAVE_V3_POOL_ADDRESSES_PROVIDER, + environment.poolAddressesProvider, 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, 2, 8, DataTypes.AaveV3PreliquidationParamsInit({ id: id, - lendReserve: ST_XTZ, - borrowReserve: XTZ, + lendReserve: environment.lendAssets[0], + borrowReserve: environment.borrowAssets[0], preLltv: PRE_LLTV, preCF1: PRE_CF1, preCF2: PRE_CF2, @@ -40,8 +43,8 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidationParams() public view { DataTypes.AaveV3PreliquidationParams memory preliquidationParams = preliquidation.preliquidationParams(id, DataTypes.CallType.DELEGATECALL); - assertEq(preliquidationParams.lendReserve, ST_XTZ); - assertEq(preliquidationParams.borrowReserve, XTZ); + assertEq(preliquidationParams.lendReserve, environment.lendAssets[0]); + assertEq(preliquidationParams.borrowReserve, environment.borrowAssets[0]); assertEq(preliquidationParams.Lltv, LLTV); assertEq(preliquidationParams.preLltv, PRE_LLTV); assertEq(preliquidationParams.preCF1, PRE_CF1); @@ -51,66 +54,121 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { } function test_preliquidation() public { - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 70 * 10 ** 18); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 70 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); - uint256 stxtzBalanceBefore = IERC20(ST_XTZ).balanceOf(address(this)); - uint256 xtzBalanceBefore = IERC20(XTZ).balanceOf(address(this)); + uint256 lendAssetBalanceBefore = IERC20(environment.lendAssets[0]).balanceOf(address(this)); + uint256 borrowAssetBalanceBefore = IERC20(environment.borrowAssets[0]).balanceOf(address(this)); - IAaveOracle oracle = IAaveOracle(IPoolAddressesProvider(AAVE_V3_POOL_ADDRESSES_PROVIDER).getPriceOracle()); + IAaveOracle oracle = IAaveOracle(IPoolAddressesProvider(environment.poolAddressesProvider).getPriceOracle()); // usd value of debt repaid uint256 debtRepaid = 10 * 10 ** 18; - uint256 debtPriceUsd = oracle.getAssetPrice(XTZ); - uint256 debtRepaidUSD = debtRepaid * debtPriceUsd / (10 ** 18); + uint256 debtPriceUsd = oracle.getAssetPrice(environment.borrowAssets[0]); + uint256 debtRepaidUSD = + (debtRepaid * debtPriceUsd) / (10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); preliquidation.preliquidate( id, DataTypes.CallType.DELEGATECALL, DataTypes.AaveV3ExecutePreliquidationParams({user: address(preliquidation), debtToCover: 10 * 10 ** 18}) ); - uint256 stxtzBalanceAfter = IERC20(ST_XTZ).balanceOf(address(this)); - uint256 xtzBalanceAfter = IERC20(XTZ).balanceOf(address(this)); + uint256 lendAssetBalanceAfter = IERC20(environment.lendAssets[0]).balanceOf(address(this)); + uint256 borrowAssetBalanceAfter = IERC20(environment.borrowAssets[0]).balanceOf(address(this)); - uint256 collateralToSieze = stxtzBalanceAfter - stxtzBalanceBefore; - uint256 collateralPriceUsd = oracle.getAssetPrice(ST_XTZ); - uint256 collateralToSiezeUSD = collateralToSieze * collateralPriceUsd / (10 ** 6); + uint256 collateralToSieze = lendAssetBalanceAfter - lendAssetBalanceBefore; + uint256 collateralPriceUsd = oracle.getAssetPrice(environment.lendAssets[0]); + uint256 collateralToSiezeUSD = (collateralToSieze * collateralPriceUsd) / (10 ** 6); // compare collateralToSiezeUSD and debtRepaidUSD uint256 incentiveWAD = ((collateralToSiezeUSD) * WAD) / debtRepaidUSD; assertTrue(incentiveWAD > PRE_IF1 && incentiveWAD < PRE_IF2); - assertEq(xtzBalanceAfter, xtzBalanceBefore - 10 * 10 ** 18); + assertEq( + borrowAssetBalanceAfter, + borrowAssetBalanceBefore - 10 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); } // ============ REVERTING CASES ============ function test_preliquidation_revert_invalid_lltv() public { - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 70 * 10 ** 18); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 70 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); // updat emode ltv to 9900 => it should revert - vm.prank(POOL_ADMIN); - IPoolConfigurator(POOL_CONFIGURATOR).configureReserveAsCollateral(ST_XTZ, 9000, 9200, 10100); + vm.prank(environment.poolAdmin); + IPoolConfigurator(environment.poolConfigurator) + .configureReserveAsCollateral(environment.lendAssets[0], 9000, 9200, 10100); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); vm.expectRevert(bytes(Errors.AAVE_V3_PRELIQUIDATION_INVALID_LLTV)); preliquidation.preliquidate( @@ -121,17 +179,42 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { } function test_preliquidate_revert_invalid_id() public { - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 70 * 10 ** 18); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 70 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); bytes32 wrongId = bytes32("wrong_id"); vm.expectRevert(bytes(Errors.PRELIQUIDATION_INVALID_ID)); @@ -143,17 +226,42 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { } function test_preliquidate_revert_invalid_user() public { - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 70 * 10 ** 18); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 70 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); address wrongUser = makeAddr("wrongUser"); vm.expectRevert(bytes(Errors.PRELIQUIDATION_INVALID_USER)); @@ -166,30 +274,75 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidate_revert_possible_bad_debt() public { // Set up position with LTV > LLTV (bad debt scenario) - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 80 * 10 ** 18); // High borrow amount + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 80 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); // High borrow amount vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); vm.expectRevert(bytes(Errors.INSUFFICIENT_CASH_SHORTFALL)); - pool.borrow(XTZ, 80 * 10 ** 18, 2, 0, address(preliquidation)); + pool.borrow( + environment.borrowAssets[0], + 80 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); } function test_preliquidate_revert_not_in_preliquidation_state() public { // Set up position with LTV <= preLltv (not in preliquidation state) - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(preliquidation), 40 * 10 ** 18); // Low borrow amount + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(preliquidation), + 40 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); // Low borrow amount vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 40 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 40 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); vm.expectRevert(bytes(Errors.PRELIQUIDATION_NOT_IN_PRELIQUIDATION_STATE)); preliquidation.preliquidate( @@ -203,23 +356,38 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidate_edge_case_ltv_equals_prelltv() public { // Set up position with LTV = preLltv (exactly at the boundary) - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); // Calculate exact borrow amount to achieve LTV = preLltv // We need: borrowUsdWAD = collateralUsdWAD * preLltv // For simplicity, we'll use a value that should be close to the boundary uint256 borrowAmount = 50 * 10 ** 18; // This should be close to preLltv boundary - deal(XTZ, address(preliquidation), borrowAmount); + deal(environment.borrowAssets[0], address(preliquidation), borrowAmount); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, borrowAmount, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow(environment.borrowAssets[0], borrowAmount, 2, 0, address(preliquidation)); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); // This should revert because LTV <= preLltv vm.expectRevert(bytes(Errors.PRELIQUIDATION_NOT_IN_PRELIQUIDATION_STATE)); @@ -232,21 +400,36 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidate_edge_case_ltv_equals_lltv() public { // Set up position with LTV = Lltv (exactly at the liquidation threshold) - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); // Calculate exact borrow amount to achieve LTV = Lltv uint256 borrowAmount = 74 * 10 ** 18; // This should be close to Lltv boundary - deal(XTZ, address(preliquidation), borrowAmount); + deal(environment.borrowAssets[0], address(preliquidation), borrowAmount); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, borrowAmount, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow(environment.borrowAssets[0], borrowAmount, 2, 0, address(preliquidation)); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); // This should succeed as it's exactly at the boundary preliquidation.preliquidate( @@ -257,53 +440,92 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { } function test_preliquidate_edge_case_trying_to_liquidate_max() public { - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - deal(XTZ, address(this), 70 * 10 ** 18); + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + deal( + environment.borrowAssets[0], + address(this), + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, 70 * 10 ** 18, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow( + environment.borrowAssets[0], + 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(), + 2, + 0, + address(preliquidation) + ); vm.stopPrank(); // try to liquidate 70xtz, but i should be able to do only till close factor - uint256 xtzBalanceBefore = IERC20(XTZ).balanceOf(address(this)); - uint256 stxtzBalanceBefore = IERC20(ST_XTZ).balanceOf(address(this)); + uint256 borrowAssetBalanceBefore = IERC20(environment.borrowAssets[0]).balanceOf(address(this)); + uint256 lendAssetBalanceBefore = IERC20(environment.lendAssets[0]).balanceOf(address(this)); - IERC20(XTZ).approve(address(preliquidation), 70 * 10 ** 18); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 70 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); preliquidation.preliquidate( id, DataTypes.CallType.DELEGATECALL, DataTypes.AaveV3ExecutePreliquidationParams({user: address(preliquidation), debtToCover: 70 * 10 ** 18}) ); - uint256 xtzBalanceAfter = IERC20(XTZ).balanceOf(address(this)); - uint256 stxtzBalanceAfter = IERC20(ST_XTZ).balanceOf(address(this)); + uint256 borrowAssetBalanceAfter = IERC20(environment.borrowAssets[0]).balanceOf(address(this)); + uint256 lendAssetBalanceAfter = IERC20(environment.lendAssets[0]).balanceOf(address(this)); - uint256 collateralSeized = stxtzBalanceAfter - stxtzBalanceBefore; + uint256 collateralSeized = lendAssetBalanceAfter - lendAssetBalanceBefore; - assertTrue(collateralSeized < 40 * 10 ** 6); - assertTrue(xtzBalanceBefore - xtzBalanceAfter < 40 * 10 ** 18); + assertTrue(collateralSeized < 40 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + assertTrue( + borrowAssetBalanceBefore - borrowAssetBalanceAfter + < 40 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); } // ============ LTV IN RANGE TESTS ============ function test_preliquidate_ltv_low_range() public { // Set up position with LTV slightly above preLltv - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - uint256 borrowAmount = 55 * 10 ** 18; // Between preLltv and Lltv + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + uint256 borrowAmount = 55 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // Between preLltv and Lltv - deal(XTZ, address(preliquidation), borrowAmount); + deal(environment.borrowAssets[0], address(preliquidation), borrowAmount); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, borrowAmount, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow(environment.borrowAssets[0], borrowAmount, 2, 0, address(preliquidation)); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); preliquidation.preliquidate( id, @@ -314,19 +536,34 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidate_ltv_mid_range() public { // Set up position with LTV in the middle of preLltv and Lltv - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - uint256 borrowAmount = 65 * 10 ** 18; // Middle of the range + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + uint256 borrowAmount = 65 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // Middle of the range - deal(XTZ, address(preliquidation), borrowAmount); + deal(environment.borrowAssets[0], address(preliquidation), borrowAmount); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, borrowAmount, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow(environment.borrowAssets[0], borrowAmount, 2, 0, address(preliquidation)); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); preliquidation.preliquidate( id, @@ -337,19 +574,34 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { function test_preliquidate_ltv_high_range() public { // Set up position with LTV close to Lltv - deal(ST_XTZ, address(preliquidation), 100 * 10 ** 6); - uint256 borrowAmount = 74 * 10 ** 18; // Close to Lltv + deal( + environment.lendAssets[0], + address(preliquidation), + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals() + ); + uint256 borrowAmount = 74 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals(); // Close to Lltv - deal(XTZ, address(preliquidation), borrowAmount); + deal(environment.borrowAssets[0], address(preliquidation), borrowAmount); vm.startPrank(address(preliquidation)); - IERC20(ST_XTZ).approve(address(pool), 100 * 10 ** 6); - pool.supply(ST_XTZ, 100 * 10 ** 6, address(preliquidation), 0); - pool.borrow(XTZ, borrowAmount, 2, 0, address(preliquidation)); + IERC20(environment.lendAssets[0]) + .approve(address(pool), 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals()); + pool.supply( + environment.lendAssets[0], + 100 * 10 ** IERC20Metadata(environment.lendAssets[0]).decimals(), + address(preliquidation), + 0 + ); + pool.borrow(environment.borrowAssets[0], borrowAmount, 2, 0, address(preliquidation)); vm.stopPrank(); - deal(XTZ, address(this), 12 * 10 ** 18); - IERC20(XTZ).approve(address(preliquidation), 12 * 10 ** 18); + deal( + environment.borrowAssets[0], + address(this), + 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals() + ); + IERC20(environment.borrowAssets[0]) + .approve(address(preliquidation), 12 * 10 ** IERC20Metadata(environment.borrowAssets[0]).decimals()); preliquidation.preliquidate( id, @@ -365,14 +617,14 @@ contract AaveV3PreliquidationFallbackHandlerTest is TestBase { vm.prank(address(newPreLiq)); pool.setUserEMode(3); preliquidation = new AaveV3PreliquidationFallbackHandler( - AAVE_V3_POOL_ADDRESSES_PROVIDER, + environment.poolAddressesProvider, newPreLiq, 2, 8, DataTypes.AaveV3PreliquidationParamsInit({ id: id, - lendReserve: ST_XTZ, - borrowReserve: XTZ, + lendReserve: environment.lendAssets[0], + borrowReserve: environment.borrowAssets[0], preLltv: PRE_LLTV, preCF1: PRE_CF1, preCF2: PRE_CF2, diff --git a/test/modules/helpers/UnwrapModule.t.sol b/test/modules/helpers/UnwrapModule.t.sol index 4b7cd1c..952e2bb 100644 --- a/test/modules/helpers/UnwrapModule.t.sol +++ b/test/modules/helpers/UnwrapModule.t.sol @@ -7,8 +7,9 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {console} from "forge-std/console.sol"; @@ -27,10 +28,10 @@ contract UnwrapModuleTest is TestBase { modules[0] = address(unwrapModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -60,16 +61,19 @@ contract UnwrapModuleTest is TestBase { } function test_SupplyBasicFlow() public { - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(address(superloop), 1000 * 10 ** 18); + // works only on etherlink + if (environment.chainId != 42793) return; + + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); // Arrange - uint256 supplyAmount = 100 * 10 ** 18; // 1000 XTZ + uint256 supplyAmount = 100 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset // Create supply params DataTypes.AaveV3ActionParams memory unwarpParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); // Create module execution data DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); @@ -80,14 +84,14 @@ contract UnwrapModuleTest is TestBase { }); // current supply - uint256 currentBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 currentBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 currentBalanceUnderlyingToken = address(superloop).balance; // Act vm.prank(admin); superloop.operate(moduleExecutionData); - uint256 finalBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 finalBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 finalBalanceUnderlyingToken = address(superloop).balance; console.log("currentBalanceWrappedToken", currentBalanceWrappedToken); diff --git a/test/modules/helpers/WrapModule.t.sol b/test/modules/helpers/WrapModule.t.sol index 9c9261e..754c1c2 100644 --- a/test/modules/helpers/WrapModule.t.sol +++ b/test/modules/helpers/WrapModule.t.sol @@ -7,8 +7,9 @@ import {IFlashLoanSimpleReceiver} from "aave-v3-core/contracts/flashloan/interfa import {DataTypes} from "../../../src/common/DataTypes.sol"; import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {console} from "forge-std/console.sol"; @@ -28,10 +29,10 @@ contract WrapModuleTest is TestBase { modules[1] = address(wrapModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: XTZ, - name: "XTZ Vault", - symbol: "XTZV", - supplyCap: 100000 * 10 ** 18, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -61,16 +62,19 @@ contract WrapModuleTest is TestBase { } function test_SupplyBasicFlow() public { - vm.startPrank(XTZ_WHALE); - IERC20(XTZ).transfer(address(superloop), 1000 * 10 ** 18); + // works only on etherlink + if (environment.chainId != 42793) return; + + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); vm.stopPrank(); - uint256 supplyAmount = 100 * 10 ** 18; + uint256 supplyAmount = 100 * 10 ** environment.vaultAssetDecimals; _executeUnwrap(supplyAmount); // warp DataTypes.AaveV3ActionParams memory wrapParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); moduleExecutionData[0] = DataTypes.ModuleExecutionData({ @@ -79,13 +83,13 @@ contract WrapModuleTest is TestBase { data: abi.encodeWithSelector(wrapModule.execute.selector, wrapParams) }); - uint256 currentBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 currentBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 currentBalanceUnderlyingToken = address(superloop).balance; vm.prank(admin); superloop.operate(moduleExecutionData); - uint256 finalBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 finalBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 finalBalanceUnderlyingToken = address(superloop).balance; assertEq(finalBalanceWrappedToken, currentBalanceWrappedToken + supplyAmount); @@ -95,7 +99,7 @@ contract WrapModuleTest is TestBase { function _executeUnwrap(uint256 supplyAmount) internal { // Create supply params DataTypes.AaveV3ActionParams memory unwarpParams = - DataTypes.AaveV3ActionParams({asset: XTZ, amount: supplyAmount}); + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: supplyAmount}); // Create module execution data DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); @@ -106,14 +110,14 @@ contract WrapModuleTest is TestBase { }); // current supply - uint256 currentBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 currentBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 currentBalanceUnderlyingToken = address(superloop).balance; // Act vm.prank(admin); superloop.operate(moduleExecutionData); - uint256 finalBalanceWrappedToken = IERC20(XTZ).balanceOf(address(superloop)); + uint256 finalBalanceWrappedToken = IERC20(environment.vaultAsset).balanceOf(address(superloop)); uint256 finalBalanceUnderlyingToken = address(superloop).balance; // Assert diff --git a/test/modules/merkl/MerklModule.t.sol b/test/modules/merkl/MerklModule.t.sol new file mode 100644 index 0000000..59a62a3 --- /dev/null +++ b/test/modules/merkl/MerklModule.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {TestBase} from "../../core/TestBase.sol"; +import {DataTypes} from "../../../src/common/DataTypes.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {DepositManager} from "../../../src/core/DepositManager/DepositManager.sol"; +import {console} from "forge-std/Test.sol"; +import {Errors} from "../../../src/common/Errors.sol"; +import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IDistributor} from "../../../src/modules/merkl/IDistributor.sol"; + +contract MerklModuleTest is TestBase { + Superloop public superloopImplementation; + ProxyAdmin public proxyAdmin; + IDistributor public distributor; + address public user; + + uint256 public AMOUNT; + + function setUp() public override { + super.setUp(); + + vm.startPrank(admin); + _deployModules(); + + address[] memory modules = new address[](1); + modules[0] = address(merklModule); + + DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, + minimumDepositAmount: 100, + instantWithdrawFee: 0, + superloopModuleRegistry: address(moduleRegistry), + modules: modules, + accountant: mockModule, + withdrawManager: mockModule, + depositManager: mockModule, + cashReserve: 1000, + vaultAdmin: admin, + treasury: treasury, + vaultOperator: admin + }); + superloopImplementation = new Superloop(); + proxyAdmin = new ProxyAdmin(address(this)); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(superloopImplementation), + address(proxyAdmin), + abi.encodeWithSelector(Superloop.initialize.selector, initData) + ); + superloop = Superloop(payable(address(proxy))); + vm.stopPrank(); + + distributor = IDistributor(environment.distributor); + + user = makeAddr("user"); + vm.label(user, "user"); + vm.label(address(superloop), "superloop"); + + AMOUNT = 1000 * 10 ** environment.vaultAssetDecimals; + address distributorAdmin = 0x435046800Fb9149eE65159721A92cB7d50a7534b; + + // set the root of distributor as hash of vault asset + deal(environment.vaultAsset, environment.distributor, AMOUNT); + bytes32 root = keccak256(abi.encode(address(superloop), environment.vaultAsset, AMOUNT)); + bytes32 ipfsHash = bytes32(0); + + vm.prank(distributorAdmin); + distributor.updateTree(IDistributor.MerkleTree(root, ipfsHash)); + + vm.warp(block.timestamp + 86400); + } + + function test_claim() public { + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + address[] memory users = new address[](1); + users[0] = address(superloop); + address[] memory tokens = new address[](1); + tokens[0] = environment.vaultAsset; + uint256[] memory amounts = new uint256[](1); + amounts[0] = AMOUNT; + bytes32[][] memory proofs = new bytes32[][](1); + + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(merklModule), + data: abi.encodeWithSelector( + merklModule.execute.selector, + DataTypes.MerklClaimParams({users: users, tokens: tokens, amounts: amounts, proofs: proofs}) + ) + }); + + vm.prank(admin); + superloop.operate(moduleExecutionData); + + assertEq(IERC20(environment.vaultAsset).balanceOf(address(superloop)), AMOUNT); + } +} diff --git a/test/modules/morpho/MorphoFlashloanModule.t.sol b/test/modules/morpho/MorphoFlashloanModule.t.sol new file mode 100644 index 0000000..3b98edc --- /dev/null +++ b/test/modules/morpho/MorphoFlashloanModule.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {TestBase} from "../../core/TestBase.sol"; +import {DataTypes} from "../../../src/common/DataTypes.sol"; +import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; +import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IMorphoFlashLoanCallback} from "morpho-blue/interfaces/IMorphoCallbacks.sol"; +import {MorphoFlashloanModule} from "../../../src/modules/morpho/MorphoFlashloanModule.sol"; + +contract MorphoFlashloanModuleTest is TestBase { + Superloop public superloopImplementation; + ProxyAdmin public proxyAdmin; + address public user; + + function setUp() public override { + super.setUp(); + + vm.startPrank(admin); + _deployModules(); + + address[] memory modules = new address[](1); + modules[0] = address(morphoFlashloanModule); + + DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, + minimumDepositAmount: 100, + instantWithdrawFee: 0, + superloopModuleRegistry: address(moduleRegistry), + modules: modules, + accountant: mockModule, + withdrawManager: mockModule, + depositManager: mockModule, + cashReserve: 1000, + vaultAdmin: admin, + treasury: treasury, + vaultOperator: admin + }); + superloopImplementation = new Superloop(); + proxyAdmin = new ProxyAdmin(address(this)); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(superloopImplementation), + address(proxyAdmin), + abi.encodeWithSelector(Superloop.initialize.selector, initData) + ); + superloop = Superloop(payable(address(proxy))); + + bytes32 key = + keccak256(abi.encodePacked(environment.morpho, IMorphoFlashLoanCallback.onMorphoFlashLoan.selector)); + superloop.setCallbackHandler(key, address(morphoCallbackHandler)); + vm.stopPrank(); + user = makeAddr("user"); + + vm.label(user, "user"); + vm.label(address(superloop), "superloop"); + } + + function test_FlashloanBasicFlow() public { + vm.startPrank(environment.vaultAssetWhale); + IERC20(environment.vaultAsset).transfer(address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); + vm.stopPrank(); + + // Arrange + uint256 flashloanAmount = 1000 * 10 ** environment.vaultAssetDecimals; // 1000 vaultAsset + + // Create flashloan params + DataTypes.MorphoFlashloanParams memory flashloanParams = DataTypes.MorphoFlashloanParams({ + asset: environment.vaultAsset, + amount: flashloanAmount, + callbackExecutionData: "" // No additional execution data for simple return + }); + + // Create module execution data + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(morphoFlashloanModule), + data: abi.encodeWithSelector(morphoFlashloanModule.execute.selector, flashloanParams) + }); + + // Act + vm.prank(admin); + // catch event MorphoFlashloanExecuted + vm.expectEmit(); + emit MorphoFlashloanModule.MorphoFlashloanExecuted(environment.vaultAsset, flashloanAmount, address(superloop)); + superloop.operate(moduleExecutionData); + } +} diff --git a/test/modules/stake/hyperliquid/HyperbeatStakingModule.t.sol b/test/modules/stake/hyperliquid/HyperbeatStakingModule.t.sol index 5ead9a5..d202bf2 100644 --- a/test/modules/stake/hyperliquid/HyperbeatStakingModule.t.sol +++ b/test/modules/stake/hyperliquid/HyperbeatStakingModule.t.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.13; -import {TestBase} from "./TestBase.sol"; +import {TestBase} from "../../../core/TestBase.sol"; import {DataTypes} from "../../../../src/common/DataTypes.sol"; import {Superloop} from "../../../../src/core/Superloop/Superloop.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; import {console} from "forge-std/console.sol"; @@ -15,11 +16,13 @@ contract HyperbeatStakingModuleTest is TestBase { Superloop public superloopImplementation; address public user; + uint256 public constant WHYPE_SCALE = 10 ** 18; + function setUp() public override { super.setUp(); vm.startPrank(admin); - _deployModules(); + _deployHyperliquidStakeModule(); address[] memory modules = new address[](5); modules[0] = address(hyperliquidStakeModule); @@ -29,10 +32,10 @@ contract HyperbeatStakingModuleTest is TestBase { modules[4] = address(unwrapModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: WHYPE, - name: "WHYPE Vault", - symbol: "WHYPEV", - supplyCap: 100000 * WHYPE_SCALE, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -61,8 +64,10 @@ contract HyperbeatStakingModuleTest is TestBase { } function test_HyperbeatStake() public { + if (environment.chainId != 999) return; + // transfer 100 wHYPE to the vault - deal(WHYPE, address(superloop), 100 * WHYPE_SCALE); + deal(environment.vaultAsset, address(superloop), 100 * 10 ** environment.vaultAssetDecimals); // call unwrap module & kinetiq stake module DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](2); @@ -70,7 +75,10 @@ contract HyperbeatStakingModuleTest is TestBase { executionType: DataTypes.CallType.DELEGATECALL, module: address(unwrapModule), data: abi.encodeWithSelector( - unwrapModule.execute.selector, DataTypes.AaveV3ActionParams({asset: WHYPE, amount: 100 * WHYPE_SCALE}) + unwrapModule.execute.selector, + DataTypes.AaveV3ActionParams({ + asset: environment.vaultAsset, amount: 100 * 10 ** environment.vaultAssetDecimals + }) ) }); moduleExecutionData[1] = DataTypes.ModuleExecutionData({ @@ -78,7 +86,9 @@ contract HyperbeatStakingModuleTest is TestBase { module: address(hyperbeatStakingModule), data: abi.encodeWithSelector( hyperbeatStakingModule.execute.selector, - DataTypes.StakeParams({assets: 100 * WHYPE_SCALE, data: abi.encode(string(""))}) + DataTypes.StakeParams({ + assets: 100 * 10 ** environment.vaultAssetDecimals, data: abi.encode(string("")) + }) ) }); diff --git a/test/modules/stake/hyperliquid/HyperliquidStakingModule.t.sol b/test/modules/stake/hyperliquid/HyperliquidStakingModule.t.sol index 05c772e..9d4b729 100644 --- a/test/modules/stake/hyperliquid/HyperliquidStakingModule.t.sol +++ b/test/modules/stake/hyperliquid/HyperliquidStakingModule.t.sol @@ -2,23 +2,25 @@ pragma solidity ^0.8.13; -import {TestBase} from "./TestBase.sol"; +import {TestBase} from "../../../core/TestBase.sol"; import {DataTypes} from "../../../../src/common/DataTypes.sol"; import {Superloop} from "../../../../src/core/Superloop/Superloop.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; contract HyperliquidStakingModuleTest is TestBase { Superloop public superloopImplementation; address public user; + uint256 public constant WHYPE_SCALE = 10 ** 18; function setUp() public override { super.setUp(); vm.startPrank(admin); - _deployModules(); + _deployHyperliquidStakeModule(); address[] memory modules = new address[](5); modules[0] = address(hyperliquidStakeModule); @@ -28,7 +30,7 @@ contract HyperliquidStakingModuleTest is TestBase { modules[4] = address(unwrapModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: WHYPE, + asset: environment.vaultAsset, name: "WHYPE Vault", symbol: "WHYPEV", supplyCap: 100000 * WHYPE_SCALE, @@ -69,7 +71,8 @@ contract HyperliquidStakingModuleTest is TestBase { executionType: DataTypes.CallType.DELEGATECALL, module: address(unwrapModule), data: abi.encodeWithSelector( - unwrapModule.execute.selector, DataTypes.AaveV3ActionParams({asset: WHYPE, amount: 100 * WHYPE_SCALE}) + unwrapModule.execute.selector, + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: 100 * WHYPE_SCALE}) ) }); moduleExecutionData[1] = DataTypes.ModuleExecutionData({ diff --git a/test/modules/stake/hyperliquid/KinetiqStakingModule.t.sol b/test/modules/stake/hyperliquid/KinetiqStakingModule.t.sol index 5631c46..754ddd7 100644 --- a/test/modules/stake/hyperliquid/KinetiqStakingModule.t.sol +++ b/test/modules/stake/hyperliquid/KinetiqStakingModule.t.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.13; -import {TestBase} from "./TestBase.sol"; +import {TestBase} from "../../../core/TestBase.sol"; import {DataTypes} from "../../../../src/common/DataTypes.sol"; import {Superloop} from "../../../../src/core/Superloop/Superloop.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { + TransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IPoolConfigurator} from "aave-v3-core/contracts/interfaces/IPoolConfigurator.sol"; import {console} from "forge-std/console.sol"; @@ -14,12 +15,13 @@ import {console} from "forge-std/console.sol"; contract KinetiqStakingModuleTest is TestBase { Superloop public superloopImplementation; address public user; + uint256 public constant WHYPE_SCALE = 10 ** 18; function setUp() public override { super.setUp(); vm.startPrank(admin); - _deployModules(); + _deployHyperliquidStakeModule(); address[] memory modules = new address[](5); modules[0] = address(hyperliquidStakeModule); @@ -29,10 +31,10 @@ contract KinetiqStakingModuleTest is TestBase { modules[4] = address(unwrapModule); DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ - asset: WHYPE, - name: "WHYPE Vault", - symbol: "WHYPEV", - supplyCap: 100000 * WHYPE_SCALE, + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, minimumDepositAmount: 100, instantWithdrawFee: 0, superloopModuleRegistry: address(moduleRegistry), @@ -62,7 +64,7 @@ contract KinetiqStakingModuleTest is TestBase { function test_KinetiqStake() public { // transfer 100 wHYPE to the vault - deal(WHYPE, address(superloop), 100 * WHYPE_SCALE); + deal(environment.vaultAsset, address(superloop), 100 * 10 ** environment.vaultAssetDecimals); // call unwrap module & kinetiq stake module DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](2); @@ -70,7 +72,8 @@ contract KinetiqStakingModuleTest is TestBase { executionType: DataTypes.CallType.DELEGATECALL, module: address(unwrapModule), data: abi.encodeWithSelector( - unwrapModule.execute.selector, DataTypes.AaveV3ActionParams({asset: WHYPE, amount: 100 * WHYPE_SCALE}) + unwrapModule.execute.selector, + DataTypes.AaveV3ActionParams({asset: environment.vaultAsset, amount: 100 * WHYPE_SCALE}) ) }); moduleExecutionData[1] = DataTypes.ModuleExecutionData({ @@ -78,7 +81,9 @@ contract KinetiqStakingModuleTest is TestBase { module: address(kinetiqStakeModule), data: abi.encodeWithSelector( kinetiqStakeModule.execute.selector, - DataTypes.StakeParams({assets: 100 * WHYPE_SCALE, data: abi.encode(string(""))}) + DataTypes.StakeParams({ + assets: 100 * 10 ** environment.vaultAssetDecimals, data: abi.encode(string("")) + }) ) }); diff --git a/test/modules/stake/hyperliquid/TestBase.sol b/test/modules/stake/hyperliquid/TestBase.sol deleted file mode 100644 index 66dc71a..0000000 --- a/test/modules/stake/hyperliquid/TestBase.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {Superloop} from "../../../../src/core/Superloop/Superloop.sol"; -import {DataTypes} from "../../../../src/common/DataTypes.sol"; -import {SuperloopModuleRegistry} from "../../../../src/core/ModuleRegistry/ModuleRegistry.sol"; -import {AaveV3FlashloanModule} from "../../../../src/modules/aave/AaveV3FlashloanModule.sol"; -import {AaveV3CallbackHandler} from "../../../../src/modules/callback/AaveV3CallbackHandler.sol"; -import {AaveV3EmodeModule} from "../../../../src/modules/aave/AaveV3EmodeModule.sol"; -import {AaveV3SupplyModule} from "../../../../src/modules/aave/AaveV3SupplyModule.sol"; -import {AaveV3WithdrawModule} from "../../../../src/modules/aave/AaveV3WithdrawModule.sol"; -import {AaveV3BorrowModule} from "../../../../src/modules/aave/AaveV3BorrowModule.sol"; -import {AaveV3RepayModule} from "../../../../src/modules/aave/AaveV3RepayModule.sol"; -import {IPoolDataProvider} from "aave-v3-core/contracts/interfaces/IPoolDataProvider.sol"; -import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol"; -import {UniversalDexModule} from "../../../../src/modules/dex/UniversalDexModule.sol"; -import {AccountantAaveV3} from "../../../../src/core/Accountant/aaveV3Accountant/AccountantAaveV3.sol"; -import {WithdrawManager} from "../../../../src/core/WithdrawManager/WithdrawManager.sol"; -import {DepositManager} from "../../../../src/core/DepositManager/DepositManager.sol"; -import {DepositManagerCallbackHandler} from "../../../../src/modules/callback/DepositManagerCallbackHandler.sol"; -import {ProxyAdmin} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from - "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {UniversalAccountant} from "../../../../src/core/Accountant/universalAccountant/UniversalAccountant.sol"; -import {AaveV3AccountantPlugin} from "../../../../src/plugins/Accountant/AaveV3AccountantPlugin.sol"; -import {WithdrawManagerCallbackHandler} from "../../../../src/modules/callback/WithdrawManagerCallbackHandler.sol"; -import {UnwrapModule} from "../../../../src/modules/helper/UnwrapModule.sol"; -import {WrapModule} from "../../../../src/modules/helper/WrapModule.sol"; -import {HyperliquidStakeModule} from "../../../../src/modules/stake/hyperliquid/HyperliquidStakeModule.sol"; -import {KinetiqStakeModule} from "../../../../src/modules/stake/hyperliquid/KinetiqStakeModule.sol"; -import {HyperbeatStakingModule} from "../../../../src/modules/stake/hyperliquid/HyperbeatStakingModule.sol"; - -contract TestBase is Test { - address public AAVE_V3_POOL_ADDRESSES_PROVIDER; - address public AAVE_V3_POOL_DATA_PROVIDER; - address public AAVE_V3_PRICE_ORACLE; - address public POOL; - uint256 public PERFORMANCE_FEE; // 20% - address public POOL_CONFIGURATOR; - - address public stakingManager = 0x393D0B87Ed38fc779FD9611144aE649BA6082109; - address public stakingCore = 0xCeaD893b162D38e714D82d06a7fe0b0dc3c38E0b; - address public overseer = 0xB96f07367e69e86d6e9C3F29215885104813eeAE; - address public constant WHYPE = 0x5555555555555555555555555555555555555555; - uint256 public constant WHYPE_SCALE = 10 ** 18; - - address public constant ST_HYPE = 0xfFaa4a3D97fE9107Cef8a3F48c069F577Ff76cC1; - address public constant WST_HYPE = 0x94e8396e0869c9F2200760aF0621aFd240E1CF38; - address public constant K_HYPE = 0xfD739d4e423301CE9385c1fb8850539D657C296D; - address public constant BE_HYPE = 0xd8FC8F0b03eBA61F64D08B0bef69d80916E5DdA9; - - address public admin; - address public treasury; - - SuperloopModuleRegistry public moduleRegistry; - Superloop public superloop; - AaveV3FlashloanModule public flashloanModule; - DepositManagerCallbackHandler public depositManagerCallbackHandler; - WithdrawManagerCallbackHandler public withdrawManagerCallbackHandler; - AaveV3CallbackHandler public callbackHandler; - AaveV3SupplyModule public supplyModule; - AaveV3WithdrawModule public withdrawModule; - AaveV3BorrowModule public borrowModule; - AaveV3RepayModule public repayModule; - UniversalDexModule public dexModule; - AccountantAaveV3 public accountantAaveV3; - UniversalAccountant public accountant; - WithdrawManager public withdrawManager; - UnwrapModule public unwrapModule; - WrapModule public wrapModule; - - HyperliquidStakeModule public hyperliquidStakeModule; - KinetiqStakeModule public kinetiqStakeModule; - HyperbeatStakingModule public hyperbeatStakingModule; - - DepositManager public depositManager; - - address public mockModule; - AaveV3EmodeModule public emodeModule; - IPoolDataProvider public poolDataProvider; - IPool public pool; - - function setUp() public virtual { - vm.createSelectFork("hyperevm"); - admin = makeAddr("admin"); - treasury = makeAddr("treasury"); - - vm.startPrank(admin); - moduleRegistry = new SuperloopModuleRegistry(); - mockModule = makeAddr("mockModule"); - moduleRegistry.setModule("MockModule", mockModule); - poolDataProvider = IPoolDataProvider(AAVE_V3_POOL_DATA_PROVIDER); - pool = IPool(POOL); - vm.stopPrank(); - - vm.label(admin, "admin"); - vm.label(treasury, "treasury"); - vm.label(mockModule, "mockModule"); - vm.label(address(moduleRegistry), "moduleRegistry"); - } - - function _deployModules() internal { - unwrapModule = new UnwrapModule(WHYPE); - moduleRegistry.setModule("UnwrapModule", address(unwrapModule)); - wrapModule = new WrapModule(WHYPE); - moduleRegistry.setModule("WrapModule", address(wrapModule)); - - hyperliquidStakeModule = new HyperliquidStakeModule(overseer); - moduleRegistry.setModule("HyperliquidStakeModule", address(hyperliquidStakeModule)); - kinetiqStakeModule = new KinetiqStakeModule(stakingManager); - moduleRegistry.setModule("KinetiqStakeModule", address(kinetiqStakeModule)); - hyperbeatStakingModule = new HyperbeatStakingModule(stakingCore); - moduleRegistry.setModule("HyperbeatStakingModule", address(hyperbeatStakingModule)); - - vm.label(address(unwrapModule), "unwrapModule"); - vm.label(address(wrapModule), "wrapModule"); - vm.label(address(hyperliquidStakeModule), "hyperliquidStakeModule"); - vm.label(address(kinetiqStakeModule), "kinetiqStakeModule"); - vm.label(address(hyperbeatStakingModule), "hyperbeatStakingModule"); - } -} diff --git a/test/modules/vault/ERC4626Module.t.sol b/test/modules/vault/ERC4626Module.t.sol new file mode 100644 index 0000000..ed260fc --- /dev/null +++ b/test/modules/vault/ERC4626Module.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.13; + +import {TestBase} from "../../core/TestBase.sol"; +import {DataTypes} from "../../../src/common/DataTypes.sol"; +import {Superloop} from "../../../src/core/Superloop/Superloop.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {console} from "forge-std/console.sol"; + +contract ERC4626ModuleTest is TestBase { + Superloop public superloopImplementation; + address public user; + + function setUp() public override { + super.setUp(); + + vm.startPrank(admin); + _deployModules(); + + address[] memory modules = new address[](2); + modules[0] = address(vaultSupplyModule); + modules[1] = address(vaultWithdrawModule); + + DataTypes.VaultInitData memory initData = DataTypes.VaultInitData({ + asset: environment.vaultAsset, + name: "Vault", + symbol: "VLT", + supplyCap: 100000 * 10 ** environment.vaultAssetDecimals, + minimumDepositAmount: 100, + instantWithdrawFee: 0, + superloopModuleRegistry: address(moduleRegistry), + modules: modules, + accountant: mockModule, + withdrawManager: mockModule, + depositManager: mockModule, + cashReserve: 1000, + vaultAdmin: admin, + treasury: treasury, + vaultOperator: admin + }); + superloopImplementation = new Superloop(); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(superloopImplementation), + address(this), + abi.encodeWithSelector(Superloop.initialize.selector, initData) + ); + superloop = Superloop(payable(address(proxy))); + vm.stopPrank(); + + user = makeAddr("user"); + vm.label(user, "user"); + vm.label(address(superloop), "superloop"); + } + + function test_VaultSupply() public { + if (environment.chainId != 1) return; + + address stakingVault = environment.lendAssets[1]; + + deal(environment.vaultAsset, address(superloop), 1000 * 10 ** environment.vaultAssetDecimals); + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(vaultSupplyModule), + data: abi.encodeWithSelector( + vaultSupplyModule.execute.selector, + DataTypes.VaultActionParams({vault: stakingVault, amount: 1000 * 10 ** environment.vaultAssetDecimals}) + ) + }); + + uint256 currentBalance = IERC20(stakingVault).balanceOf(address(superloop)); + + vm.prank(admin); + superloop.operate(moduleExecutionData); + + uint256 finalBalance = IERC20(stakingVault).balanceOf(address(superloop)); + + assert(finalBalance > currentBalance); + } + + function test_VaultWithdraw() public { + if (environment.chainId != 1) return; + // create supply + test_VaultSupply(); + + address stakingVault = environment.lendAssets[1]; + + DataTypes.ModuleExecutionData[] memory moduleExecutionData = new DataTypes.ModuleExecutionData[](1); + moduleExecutionData[0] = DataTypes.ModuleExecutionData({ + executionType: DataTypes.CallType.DELEGATECALL, + module: address(vaultWithdrawModule), + data: abi.encodeWithSelector( + vaultWithdrawModule.execute.selector, + DataTypes.VaultActionParams({vault: stakingVault, amount: type(uint256).max}) + ) + }); + + // expected to revert in this case becase direct withdraw from vault is not allowed for sUSDe + vm.prank(admin); + vm.expectRevert(); + superloop.operate(moduleExecutionData); + } +}