From 674ee9c84a5c937899616d5209f6fea0e554167a Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Wed, 27 Nov 2024 14:27:32 -0500 Subject: [PATCH 01/22] [SC-838] Add ERC4626 support to ForeignController with Morpho Testing (#50) * added erc4626 support; adding tests * forge install: morpho-blue v1.0.0 * forge install: metamorpho v1.0.0 * add erc4626 support with morpho * keep old block for existing tests * minor change * use existing helper function * refactor the morpho test structure * address registry update; match mainnet erc4626 naming * remove todo * review fixes * chore: reorganize testing (#56) --------- Co-authored-by: Lucas Manuel --- .gitmodules | 3 + lib/metamorpho | 1 + lib/spark-address-registry | 2 +- src/ForeignController.sol | 65 ++++++- test/base-fork/ForkTestBase.t.sol | 6 +- test/base-fork/Morpho.t.sol | 285 ++++++++++++++++++++++++++++++ 6 files changed, 359 insertions(+), 3 deletions(-) create mode 160000 lib/metamorpho create mode 100644 test/base-fork/Morpho.t.sol diff --git a/.gitmodules b/.gitmodules index c475ba2..e64c5c3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/dss-test"] path = lib/dss-test url = https://github.com/makerdao/dss-test +[submodule "lib/metamorpho"] + path = lib/metamorpho + url = https://github.com/morpho-org/metamorpho diff --git a/lib/metamorpho b/lib/metamorpho new file mode 160000 index 0000000..f5faa9c --- /dev/null +++ b/lib/metamorpho @@ -0,0 +1 @@ +Subproject commit f5faa9c21b1396c291b471a6a5ad9407d23486a9 diff --git a/lib/spark-address-registry b/lib/spark-address-registry index cf8be61..bf584d8 160000 --- a/lib/spark-address-registry +++ b/lib/spark-address-registry @@ -1 +1 @@ -Subproject commit cf8be6199b40a3337916e3d6f7aba7991916f53d +Subproject commit bf584d8b5d0afdd6cdc581fbffe543d3e00e2c3c diff --git a/src/ForeignController.sol b/src/ForeignController.sol index 3b3c1c9..c0b832c 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; @@ -44,6 +45,7 @@ contract ForeignController is AccessControl { bytes32 public constant LIMIT_PSM_WITHDRAW = keccak256("LIMIT_PSM_WITHDRAW"); bytes32 public constant LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP"); bytes32 public constant LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN"); + bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); IALMProxy public immutable proxy; ICCTPLike public immutable cctp; @@ -214,6 +216,67 @@ contract ForeignController is AccessControl { } } + /**********************************************************************************************/ + /*** Relayer ERC4626 functions ***/ + /**********************************************************************************************/ + + function depositERC4626(address token, uint256 amount) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, token), + amount + ) + returns (uint256 shares) + { + // Note that whitelist is done by rate limits + IERC20 asset = IERC20(IERC4626(token).asset()); + + // Approve asset to token from the proxy (assumes the proxy has enough of the asset). + proxy.doCall( + address(asset), + abi.encodeCall(asset.approve, (token, amount)) + ); + + // Deposit asset into the token, proxy receives token shares, decode the resulting shares + shares = abi.decode( + proxy.doCall( + token, + abi.encodeCall(IERC4626(token).deposit, (amount, address(proxy))) + ), + (uint256) + ); + } + + function withdrawERC4626(address token, uint256 amount) + external onlyRole(RELAYER) isActive returns (uint256 shares) + { + // Withdraw asset from a token, decode resulting shares. + // Assumes proxy has adequate token shares. + shares = abi.decode( + proxy.doCall( + token, + abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy))) + ), + (uint256) + ); + } + + function redeemERC4626(address token, uint256 shares) + external onlyRole(RELAYER) isActive returns (uint256 assets) + { + // Redeem shares for assets from the token, decode the resulting assets. + // Assumes proxy has adequate token shares. + assets = abi.decode( + proxy.doCall( + token, + abi.encodeCall(IERC4626(token).redeem, (shares, address(proxy), address(proxy))) + ), + (uint256) + ); + } + /**********************************************************************************************/ /*** Internal helper functions ***/ /**********************************************************************************************/ diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 2cf797b..b6b1d3b 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -88,7 +88,7 @@ contract ForkTestBase is Test { function setUp() public virtual { /*** Step 1: Set up environment, deploy mock addresses ***/ - vm.createSelectFork(getChain('base').rpcUrl, 20782500); // October 8, 2024 + vm.createSelectFork(getChain('base').rpcUrl, _getBlock()); usdsBase = IERC20(address(new ERC20Mock())); susdsBase = IERC20(address(new ERC20Mock())); @@ -176,4 +176,8 @@ contract ForkTestBase is Test { vm.stopPrank(); } + function _getBlock() internal virtual pure returns (uint256) { + return 20782500; // October 8, 2024 + } + } diff --git a/test/base-fork/Morpho.t.sol b/test/base-fork/Morpho.t.sol new file mode 100644 index 0000000..1d237f8 --- /dev/null +++ b/test/base-fork/Morpho.t.sol @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "test/base-fork/ForkTestBase.t.sol"; + +import { IERC4626 } from "lib/forge-std/src/interfaces/IERC4626.sol"; + +import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; + +import { IMetaMorpho, Id } from "metamorpho/interfaces/IMetaMorpho.sol"; +import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; +import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; + +contract MorphoBaseTest is ForkTestBase { + + address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + + address constant MORPHO_VAULT_USDS = 0x0fFDeCe791C5a2cb947F8ddBab489E5C02c6d4F7; + address constant MORPHO_VAULT_USDC = 0x305E03Ed9ADaAB22F4A58c24515D79f2B1E2FD5D; + + IERC4626 usdsVault = IERC4626(MORPHO_VAULT_USDS); + IERC4626 usdcVault = IERC4626(MORPHO_VAULT_USDC); + + function setUp() public override { + super.setUp(); + + vm.startPrank(Base.SPARK_EXECUTOR); + + // Add in the idle markets so deposits can be made + MarketParams memory usdsParams = MarketParams({ + loanToken: Base.USDS, + collateralToken: address(0), + oracle: address(0), + irm: address(0), + lltv: 0 + }); + MarketParams memory usdcParams = MarketParams({ + loanToken: Base.USDC, + collateralToken: address(0), + oracle: address(0), + irm: address(0), + lltv: 0 + }); + IMorpho(MORPHO).createMarket( + usdsParams + ); + // USDC idle market already exists + IMetaMorpho(MORPHO_VAULT_USDS).submitCap( + usdsParams, + type(uint184).max + ); + IMetaMorpho(MORPHO_VAULT_USDC).submitCap( + usdcParams, + type(uint184).max + ); + + skip(1 days); + + IMetaMorpho(MORPHO_VAULT_USDS).acceptCap(usdsParams); + IMetaMorpho(MORPHO_VAULT_USDC).acceptCap(usdcParams); + + Id[] memory supplyQueueUSDS = new Id[](1); + supplyQueueUSDS[0] = MarketParamsLib.id(usdsParams); + IMetaMorpho(MORPHO_VAULT_USDS).setSupplyQueue(supplyQueueUSDS); + Id[] memory supplyQueueUSDC = new Id[](1); + supplyQueueUSDC[0] = MarketParamsLib.id(usdcParams); + IMetaMorpho(MORPHO_VAULT_USDC).setSupplyQueue(supplyQueueUSDC); + + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_4626_DEPOSIT(), + MORPHO_VAULT_USDS + ), + 25_000_000e18, + uint256(5_000_000e18) / 1 days + ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_4626_DEPOSIT(), + MORPHO_VAULT_USDC + ), + 25_000_000e6, + uint256(5_000_000e6) / 1 days + ); + + vm.stopPrank(); + } + + function _getBlock() internal pure override returns (uint256) { + return 22841965; // November 24, 2024 + } + +} + +// NOTE: Only testing USDS for non-rate limit failures as it doesn't matter which asset is used + +contract MorphoDepositFailureTests is MorphoBaseTest { + + function test_morpho_deposit_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + + function test_morpho_deposit_frozen() external { + vm.prank(freezer); + foreignController.freeze(); + + vm.prank(relayer); + vm.expectRevert("ForeignController/not-active"); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + + function test_morpho_usds_deposit_rateLimitedBoundary() external { + deal(Base.USDS, address(almProxy), 25_000_000e18 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 25_000_000e18 + 1); + + foreignController.depositERC4626(MORPHO_VAULT_USDS, 25_000_000e18); + } + + function test_morpho_usdc_deposit_rateLimitedBoundary() external { + deal(Base.USDC, address(almProxy), 25_000_000e6 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDC, 25_000_000e6 + 1); + + foreignController.depositERC4626(MORPHO_VAULT_USDC, 25_000_000e6); + } + +} + +contract MorphoDepositSuccessTests is MorphoBaseTest { + + function test_morpho_usds_deposit() public { + deal(Base.USDS, address(almProxy), 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 1_000_000e18); + assertEq(IERC20(Base.USDS).allowance(address(almProxy), address(MORPHO_VAULT_USDS)), 0); + + vm.prank(relayer); + assertEq(foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18), 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 1_000_000e18); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 0); + assertEq(IERC20(Base.USDS).allowance(address(almProxy), address(MORPHO_VAULT_USDS)), 0); + } + + function test_morpho_usdc_deposit() public { + deal(Base.USDC, address(almProxy), 1_000_000e6); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 1_000_000e6); + assertEq(IERC20(Base.USDC).allowance(address(almProxy), address(MORPHO_VAULT_USDC)), 0); + + vm.prank(relayer); + assertEq(foreignController.depositERC4626(MORPHO_VAULT_USDC, 1_000_000e6), 1_000_000e18); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 1_000_000e6); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 0); + assertEq(IERC20(Base.USDC).allowance(address(almProxy), address(MORPHO_VAULT_USDC)), 0); + } + +} + +contract MorphoWithdrawFailureTests is MorphoBaseTest { + + function test_morpho_withdraw_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + + function test_morpho_withdraw_frozen() external { + vm.prank(freezer); + foreignController.freeze(); + + vm.prank(relayer); + vm.expectRevert("ForeignController/not-active"); + foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + +} + +contract MorphoWithdrawSuccessTests is MorphoBaseTest { + + function test_morpho_usds_withdraw() public { + deal(Base.USDS, address(almProxy), 1_000_000e18); + vm.prank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 1_000_000e18); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 0); + + vm.prank(relayer); + assertEq(foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 1_000_000e18), 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 1_000_000e18); + } + + function test_morpho_usdc_withdraw() public { + deal(Base.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDC, 1_000_000e6); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 1_000_000e6); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 0); + + vm.prank(relayer); + assertEq(foreignController.withdrawERC4626(MORPHO_VAULT_USDC, 1_000_000e6), 1_000_000e18); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 1_000_000e6); + } + +} + +contract MorphoRedeemFailureTests is MorphoBaseTest { + + function test_morpho_redeem_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + foreignController.redeemERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + + function test_morpho_redeem_frozen() external { + vm.prank(freezer); + foreignController.freeze(); + + vm.prank(relayer); + vm.expectRevert("ForeignController/not-active"); + foreignController.redeemERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + +} + +contract MorphoRedeemSuccessTests is MorphoBaseTest { + + function test_morpho_usds_redeem() public { + deal(Base.USDS, address(almProxy), 1_000_000e18); + vm.prank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 1_000_000e18); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 0); + + uint256 shares = usdsVault.balanceOf(address(almProxy)); + vm.prank(relayer); + assertEq(foreignController.redeemERC4626(MORPHO_VAULT_USDS, shares), 1_000_000e18); + + assertEq(usdsVault.convertToAssets(usdsVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDS).balanceOf(address(almProxy)), 1_000_000e18); + } + + function test_morpho_usdc_redeem() public { + deal(Base.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDC, 1_000_000e6); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 1_000_000e6); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 0); + + uint256 shares = usdcVault.balanceOf(address(almProxy)); + vm.prank(relayer); + assertEq(foreignController.redeemERC4626(MORPHO_VAULT_USDC, shares), 1_000_000e6); + + assertEq(usdcVault.convertToAssets(usdcVault.balanceOf(address(almProxy))), 0); + assertEq(IERC20(Base.USDC).balanceOf(address(almProxy)), 1_000_000e6); + } + +} From 72af042ed00db616f29a608a0a2a2517efddd321 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 27 Nov 2024 16:42:25 -0500 Subject: [PATCH 02/22] feat: Remove sepolia and temporarily skip staging testing (#57) * feat: rm sepolia * chore: skip staging testing * feat: rm ci job for deployments --- .github/workflows/ci.yml | 22 +- script/input/11155111/base.json | 5 - script/input/11155111/common.json | 5 - script/input/11155111/mainnet.json | 5 - script/output/11155111/.gitkeep | 0 .../11155111/base-release-20241005.json | 11 - .../11155111/base-release-20241014.json | 11 - .../11155111/mainnet-release-20241005.json | 16 -- .../11155111/mainnet-release-20241014.json | 21 -- script/staging/DeploySepolia.s.sol | 43 ---- script/staging/test/DeployEthereum.t.sol | 40 ++-- script/staging/test/DeploySepolia.t.sol | 208 ------------------ 12 files changed, 24 insertions(+), 363 deletions(-) delete mode 100644 script/input/11155111/base.json delete mode 100644 script/input/11155111/common.json delete mode 100644 script/input/11155111/mainnet.json delete mode 100644 script/output/11155111/.gitkeep delete mode 100644 script/output/11155111/base-release-20241005.json delete mode 100644 script/output/11155111/base-release-20241014.json delete mode 100644 script/output/11155111/mainnet-release-20241005.json delete mode 100644 script/output/11155111/mainnet-release-20241014.json delete mode 100644 script/staging/DeploySepolia.s.sol delete mode 100644 script/staging/test/DeploySepolia.t.sol diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad36062..d19ee12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,25 +40,7 @@ jobs: ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}} GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}} BASE_RPC_URL: ${{secrets.BASE_RPC_URL}} - run: FOUNDRY_PROFILE=ci forge test --nmc DeployEthereumTest - - test-ethereum-staging: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Run tests - env: - MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}} - OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}} - ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}} - ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}} - GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}} - BASE_RPC_URL: ${{secrets.BASE_RPC_URL}} - run: FOUNDRY_PROFILE=ci forge test --mc DeployEthereumTest + run: FOUNDRY_PROFILE=ci forge test coverage: runs-on: ubuntu-latest @@ -76,7 +58,7 @@ jobs: ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}} GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}} BASE_RPC_URL: ${{secrets.BASE_RPC_URL}} - run: forge coverage --report summary --report lcov --nmc DeployEthereumTest + run: forge coverage --report summary --report lcov # To ignore coverage for certain directories modify the paths in this step as needed. The # below default ignores coverage results for the test and script directories. Alternatively, diff --git a/script/input/11155111/base.json b/script/input/11155111/base.json deleted file mode 100644 index c8e3c7c..0000000 --- a/script/input/11155111/base.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cctpTokenMessenger": "0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x036CbD53842c5426634e7929541eC2318f3dCF7e" -} diff --git a/script/input/11155111/common.json b/script/input/11155111/common.json deleted file mode 100644 index a306149..0000000 --- a/script/input/11155111/common.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ilk": "ALLOCATOR-SPARK-A", - "usdcUnitSize": 1000, - "usdsUnitSize": 1000000 -} diff --git a/script/input/11155111/mainnet.json b/script/input/11155111/mainnet.json deleted file mode 100644 index 72516bb..0000000 --- a/script/input/11155111/mainnet.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cctpTokenMessenger": "0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" -} diff --git a/script/output/11155111/.gitkeep b/script/output/11155111/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/script/output/11155111/base-release-20241005.json b/script/output/11155111/base-release-20241005.json deleted file mode 100644 index 5b88b52..0000000 --- a/script/output/11155111/base-release-20241005.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", - "almProxy": "0x33a3aB524A43E69f30bFd9Ae97d1Ec679FF00B64", - "controller": "0xcA61540eC2AC74E6954FA558B4aF836d95eCb91b", - "psm": "0x6Bf8Ea24853F8E5A6288A2dFD2e4c541105c6BA2", - "rateLimits": "0xE206AEbca7B28e3E8d6787df00B010D4a77c32F3", - "susds": "0x3A60e678eA258A30c7cab2B70439a37fd6495Fe1", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", - "usds": "0xeD56689ee0d21a1Da68102e5EcBDe3fD1d606709" -} diff --git a/script/output/11155111/base-release-20241014.json b/script/output/11155111/base-release-20241014.json deleted file mode 100644 index c239be8..0000000 --- a/script/output/11155111/base-release-20241014.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", - "almProxy": "0x930e7EFC310F1E62ff3DfC7b60A8FF06d4046887", - "controller": "0x347F189485A80Ae373d39b33F4e5780d3d1246Fa", - "psm": "0x2B05F8e1cACC6974fD79A673a341Fe1f58d27266", - "rateLimits": "0x637038064e9497c7D6efB29EFF9Ecc0EB3124788", - "susds": "0x9e662A18F3a1Eae1eb6fb1469FB793974ACe6E92", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", - "usds": "0x49aF4eE75Ae62C2229bb2486a59Aa1a999f050f0" -} diff --git a/script/output/11155111/mainnet-release-20241005.json b/script/output/11155111/mainnet-release-20241005.json deleted file mode 100644 index c4adca9..0000000 --- a/script/output/11155111/mainnet-release-20241005.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", - "allocatorBuffer": "0xEE2816c1E1eed14d444552654Ed3027abC033A36", - "allocatorOracle": "0x73750DbD85753074e452B2C27fB9e3B0E75Ff3B8", - "allocatorRegistry": "0x15ACEE5F73b36762Ab1a6b7C98787b8148447898", - "allocatorRoles": "0xb4FB5e6746701Fe652Ed1386547AbBBfb88FBA5B", - "allocatorVault": "0x567214Dc57a2385Abc4a756f523ddF0275305Cbc", - "almProxy": "0x49aF4eE75Ae62C2229bb2486a59Aa1a999f050f0", - "controller": "0x31E3aC4848d2885112868f60aDD59176ecC29AF6", - "dai": "0x3A60e678eA258A30c7cab2B70439a37fd6495Fe1", - "rateLimits": "0x9e662A18F3a1Eae1eb6fb1469FB793974ACe6E92", - "susds": "0x6Bf8Ea24853F8E5A6288A2dFD2e4c541105c6BA2", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", - "usds": "0xE10aDC43aA2fD72A402E13AFd4153Ceca14E6E1f" -} diff --git a/script/output/11155111/mainnet-release-20241014.json b/script/output/11155111/mainnet-release-20241014.json deleted file mode 100644 index f4817b1..0000000 --- a/script/output/11155111/mainnet-release-20241014.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", - "allocatorBuffer": "0xdC8DF20bE448E41FdE88E2478d6Bc4d3C9A092d6", - "allocatorOracle": "0x339A7c4e51E385Ee22084C77456dedf58D742396", - "allocatorRegistry": "0xe0F9978b907853F354d79188A3dEfbD41978af62", - "allocatorRoles": "0xd18486Cb62Dd9C4b6003666Bf9f9cb21C10641c4", - "allocatorVault": "0x940098b108fB7D0a7E374f6eDED7760787464609", - "almProxy": "0x545eeEc8Ca599085cE86ada51eb8c0c35Af1e9d6", - "controller": "0x61B989D473a977884Ac73A3726e1d2f7A6b50e07", - "dai": "0x04A65f8F15fcb8F3D5da106cA4E79fCAaed097Ce", - "daiUsds": "0x6E53585449142A5E6D5fC918AE6BEa341dC81C68", - "jug": "0xE2868095814c2714039b3A9eBEE035B9E2c411E5", - "psm": "0xe1e4953C93Da52b95eDD0ffd910565D3369aCd6b", - "rateLimits": "0xab465726A358c004C22bB8136d43716e1936AFa6", - "susds": "0x982dE6D637cdc542f8AFbb3440A8AbF0f100dc0F", - "safe": "0x22fB6fe2B9aA289D26724eCBD5a679751A4508b5", - "usdc": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", - "usds": "0xB96A2e80fE63879e6025276935552368812D4Fb6", - "usdsJoin": "0x76B3D354FEe58523E4687a2142c2CCc6a4d35e8B", - "vat": "0x3C258BCaC2ab3615fC8BCfcD878e8B8df81fFA06" -} diff --git a/script/staging/DeploySepolia.s.sol b/script/staging/DeploySepolia.s.sol deleted file mode 100644 index 1d9d607..0000000 --- a/script/staging/DeploySepolia.s.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; - -import { stdJson } from "forge-std/StdJson.sol"; - -import { Domain, StagingDeploymentBase } from "script/staging/StagingDeploymentBase.sol"; - -contract DeploySepoliaStaging is StagingDeploymentBase { - - using stdJson for string; - using ScriptTools for string; - - function run() public { - vm.setEnv("FOUNDRY_ROOT_CHAINID", "11155111"); - vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); - - deployer = msg.sender; - - setChain("sepolia_base", ChainData({ - rpcUrl : vm.envOr("SEPOLIA_BASE_RPC_URL", string("https://base-sepolia-rpc.publicnode.com")), - chainId : 84532, - name : "Sepolia Base Testnet" - })); - - mainnet = Domain({ - name : "mainnet", - config : ScriptTools.loadConfig("mainnet"), - forkId : vm.createFork(getChain("sepolia").rpcUrl), - admin : deployer - }); - base = Domain({ - name : "base", - config : ScriptTools.loadConfig("base"), - forkId : vm.createFork(getChain("sepolia_base").rpcUrl), - admin : deployer - }); - - _runFullDeployment({ useLiveContracts: false }); - } - -} diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol index f0cc791..7f42775 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/DeployEthereum.t.sol @@ -146,13 +146,15 @@ contract DeployEthereumTest is Test { psmBase = PSM3(outputBase.readAddress(".psm")); mainnet.selectFork(); + + deal(address(usds), address(usdsJoin), 1000e18); // Ensure there is enough balance } /**********************************************************************************************/ /**** Tests ***/ /**********************************************************************************************/ - function test_mainnetConfiguration() public { + function skip_test_mainnetConfiguration() public { mainnet.selectFork(); // Mainnet controller initialization @@ -251,7 +253,7 @@ contract DeployEthereumTest is Test { _assertRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), type(uint256).max, 0); } - function test_baseConfiguration() public { + function skip_test_baseConfiguration() public { base.selectFork(); // PSM configuration @@ -261,8 +263,8 @@ contract DeployEthereumTest is Test { assertEq(address(psmBase.susds()), outputBase.readAddress(".susds")); assertEq(address(psmBase.pocket()), outputBase.readAddress(".psm")); - assertEq(psmBase.totalAssets(), 1e18); - assertEq(psmBase.totalShares(), 1e18); + assertGe(psmBase.totalAssets(), 1e18); + assertGe(psmBase.totalShares(), 1e18); assertEq(IRateProviderLike(psmBase.rateProvider()).getConversionRate(), 1.2e27); @@ -329,30 +331,30 @@ contract DeployEthereumTest is Test { _assertRateLimitData(address(foreignRateLimits), domainKeyEthereum, max6, slope6); } - function test_mintUSDS() public { - assertEq(usds.balanceOf(address(almProxy)), 0); + function skip_test_mintUSDS() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); vm.prank(safeMainnet); mainnetController.mintUSDS(10e18); - assertEq(usds.balanceOf(address(almProxy)), 10e18); + assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); } - function test_mintAndSwapToUSDC() public { - assertEq(usdc.balanceOf(address(almProxy)), 0); + function skip_test_mintAndSwapToUSDC() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); vm.startPrank(safeMainnet); mainnetController.mintUSDS(10e18); mainnetController.swapUSDSToUSDC(10e6); vm.stopPrank(); - assertEq(usdc.balanceOf(address(almProxy)), 10e6); + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); } - function test_transferCCTP() public { + function skip_test_transferCCTP() public { base.selectFork(); - assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), 0); + uint256 startingBalance = usdcBase.balanceOf(address(foreignAlmProxy)); mainnet.selectFork(); @@ -364,13 +366,13 @@ contract DeployEthereumTest is Test { cctpBridge.relayMessagesToDestination(true); - assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), 10e6); + assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), startingBalance + 10e6); } - function test_transferToPSM() public { + function skip_test_transferToPSM() public { base.selectFork(); - assertEq(usdcBase.balanceOf(address(psmBase)), 0); + uint256 startingBalance = usdcBase.balanceOf(address(psmBase)); mainnet.selectFork(); @@ -382,16 +384,18 @@ contract DeployEthereumTest is Test { cctpBridge.relayMessagesToDestination(true); + uint256 startingShares = psmBase.shares(address(foreignAlmProxy)); + vm.startPrank(safeBase); foreignController.depositPSM(address(usdcBase), 10e6); vm.stopPrank(); - assertEq(usdcBase.balanceOf(address(psmBase)), 10e6); + assertEq(usdcBase.balanceOf(address(psmBase)), startingBalance + 10e6); - assertEq(psmBase.shares(address(foreignAlmProxy)), 10e18); + assertEq(psmBase.shares(address(foreignAlmProxy)), startingShares + 10e18); } - function test_fullRoundTrip() public { + function skip_test_fullRoundTrip() public { mainnet.selectFork(); vm.startPrank(safeMainnet); diff --git a/script/staging/test/DeploySepolia.t.sol b/script/staging/test/DeploySepolia.t.sol deleted file mode 100644 index 4a26de6..0000000 --- a/script/staging/test/DeploySepolia.t.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; - -import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; -import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; - -import { Usds } from "lib/usds/src/Usds.sol"; -import { SUsds } from "lib/sdai/src/SUsds.sol"; - -import { AllocatorVault } from "lib/dss-allocator/src/AllocatorVault.sol"; -import { AllocatorBuffer } from "lib/dss-allocator/src/AllocatorBuffer.sol"; - -import { MainnetController } from "src/MainnetController.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; -import { RateLimits } from "src/RateLimits.sol"; - -import { PSM3, IERC20 } from "lib/spark-psm/src/PSM3.sol"; - -contract DeploySepoliaTest is Test { - - using stdJson for *; - using DomainHelpers for *; - using CCTPBridgeTesting for *; - - address CCTP_TOKEN_MESSENGER_MAINNET = 0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5; - address CCTP_MESSENGER_MAINNET = 0x7865fAfC2db2093669d92c0F33AeEF291086BEFD; - address CCTP_MESSENGER_BASE = 0x7865fAfC2db2093669d92c0F33AeEF291086BEFD; - address USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; - - address admin; - address safe; // Will be the same on all chains - - string outputMainnet; - string inputBase; - string outputBase; - - Domain mainnet; - Domain base; - Bridge cctpBridge; - - // Mainnet contracts - Usds usds; - SUsds susds; - IERC20 usdc; - - AllocatorVault allocatorVault; - AllocatorBuffer allocatorBuffer; - - MainnetController mainnetController; - ALMProxy almProxy; - RateLimits rateLimits; - - // Base contracts - PSM3 psm; - - IERC20 usdsBase; - IERC20 susdsBase; - IERC20 usdcBase; - - ForeignController foreignController; - ALMProxy almProxyBase; - - function setUp() public { - vm.setEnv("FOUNDRY_ROOT_CHAINID", "11155111"); - - setChain("sepolia_base", ChainData({ - rpcUrl: vm.envOr("SEPOLIA_BASE_RPC_URL", string("https://base-sepolia-rpc.publicnode.com")), - chainId: 84532, - name: "Sepolia Base Testnet" - })); - - mainnet = getChain("sepolia").createSelectFork(); - base = getChain("sepolia_base").createFork(); - cctpBridge = CCTPBridgeTesting.init(Bridge({ - source: mainnet, - destination: base, - sourceCrossChainMessenger: CCTP_MESSENGER_MAINNET, - destinationCrossChainMessenger: CCTP_MESSENGER_BASE, - lastSourceLogIndex: 0, - lastDestinationLogIndex: 0, - extraData: "" - })); - - outputMainnet = ScriptTools.readOutput("mainnet-release", 20241005); - inputBase = ScriptTools.readInput("base"); - outputBase = ScriptTools.readOutput("base-release", 20241005); - - admin = outputMainnet.readAddress(".admin"); - safe = outputMainnet.readAddress(".safe"); - - usds = Usds(outputMainnet.readAddress(".usds")); - susds = SUsds(outputMainnet.readAddress(".susds")); - usdc = IERC20(USDC); - - allocatorVault = AllocatorVault(outputMainnet.readAddress(".allocatorVault")); - allocatorBuffer = AllocatorBuffer(outputMainnet.readAddress(".allocatorBuffer")); - - mainnetController = MainnetController(outputMainnet.readAddress(".controller")); - almProxy = ALMProxy(payable(outputMainnet.readAddress(".almProxy"))); - rateLimits = RateLimits(outputMainnet.readAddress(".rateLimits")); - - psm = PSM3(outputBase.readAddress(".psm")); - - usdsBase = IERC20(outputBase.readAddress(".usds")); - susdsBase = IERC20(outputBase.readAddress(".susds")); - usdcBase = IERC20(outputBase.readAddress(".usdc")); - - foreignController = ForeignController(outputBase.readAddress(".controller")); - almProxyBase = ALMProxy(payable(outputBase.readAddress(".almProxy"))); - } - - function test_mintUSDS() public { - assertEq(usds.balanceOf(address(almProxy)), 0); - - vm.prank(safe); - mainnetController.mintUSDS(1000e18); - - assertEq(usds.balanceOf(address(almProxy)), 1000e18); - } - - function test_mintAndSwapToUSDC() public { - assertEq(usdc.balanceOf(address(almProxy)), 0); - - vm.startPrank(safe); - mainnetController.mintUSDS(1000e18); - mainnetController.swapUSDSToUSDC(1000e6); - vm.stopPrank(); - - assertEq(usdc.balanceOf(address(almProxy)), 1000e6); - } - - function test_transferCCTP() public { - base.selectFork(); - - assertEq(usdcBase.balanceOf(address(almProxyBase)), 0); - - mainnet.selectFork(); - - vm.startPrank(safe); - mainnetController.mintUSDS(1000e18); - mainnetController.swapUSDSToUSDC(1000e6); - mainnetController.transferUSDCToCCTP(1000e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); - vm.stopPrank(); - - cctpBridge.relayMessagesToDestination(true); - - assertEq(usdcBase.balanceOf(address(almProxyBase)), 1000e6); - } - - function test_transferToPSM() public { - base.selectFork(); - - assertEq(usdcBase.balanceOf(address(psm)), 0); - - mainnet.selectFork(); - - vm.startPrank(safe); - mainnetController.mintUSDS(1000e18); - mainnetController.swapUSDSToUSDC(1000e6); - mainnetController.transferUSDCToCCTP(1000e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); - vm.stopPrank(); - - cctpBridge.relayMessagesToDestination(true); - - vm.startPrank(safe); - foreignController.depositPSM(address(usdcBase), 1000e6); - vm.stopPrank(); - - assertEq(usdcBase.balanceOf(address(psm)), 1000e6); - } - - function test_fullRoundTrip() public { - mainnet.selectFork(); - - vm.startPrank(safe); - mainnetController.mintUSDS(1000e18); - mainnetController.swapUSDSToUSDC(1000e6); - mainnetController.transferUSDCToCCTP(1000e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); - vm.stopPrank(); - - cctpBridge.relayMessagesToDestination(true); - - vm.startPrank(safe); - foreignController.depositPSM(address(usdcBase), 1000e6); - foreignController.withdrawPSM(address(usdcBase), 1000e6); - foreignController.transferUSDCToCCTP(1000e6, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); - vm.stopPrank(); - - // There is a bug when the messenger addresses are the same - // Need to force update to skip the previous relayed message - // See: https://github.com/marsfoundation/xchain-helpers/issues/24 - cctpBridge.lastDestinationLogIndex = cctpBridge.lastSourceLogIndex; - cctpBridge.relayMessagesToSource(true); - - vm.startPrank(safe); - mainnetController.swapUSDCToUSDS(1000e6); - mainnetController.burnUSDS(1000e18); - vm.stopPrank(); - } - -} From 0abe3b0128dde939b6d0e8bf5b78f8261382369d Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Thu, 28 Nov 2024 10:28:17 -0500 Subject: [PATCH 03/22] feat: Add Ethena integration (#55) * feat: Add non-4626 related Ethena functions (SC-839) (#52) * feat: add new functions, update sub * feat: set up initial testing * feat: update to get shanghai to pass * feat: add rate limits * fix: update to add state assertions * fix: use standard rate limits * feat: Refactor to use generalized 4626 (SC-840) (#53) * feat: refactor to use generalized 4626 * feat: update helper * feat: Add e2e test for Ethena (SC-841) (#54) * feat: add new functions, update sub * feat: set up initial testing * feat: update to get shanghai to pass * feat: refactor to use generalized 4626 * feat: update helper * feat: add rate limits * feat: add e2e test * feat: push to use shares * feat: Add rate limit on cooldowns (#59) * feat: add rate limits to cooldowns, update testing * fix: consolidate rate limits --- deploy/ControllerInit.sol | 7 + foundry.toml | 1 + script/staging/StagingDeploymentBase.sol | 3 +- src/MainnetController.sol | 154 ++- .../{SNstCalls.t.sol => 4626Calls.t.sol} | 55 +- test/mainnet-fork/DeployAndInit.t.sol | 7 +- test/mainnet-fork/Ethena.t.sol | 882 ++++++++++++++++++ test/mainnet-fork/ForkTestBase.t.sol | 24 +- 8 files changed, 1069 insertions(+), 64 deletions(-) rename test/mainnet-fork/{SNstCalls.t.sol => 4626Calls.t.sol} (79%) create mode 100644 test/mainnet-fork/Ethena.t.sol diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol index 3e0bb72..e99a042 100644 --- a/deploy/ControllerInit.sol +++ b/deploy/ControllerInit.sol @@ -65,6 +65,7 @@ library MainnetControllerInit { RateLimitData usdsToUsdcData; RateLimitData usdcToCctpData; RateLimitData cctpToBaseDomainData; + RateLimitData susdsDepositData; } bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; @@ -129,10 +130,16 @@ library MainnetControllerInit { CCTPForwarder.DOMAIN_ID_CIRCLE_BASE ); + bytes32 susdsKey = RateLimitHelpers.makeAssetKey( + controller.LIMIT_4626_DEPOSIT(), + addresses.susds + ); + _setRateLimitData(controller.LIMIT_USDS_MINT(), rateLimits, data.usdsMintData, "usdsMintData", 18); _setRateLimitData(controller.LIMIT_USDS_TO_USDC(), rateLimits, data.usdsToUsdcData, "usdsToUsdcData", 6); _setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); _setRateLimitData(domainKeyBase, rateLimits, data.cctpToBaseDomainData, "cctpToBaseDomainData", 6); + _setRateLimitData(susdsKey, rateLimits, data.susdsDepositData, "susdsDepositData", 18); // Step 4: Configure the mint recipients on other domains diff --git a/foundry.toml b/foundry.toml index 931751c..dce0f71 100644 --- a/foundry.toml +++ b/foundry.toml @@ -18,6 +18,7 @@ fs_permissions = [ { access = "read", path = "./script/input/"}, { access = "read-write", path = "./script/output/"} ] +evm_version = 'shanghai' [fuzz] runs = 1000 diff --git a/script/staging/StagingDeploymentBase.sol b/script/staging/StagingDeploymentBase.sol index 582e4f7..6f7ed57 100644 --- a/script/staging/StagingDeploymentBase.sol +++ b/script/staging/StagingDeploymentBase.sol @@ -322,7 +322,8 @@ contract StagingDeploymentBase is Script { usdsMintData : rateLimitData18, usdsToUsdcData : rateLimitData6, usdcToCctpData : unlimitedRateLimit, - cctpToBaseDomainData : rateLimitData6 + cctpToBaseDomainData : rateLimitData6, + susdsDepositData : rateLimitData18 }), mintRecipients: mintRecipients }); diff --git a/src/MainnetController.sol b/src/MainnetController.sol index b0d646c..b36c7e8 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -6,6 +6,8 @@ import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; +import { Ethereum } from "lib/spark-address-registry/src/Ethereum.sol"; + import { IALMProxy } from "src/interfaces/IALMProxy.sol"; import { ICCTPLike } from "src/interfaces/CCTPInterfaces.sol"; import { IRateLimits } from "src/interfaces/IRateLimits.sol"; @@ -18,10 +20,21 @@ interface IDaiUsdsLike { function usdsToDai(address usr, uint256 wad) external; } +interface IEthenaMinterLike { + function setDelegatedSigner(address delegateSigner) external; + function removeDelegatedSigner(address delegateSigner) external; +} + interface ISUSDSLike is IERC4626 { function usds() external view returns(address); } +interface ISUSDELike is IERC4626 { + function cooldownAssets(uint256 usdeAmount) external; + function cooldownShares(uint256 susdeAmount) external; + function unstake(address receiver) external; +} + interface IVaultLike { function buffer() external view returns(address); function draw(uint256 usdsAmount) external; @@ -63,23 +76,30 @@ contract MainnetController is AccessControl { bytes32 public constant FREEZER = keccak256("FREEZER"); bytes32 public constant RELAYER = keccak256("RELAYER"); + bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); + bytes32 public constant LIMIT_SUSDE_COOLDOWN = keccak256("LIMIT_SUSDE_COOLDOWN"); bytes32 public constant LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP"); bytes32 public constant LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN"); + bytes32 public constant LIMIT_USDE_BURN = keccak256("LIMIT_USDE_BURN"); + bytes32 public constant LIMIT_USDE_MINT = keccak256("LIMIT_USDE_MINT"); bytes32 public constant LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT"); bytes32 public constant LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC"); address public immutable buffer; - IALMProxy public immutable proxy; - IRateLimits public immutable rateLimits; - ICCTPLike public immutable cctp; - IDaiUsdsLike public immutable daiUsds; - IPSMLike public immutable psm; - IVaultLike public immutable vault; + IALMProxy public immutable proxy; + ICCTPLike public immutable cctp; + IDaiUsdsLike public immutable daiUsds; + IEthenaMinterLike public immutable ethenaMinter; + IPSMLike public immutable psm; + IRateLimits public immutable rateLimits; + IVaultLike public immutable vault; IERC20 public immutable dai; IERC20 public immutable usds; + IERC20 public immutable usde; IERC20 public immutable usdc; + ISUSDELike public immutable susde; ISUSDSLike public immutable susds; uint256 public immutable psmTo18ConversionFactor; @@ -112,10 +132,14 @@ contract MainnetController is AccessControl { daiUsds = IDaiUsdsLike(daiUsds_); cctp = ICCTPLike(cctp_); - susds = ISUSDSLike(susds_ ); + ethenaMinter = IEthenaMinterLike(Ethereum.ETHENA_MINTER); + + susde = ISUSDELike(Ethereum.SUSDE); + susds = ISUSDSLike(susds_); dai = IERC20(daiUsds.dai()); usdc = IERC20(psm.gem()); usds = IERC20(susds.usds()); + usde = IERC20(Ethereum.USDE); psmTo18ConversionFactor = psm.to18ConversionFactor(); @@ -203,56 +227,134 @@ contract MainnetController is AccessControl { } /**********************************************************************************************/ - /*** Relayer sUSDS functions ***/ + /*** Relayer ERC4626 functions ***/ /**********************************************************************************************/ - function depositToSUSDS(uint256 usdsAmount) - external onlyRole(RELAYER) isActive returns (uint256 shares) + function depositERC4626(address token, uint256 amount) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, token), + amount + ) + returns (uint256 shares) { - // Approve USDS to sUSDS from the proxy (assumes the proxy has enough USDS). + // Note that whitelist is done by rate limits + IERC20 asset = IERC20(IERC4626(token).asset()); + + // Approve asset to token from the proxy (assumes the proxy has enough of the asset). proxy.doCall( - address(usds), - abi.encodeCall(usds.approve, (address(susds), usdsAmount)) + address(asset), + abi.encodeCall(asset.approve, (token, amount)) ); - // Deposit USDS into sUSDS, proxy receives sUSDS shares, decode the resulting shares + // Deposit asset into the token, proxy receives token shares, decode the resulting shares shares = abi.decode( proxy.doCall( - address(susds), - abi.encodeCall(susds.deposit, (usdsAmount, address(proxy))) + token, + abi.encodeCall(IERC4626(token).deposit, (amount, address(proxy))) ), (uint256) ); } - function withdrawFromSUSDS(uint256 usdsAmount) + function withdrawERC4626(address token, uint256 amount) external onlyRole(RELAYER) isActive returns (uint256 shares) { - // Withdraw USDS from sUSDS, decode resulting shares. - // Assumes proxy has adequate sUSDS shares. + // Withdraw asset from a token, decode resulting shares. + // Assumes proxy has adequate token shares. shares = abi.decode( proxy.doCall( - address(susds), - abi.encodeCall(susds.withdraw, (usdsAmount, address(proxy), address(proxy))) + token, + abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy))) ), (uint256) ); } - function redeemFromSUSDS(uint256 susdsSharesAmount) + function redeemERC4626(address token, uint256 shares) external onlyRole(RELAYER) isActive returns (uint256 assets) { - // Redeem shares for USDS from sUSDS, decode the resulting assets. - // Assumes proxy has adequate sUSDS shares. + // Redeem shares for assets from the token, decode the resulting assets. + // Assumes proxy has adequate token shares. assets = abi.decode( proxy.doCall( - address(susds), - abi.encodeCall(susds.redeem, (susdsSharesAmount, address(proxy), address(proxy))) + token, + abi.encodeCall(IERC4626(token).redeem, (shares, address(proxy), address(proxy))) ), (uint256) ); } + /**********************************************************************************************/ + /*** Ethena functions ***/ + /**********************************************************************************************/ + + function setDelegatedSigner(address delegatedSigner) external onlyRole(RELAYER) isActive { + proxy.doCall( + address(ethenaMinter), + abi.encodeCall(ethenaMinter.setDelegatedSigner, (address(delegatedSigner))) + ); + } + + function removeDelegatedSigner(address delegatedSigner) external onlyRole(RELAYER) isActive { + proxy.doCall( + address(ethenaMinter), + abi.encodeCall(ethenaMinter.removeDelegatedSigner, (address(delegatedSigner))) + ); + } + + // Note that 2m per block includes other users + function prepareUSDeMint(uint256 usdcAmount) + external onlyRole(RELAYER) isActive rateLimited(LIMIT_USDE_MINT, usdcAmount) + { + proxy.doCall( + address(usdc), + abi.encodeCall(usdc.approve, (address(ethenaMinter), usdcAmount)) + ); + } + + function prepareUSDeBurn(uint256 usdeAmount) + external onlyRole(RELAYER) isActive rateLimited(LIMIT_USDE_BURN, usdeAmount) + { + proxy.doCall( + address(usde), + abi.encodeCall(usde.approve, (address(ethenaMinter), usdeAmount)) + ); + } + + function cooldownAssetsSUSDe(uint256 usdeAmount) + external onlyRole(RELAYER) isActive rateLimited(LIMIT_SUSDE_COOLDOWN, usdeAmount) + { + proxy.doCall( + address(susde), + abi.encodeCall(susde.cooldownAssets, (usdeAmount)) + ); + } + + function cooldownSharesSUSDe(uint256 susdeAmount) + external + onlyRole(RELAYER) + isActive + rateLimited( + LIMIT_SUSDE_COOLDOWN, + susde.convertToAssets(susdeAmount) + ) + { + proxy.doCall( + address(susde), + abi.encodeCall(susde.cooldownShares, (susdeAmount)) + ); + } + + function unstakeSUSDe() external onlyRole(RELAYER) isActive { + proxy.doCall( + address(susde), + abi.encodeCall(susde.unstake, (address(proxy))) + ); + } + /**********************************************************************************************/ /*** Relayer PSM functions ***/ /**********************************************************************************************/ diff --git a/test/mainnet-fork/SNstCalls.t.sol b/test/mainnet-fork/4626Calls.t.sol similarity index 79% rename from test/mainnet-fork/SNstCalls.t.sol rename to test/mainnet-fork/4626Calls.t.sol index bfa6f3c..fb8e6b8 100644 --- a/test/mainnet-fork/SNstCalls.t.sol +++ b/test/mainnet-fork/4626Calls.t.sol @@ -35,31 +35,31 @@ contract SUSDSTestBase is ForkTestBase { } -contract MainnetControllerDepositToSUSDSFailureTests is SUSDSTestBase { +contract MainnetControllerDepositERC4626FailureTests is SUSDSTestBase { - function test_depositToSUSDS_notRelayer() external { + function test_depositERC4626_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( "AccessControlUnauthorizedAccount(address,bytes32)", address(this), RELAYER )); - mainnetController.depositToSUSDS(1e18); + mainnetController.depositERC4626(address(susds), 1e18); } - function test_depositToSUSDS_frozen() external { + function test_depositERC4626_frozen() external { vm.prank(freezer); mainnetController.freeze(); vm.prank(relayer); vm.expectRevert("MainnetController/not-active"); - mainnetController.depositToSUSDS(1e18); + mainnetController.depositERC4626(address(susds), 1e18); } } -contract MainnetControllerDepositToSUSDSTests is SUSDSTestBase { +contract MainnetControllerDepositERC4626Tests is SUSDSTestBase { - function test_depositToSUSDS() external { + function test_depositERC4626() external { vm.prank(relayer); mainnetController.mintUSDS(1e18); @@ -75,7 +75,7 @@ contract MainnetControllerDepositToSUSDSTests is SUSDSTestBase { assertEq(susds.balanceOf(address(almProxy)), 0); vm.prank(relayer); - uint256 shares = mainnetController.depositToSUSDS(1e18); + uint256 shares = mainnetController.depositERC4626(address(susds), 1e18); assertEq(shares, SUSDS_CONVERTED_SHARES); @@ -93,34 +93,34 @@ contract MainnetControllerDepositToSUSDSTests is SUSDSTestBase { } -contract MainnetControllerWithdrawFromSUSDSFailureTests is SUSDSTestBase { +contract MainnetControllerWithdrawERC4626FailureTests is SUSDSTestBase { - function test_withdrawFromSUSDS_notRelayer() external { + function test_withdrawERC4626_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( "AccessControlUnauthorizedAccount(address,bytes32)", address(this), RELAYER )); - mainnetController.withdrawFromSUSDS(1e18); + mainnetController.withdrawERC4626(address(susds), 1e18); } - function test_withdrawFromSUSDS_frozen() external { + function test_withdrawERC4626_frozen() external { vm.prank(freezer); mainnetController.freeze(); vm.prank(relayer); vm.expectRevert("MainnetController/not-active"); - mainnetController.withdrawFromSUSDS(1e18); + mainnetController.withdrawERC4626(address(susds), 1e18); } } -contract MainnetControllerWithdrawFromSUSDSTests is SUSDSTestBase { +contract MainnetControllerWithdrawERC4626Tests is SUSDSTestBase { - function test_withdrawFromSUSDS() external { + function test_withdrawERC4626() external { vm.startPrank(relayer); mainnetController.mintUSDS(1e18); - mainnetController.depositToSUSDS(1e18); + mainnetController.depositERC4626(address(susds), 1e18); vm.stopPrank(); assertEq(usds.balanceOf(address(almProxy)), 0); @@ -136,7 +136,7 @@ contract MainnetControllerWithdrawFromSUSDSTests is SUSDSTestBase { // Max available with rounding vm.prank(relayer); - uint256 shares = mainnetController.withdrawFromSUSDS(1e18 - 1); // Rounding + uint256 shares = mainnetController.withdrawERC4626(address(susds), 1e18 - 1); // Rounding assertEq(shares, SUSDS_CONVERTED_SHARES); @@ -154,35 +154,34 @@ contract MainnetControllerWithdrawFromSUSDSTests is SUSDSTestBase { } -contract MainnetControllerRedeemFromSUSDSFailureTests is SUSDSTestBase { +contract MainnetControllerRedeemERC4626FailureTests is SUSDSTestBase { - function test_redeemFromSUSDS_notRelayer() external { + function test_redeemERC4626_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( "AccessControlUnauthorizedAccount(address,bytes32)", address(this), RELAYER )); - mainnetController.redeemFromSUSDS(1e18); + mainnetController.redeemERC4626(address(susds), 1e18); } - function test_redeemFromSUSDS_frozen() external { + function test_redeemERC4626_frozen() external { vm.prank(freezer); mainnetController.freeze(); vm.prank(relayer); vm.expectRevert("MainnetController/not-active"); - mainnetController.redeemFromSUSDS(1e18); + mainnetController.redeemERC4626(address(susds), 1e18); } } +contract MainnetControllerRedeemERC4626Tests is SUSDSTestBase { -contract MainnetControllerRedeemFromSUSDSTests is SUSDSTestBase { - - function test_redeemFromSUSDS() external { + function test_redeemERC4626() external { vm.startPrank(relayer); mainnetController.mintUSDS(1e18); - mainnetController.depositToSUSDS(1e18); + mainnetController.depositERC4626(address(susds), 1e18); vm.stopPrank(); assertEq(usds.balanceOf(address(almProxy)), 0); @@ -197,7 +196,7 @@ contract MainnetControllerRedeemFromSUSDSTests is SUSDSTestBase { assertEq(susds.balanceOf(address(almProxy)), SUSDS_CONVERTED_SHARES); vm.prank(relayer); - uint256 assets = mainnetController.redeemFromSUSDS(SUSDS_CONVERTED_SHARES); + uint256 assets = mainnetController.redeemERC4626(address(susds), SUSDS_CONVERTED_SHARES); assertEq(assets, 1e18 - 1); // Rounding @@ -214,5 +213,3 @@ contract MainnetControllerRedeemFromSUSDSTests is SUSDSTestBase { } } - - diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/DeployAndInit.t.sol index dd0bb85..4f479ff 100644 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ b/test/mainnet-fork/DeployAndInit.t.sol @@ -76,7 +76,7 @@ contract MainnetControllerDeployInitTestBase is ForkTestBase { susds : Ethereum.SUSDS }); - RateLimitData memory usdsMintData = RateLimitData({ + RateLimitData memory standardUsdsData = RateLimitData({ maxAmount : 1_000_000e18, slope : uint256(1_000_000e18) / 4 hours }); @@ -97,10 +97,11 @@ contract MainnetControllerDeployInitTestBase is ForkTestBase { }); rateLimitData = MainnetControllerInit.InitRateLimitData({ - usdsMintData : usdsMintData, + usdsMintData : standardUsdsData, usdsToUsdcData : usdsToUsdcData, usdcToCctpData : usdcToCctpData, - cctpToBaseDomainData : cctpToBaseDomainData + cctpToBaseDomainData : cctpToBaseDomainData, + susdsDepositData : standardUsdsData }); mintRecipients = new MintRecipient[](1); diff --git a/test/mainnet-fork/Ethena.t.sol b/test/mainnet-fork/Ethena.t.sol new file mode 100644 index 0000000..95b2d9e --- /dev/null +++ b/test/mainnet-fork/Ethena.t.sol @@ -0,0 +1,882 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "test/mainnet-fork/ForkTestBase.t.sol"; + +interface IEthenaMinterLike { + function delegatedSigner(address signer, address owner) external view returns (uint8); +} + +contract MainnetControllerSetDelegatedSignerFailureTests is ForkTestBase { + + function test_setDelegatedSigner_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.setDelegatedSigner(makeAddr("signer")); + } + + function test_setDelegatedSigner_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.setDelegatedSigner(makeAddr("signer")); + } + +} + +contract MainnetControllerSetDelegatedSignerSuccessTests is ForkTestBase { + + event DelegatedSignerInitiated(address indexed delegateTo, address indexed initiatedBy); + + function test_setDelegatedSigner() external { + address signer = makeAddr("signer"); + + IEthenaMinterLike ethenaMinter = IEthenaMinterLike(ETHENA_MINTER); + + assertEq(ethenaMinter.delegatedSigner(signer, address(almProxy)), 0); // REJECTED + + vm.prank(relayer); + vm.expectEmit(ETHENA_MINTER); + emit DelegatedSignerInitiated(signer, address(almProxy)); + mainnetController.setDelegatedSigner(signer); + + assertEq(ethenaMinter.delegatedSigner(signer, address(almProxy)), 1); // PENDING + } + +} + +contract MainnetControllerRemoveDelegatedSignerFailureTests is ForkTestBase { + + function test_removeDelegatedSigner_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.removeDelegatedSigner(makeAddr("signer")); + } + + function test_removeDelegatedSigner_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.removeDelegatedSigner(makeAddr("signer")); + } + +} + +contract MainnetControllerRemoveDelegatedSignerSuccessTests is ForkTestBase { + + event DelegatedSignerRemoved(address indexed removedSigner, address indexed initiatedBy); + + function test_removeDelegatedSigner() external { + address signer = makeAddr("signer"); + + IEthenaMinterLike ethenaMinter = IEthenaMinterLike(ETHENA_MINTER); + + vm.prank(relayer); + mainnetController.setDelegatedSigner(signer); + + assertEq(ethenaMinter.delegatedSigner(signer, address(almProxy)), 1); // PENDING + + vm.prank(relayer); + vm.expectEmit(ETHENA_MINTER); + emit DelegatedSignerRemoved(signer, address(almProxy)); + mainnetController.removeDelegatedSigner(signer); + + assertEq(ethenaMinter.delegatedSigner(signer, address(almProxy)), 0); // REJECTED + } + +} + +contract MainnetControllerPrepareUSDeMintFailureTests is ForkTestBase { + + function test_prepareUSDeMint_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.prepareUSDeMint(100); + } + + function test_prepareUSDeMint_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.prepareUSDeMint(100); + } + + function test_prepareUSDeMint_rateLimitBoundary() external { + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_USDE_MINT(), + 100e6, + uint256(100e6) / 1 hours + ); + vm.stopPrank(); + + vm.prank(relayer); + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.prepareUSDeMint(100e6 + 1); + + vm.prank(relayer); + mainnetController.prepareUSDeMint(100e6); + } + +} + +contract MainnetControllerPrepareUSDeMintSuccessTests is ForkTestBase { + + bytes32 key; + + function setUp() public override { + super.setUp(); + + key = mainnetController.LIMIT_USDE_MINT(); + + vm.prank(SPARK_PROXY); + rateLimits.setRateLimitData(key, 5_000_000e6, uint256(1_000_000e6) / 4 hours); + } + + function test_prepareUSDeMint() external { + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 0); + + vm.prank(relayer); + mainnetController.prepareUSDeMint(100e6); + + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 100e6); + } + + function test_prepareUSDeMint_rateLimits() external { + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e6); + + vm.prank(relayer); + mainnetController.prepareUSDeMint(4_000_000e6); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(key), 2_000_000e6 - 6400); // Rounding + + vm.prank(relayer); + mainnetController.prepareUSDeMint(600_000e6); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_400_000e6 - 6400); // Rounding + } + +} + +contract MainnetControllerPrepareUSDeBurnFailureTests is ForkTestBase { + + function test_prepareUSDeBurn_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.prepareUSDeBurn(100); + } + + function test_prepareUSDeBurn_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.prepareUSDeBurn(100); + } + + function test_prepareUSDeBurn_rateLimitBoundary() external { + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_USDE_BURN(), + 100e18, + uint256(100e18) / 1 hours + ); + vm.stopPrank(); + + vm.prank(relayer); + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.prepareUSDeBurn(100e18 + 1); + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(100e18); + } + +} + +contract MainnetControllerPrepareUSDeBurnSuccessTests is ForkTestBase { + + bytes32 key; + + function setUp() public override { + super.setUp(); + + key = mainnetController.LIMIT_USDE_BURN(); + + vm.prank(SPARK_PROXY); + rateLimits.setRateLimitData(key, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + } + + function test_prepareUSDeBurn() external { + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(100e18); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 100e18); + } + + function test_prepareUSDeBurn_rateLimits() external { + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18); + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(4_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e18); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(key), 2_000_000e18 - 6400); // Rounding + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(600_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_400_000e18 - 6400); // Rounding + } + +} + +contract MainnetControllerCooldownAssetsSUSDeFailureTests is ForkTestBase { + + function test_cooldownAssetsSUSDe_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.cooldownAssetsSUSDe(100e18); + } + + function test_cooldownAssetsSUSDe_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.cooldownAssetsSUSDe(100e18); + } + + function test_cooldownAssetsSUSDe_rateLimitBoundary() external { + // For success case (exchange rate is more than 1:1) + deal(address(susde), address(almProxy), 100e18); + + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_SUSDE_COOLDOWN(), + 100e18, + uint256(100e18) / 1 hours + ); + vm.stopPrank(); + + vm.prank(relayer); + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.cooldownAssetsSUSDe(100e18 + 1); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(100e18); + } + +} + +contract MainnetControllerCooldownAssetsSUSDeSuccessTests is ForkTestBase { + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + bytes32 key; + + function setUp() public override { + super.setUp(); + + key = mainnetController.LIMIT_SUSDE_COOLDOWN(); + + vm.prank(SPARK_PROXY); + rateLimits.setRateLimitData(key, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + } + + function test_cooldownAssetsSUSDe() external { + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + uint256 assets = susde.convertToAssets(100e18); + + // Exchange rate is more than 1:1 + deal(address(susde), address(almProxy), 100e18); + + assertEq(susde.balanceOf(address(almProxy)), 100e18); + assertEq(usde.balanceOf(silo), startingSiloBalance); + + vm.prank(relayer); + vm.expectEmit(address(susde)); + emit Withdraw(address(almProxy), silo, address(almProxy), assets, 100e18); + mainnetController.cooldownAssetsSUSDe(assets); + + assertEq(susde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(silo), startingSiloBalance + assets); + } + + function test_cooldownAssetsSUSDe_rateLimits() external { + // Exchange rate is more than 1:1 + deal(address(susde), address(almProxy), 5_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(4_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e18); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(key), 2_000_000e18 - 6400); // Rounding + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(600_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 1_400_000e18 - 6400); // Rounding + } + +} + +contract MainnetControllerCooldownSharesSUSDeFailureTests is ForkTestBase { + + function test_cooldownSharesSUSDe_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.cooldownSharesSUSDe(100); + } + + function test_cooldownSharesSUSDe_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.cooldownSharesSUSDe(100); + } + + function test_cooldownSharesSUSDe_rateLimitBoundary() external { + deal(address(susde), address(almProxy), 100e18); // For success case + + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_SUSDE_COOLDOWN(), + 100e18, + uint256(100e18) / 1 hours + ); + vm.stopPrank(); + + uint256 overBoundaryShares = susde.convertToShares(100e18 + 2); + uint256 boundaryShares = susde.convertToShares(100e18 + 1); + + // Demonstrate how rounding works + assertEq(susde.convertToAssets(overBoundaryShares), 100e18 + 1); + assertEq(susde.convertToAssets(boundaryShares), 100e18); + + vm.prank(relayer); + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.cooldownSharesSUSDe(overBoundaryShares); + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(boundaryShares); + } + +} + +contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + bytes32 key; + + function setUp() public override { + super.setUp(); + + key = mainnetController.LIMIT_SUSDE_COOLDOWN(); + + vm.prank(SPARK_PROXY); + rateLimits.setRateLimitData(key, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + } + + function test_cooldownSharesSUSDe() external { + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + uint256 assets = susde.convertToAssets(100e18); + + deal(address(susde), address(almProxy), 100e18); + + assertEq(susde.balanceOf(address(almProxy)), 100e18); + assertEq(usde.balanceOf(silo), startingSiloBalance); + + vm.prank(relayer); + vm.expectEmit(address(susde)); + emit Withdraw(address(almProxy), silo, address(almProxy), assets, 100e18); + mainnetController.cooldownSharesSUSDe(100e18); + + assertEq(susde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(silo), startingSiloBalance + assets); + } + + function test_cooldownSharesSUSDe_rateLimits() external { + // Exchange rate is more than 1:1 + deal(address(susde), address(almProxy), 5_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(4_000_000e18); + + uint256 assets1 = susde.convertToAssets(4_000_000e18); + + assertGe(assets1, 4_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1 + (1_000_000e18 - 6400)); // Rounding + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(600_000e18); + + uint256 assets2 = susde.convertToAssets(600_000e18); + + assertGe(assets2, 600_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1 + (1_000_000e18 - 6400) - assets2); + } + +} + +contract MainnetControllerUnstakeSUSDeFailureTests is ForkTestBase { + + function test_unstakeSUSDe_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.unstakeSUSDe(); + } + + function test_unstakeSUSDe_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.unstakeSUSDe(); + } + + function test_unstakeSUSDe_cooldownBoundary() external { + // Exchange rate greater than 1:1 + deal(address(susde), address(almProxy), 100e18); + + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_SUSDE_COOLDOWN(), + 100e18, + uint256(100e18) / 1 hours + ); + vm.stopPrank(); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(100e18); + + skip(7 days - 1); // Cooldown period boundary + + vm.prank(relayer); + vm.expectRevert(abi.encodeWithSignature("InvalidCooldown()")); + mainnetController.unstakeSUSDe(); + + skip(1 seconds); + + vm.prank(relayer); + mainnetController.unstakeSUSDe(); + } + +} + +contract MainnetControllerUnstakeSUSDeSuccessTests is ForkTestBase { + + function test_unstakeSUSDe() external { + // Setting higher rate limit so shares can be used for cooldown + vm.startPrank(SPARK_PROXY); + rateLimits.setRateLimitData( + mainnetController.LIMIT_SUSDE_COOLDOWN(), + 1000e18, + uint256(1000e18) / 1 hours + ); + vm.stopPrank(); + + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + uint256 assets = susde.convertToAssets(100e18); + + deal(address(susde), address(almProxy), 100e18); + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(100e18); + + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(silo), startingSiloBalance + assets); + + skip(7 days); // Cooldown period + + vm.prank(relayer); + mainnetController.unstakeSUSDe(); + + assertEq(usde.balanceOf(address(almProxy)), assets); + assertEq(usde.balanceOf(silo), startingSiloBalance); + } + +} + +contract MainnetControllerEthenaE2ETests is ForkTestBase { + + address signer = makeAddr("signer"); + + bytes32 burnKey; + bytes32 cooldownKey; + bytes32 depositKey; + bytes32 mintKey; + + function setUp() public override { + super.setUp(); + + vm.startPrank(SPARK_PROXY); + + burnKey = mainnetController.LIMIT_USDE_BURN(); + cooldownKey = mainnetController.LIMIT_SUSDE_COOLDOWN(); + depositKey = RateLimitHelpers.makeAssetKey(mainnetController.LIMIT_4626_DEPOSIT(), address(susde)); + mintKey = mainnetController.LIMIT_USDE_MINT(); + + rateLimits.setRateLimitData(burnKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + rateLimits.setRateLimitData(cooldownKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + rateLimits.setRateLimitData(depositKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + rateLimits.setRateLimitData(mintKey, 5_000_000e6, uint256(1_000_000e6) / 4 hours); + + vm.stopPrank(); + } + + // NOTE: In reality this is performed by the signer submitting an order with an EIP712 signature + // which is verified by the ethenaMinter contract, minting USDe into the ALMProxy. + // Also, for the purposes of this test, minting is done 1:1 with USDC. + function _simulateUsdeMint(uint256 amount) internal { + vm.prank(ETHENA_MINTER); + usdc.transferFrom(address(almProxy), ETHENA_MINTER, amount); + deal(address(usde), address(almProxy), amount * 1e12); + } + + // NOTE: In reality this is performed by the signer submitting an order with an EIP712 signature + // which is verified by the ethenaMinter contract, minting USDe into the ALMProxy. + // Also, for the purposes of this test, minting is done 1:1 with USDC. + function _simulateUsdeBurn(uint256 amount) internal { + vm.prank(ETHENA_MINTER); + usde.transferFrom(address(almProxy), ETHENA_MINTER, amount); + deal(address(usdc), address(almProxy), amount / 1e12); + } + + function test_ethena_e2eFlowUsingAssets() external { + deal(address(usdc), address(almProxy), 1_000_000e6); + + uint256 startingMinterBalance = usdc.balanceOf(ETHENA_MINTER); // From mainnet state + + // Step 1: Mint USDe + + assertEq(rateLimits.getCurrentRateLimit(mintKey), 5_000_000e6); + + vm.prank(relayer); + mainnetController.prepareUSDeMint(1_000_000e6); + + assertEq(rateLimits.getCurrentRateLimit(mintKey), 4_000_000e6); + + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e6); + + assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdc.balanceOf(ETHENA_MINTER), startingMinterBalance); + + assertEq(usde.balanceOf(address(almProxy)), 0); + + _simulateUsdeMint(1_000_000e6); + + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e6); + + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18); + + // Step 2: Convert half of assets to sUSDe + + uint256 startingAssets = usde.balanceOf(address(susde)); + + assertEq(usde.allowance(address(almProxy), address(susde)), 0); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + assertEq(usde.balanceOf(address(susde)), startingAssets); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(depositKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.depositERC4626(address(susde), 500_000e18); + + assertEq(rateLimits.getCurrentRateLimit(depositKey), 4_500_000e18); + + assertEq(usde.allowance(address(almProxy), address(susde)), 0); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + + assertEq(usde.balanceOf(address(susde)), startingAssets + 500_000e18); + assertEq(usde.balanceOf(address(almProxy)), 500_000e18); + + // Step 3: Cooldown sUSDe + + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + + assertEq(usde.balanceOf(silo), startingSiloBalance); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(500_000e18 - 2); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 2); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + + // Step 4: Wait for cooldown window to pass then unstake sUSDe + + skip(7 days); + + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 500_000e18); + + vm.prank(relayer); + mainnetController.unstakeSUSDe(); + + assertEq(usde.balanceOf(silo), startingSiloBalance); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + + // Step 5: Redeem USDe for USDC + + startingMinterBalance = usde.balanceOf(ETHENA_MINTER); // From mainnet state + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(1_000_000e18 - 2); + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 2); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 2); + + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance); + + assertEq(usdc.balanceOf(address(almProxy)), 0); + + _simulateUsdeBurn(1_000_000e18 - 2); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 2); + + assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6 - 1); // Rounding + } + + function test_ethena_e2eFlowUsingShares() external { + deal(address(usdc), address(almProxy), 1_000_000e6); + + uint256 startingMinterBalance = usdc.balanceOf(ETHENA_MINTER); // From mainnet state + + // Step 1: Mint USDe + + assertEq(rateLimits.getCurrentRateLimit(mintKey), 5_000_000e6); + + vm.prank(relayer); + mainnetController.prepareUSDeMint(1_000_000e6); + + assertEq(rateLimits.getCurrentRateLimit(mintKey), 4_000_000e6); + + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e6); + + assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdc.balanceOf(ETHENA_MINTER), startingMinterBalance); + + assertEq(usde.balanceOf(address(almProxy)), 0); + + _simulateUsdeMint(1_000_000e6); + + assertEq(usdc.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e6); + + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18); + + // Step 2: Convert half of assets to sUSDe + + uint256 startingAssets = usde.balanceOf(address(susde)); + + assertEq(usde.allowance(address(almProxy), address(susde)), 0); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + assertEq(usde.balanceOf(address(susde)), startingAssets); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(depositKey), 5_000_000e18); + + vm.prank(relayer); + uint256 susdeShares = mainnetController.depositERC4626(address(susde), 500_000e18); + + assertEq(rateLimits.getCurrentRateLimit(depositKey), 4_500_000e18); + + assertEq(susde.balanceOf(address(almProxy)), susdeShares); + + assertEq(usde.allowance(address(almProxy), address(susde)), 0); + + assertEq(susde.convertToAssets(susdeShares), 500_000e18 - 2); // Rounding + + assertEq(usde.balanceOf(address(susde)), startingAssets + 500_000e18); + assertEq(usde.balanceOf(address(almProxy)), 500_000e18); + + // Step 3: Cooldown sUSDe + + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + + assertEq(usde.balanceOf(silo), startingSiloBalance); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(susdeShares); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 2); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + + // Step 4: Wait for cooldown window to pass then unstake sUSDe + + skip(7 days); + + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 500_000e18); + + vm.prank(relayer); + mainnetController.unstakeSUSDe(); + + assertEq(usde.balanceOf(silo), startingSiloBalance); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + + // Step 5: Redeem USDe for USDC + + startingMinterBalance = usde.balanceOf(ETHENA_MINTER); // From mainnet state + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.prepareUSDeBurn(1_000_000e18 - 2); + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 2); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 2); + + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance); + + assertEq(usdc.balanceOf(address(almProxy)), 0); + + _simulateUsdeBurn(1_000_000e18 - 2); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 2); + + assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6 - 1); // Rounding + } + + function test_e2e_cooldownSharesAndAssets_sameRateLimit() public { + // Exchange rate is more than 1:1 + deal(address(susde), address(almProxy), 5_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(4_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 1_000_000e18); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 1_000_000e18 + (1_000_000e18 - 6400)); // Rounding + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(600_000e18); + + uint256 assets2 = susde.convertToAssets(600_000e18); + + assertGe(assets2, 600_000e18); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 1_000_000e18 + (1_000_000e18 - 6400) - assets2); + } + +} diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 6741c5b..4775ab5 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -12,7 +12,8 @@ import { import { AllocatorDeploy } from "dss-allocator/deploy/AllocatorDeploy.sol"; -import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { ISUsds } from "sdai/src/ISUsds.sol"; @@ -25,7 +26,8 @@ import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; import { MainnetControllerDeploy } from "deploy/ControllerDeploy.sol"; import { ControllerInstance } from "deploy/ControllerInstance.sol"; -import { MainnetControllerInit, +import { + MainnetControllerInit, MintRecipient, RateLimitData } from "deploy/ControllerInit.sol"; @@ -43,6 +45,13 @@ interface IBufferLike { function approve(address, address, uint256) external; } +interface ISUSDELike is IERC4626 { + function cooldownAssets(uint256 usdeAmount) external; + function cooldownShares(uint256 susdeAmount) external; + function unstake(address receiver) external; + function silo() external view returns(address); +} + interface IPSMLike { function bud(address) external view returns (uint256); function pocket() external view returns (address); @@ -87,15 +96,19 @@ contract ForkTestBase is DssTest { address constant CCTP_MESSENGER = Ethereum.CCTP_TOKEN_MESSENGER; address constant DAI_USDS = Ethereum.DAI_USDS; + address constant ETHENA_MINTER = Ethereum.ETHENA_MINTER; address constant PAUSE_PROXY = Ethereum.PAUSE_PROXY; address constant PSM = Ethereum.PSM; address constant SPARK_PROXY = Ethereum.SPARK_PROXY; IERC20 constant dai = IERC20(Ethereum.DAI); IERC20 constant usdc = IERC20(Ethereum.USDC); + IERC20 constant usde = IERC20(Ethereum.USDE); IERC20 constant usds = IERC20(Ethereum.USDS); ISUsds constant susds = ISUsds(Ethereum.SUSDS); + ISUSDELike constant susde = ISUSDELike(Ethereum.SUSDE); + IPSMLike constant psm = IPSMLike(PSM); address POCKET; @@ -223,7 +236,7 @@ contract ForkTestBase is DssTest { susds : Ethereum.SUSDS }); - RateLimitData memory usdsMintData = RateLimitData({ + RateLimitData memory standardUsdsData = RateLimitData({ maxAmount : 5_000_000e18, slope : uint256(1_000_000e18) / 4 hours }); @@ -235,10 +248,11 @@ contract ForkTestBase is DssTest { MainnetControllerInit.InitRateLimitData memory rateLimitData = MainnetControllerInit.InitRateLimitData({ - usdsMintData : usdsMintData, + usdsMintData : standardUsdsData, usdsToUsdcData : standardUsdcData, usdcToCctpData : standardUsdcData, - cctpToBaseDomainData : standardUsdcData + cctpToBaseDomainData : standardUsdcData, + susdsDepositData : standardUsdsData }); MintRecipient[] memory mintRecipients = new MintRecipient[](1); From 1f9780630a6cb921450d75cd9968b0b2ceb8e959 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 29 Nov 2024 08:53:41 -0500 Subject: [PATCH 04/22] [SC-675] Add Aave support (#58) * WIP: add aave support * forge install: aave-v3-origin v3.1.0 * finish adding aave main market support * review changes * feat: Add support for Base AAVE integration (#61) * feat: udpate mainnet logic and testing * feat: add base logic and tests * fix: rmeove lib references in imports --------- Co-authored-by: Lucas Manuel --- .gitmodules | 3 + deploy/ControllerInit.sol | 2 +- foundry.toml | 1 + lib/aave-v3-origin | 1 + script/Deploy.s.sol | 4 +- script/staging/test/DeployEthereum.t.sol | 16 +- src/ForeignController.sol | 58 +++++- src/MainnetController.sol | 60 +++++- src/interfaces/IALMProxy.sol | 2 +- src/interfaces/IRateLimits.sol | 2 +- test/base-fork/Aave.t.sol | 155 +++++++++++++++ test/base-fork/DeployAndInit.t.sol | 2 +- test/base-fork/ForkTestBase.t.sol | 8 +- test/base-fork/Morpho.t.sol | 2 +- test/mainnet-fork/Aave.t.sol | 230 +++++++++++++++++++++++ test/mainnet-fork/CCTPCalls.t.sol | 4 +- test/mainnet-fork/ForkTestBase.t.sol | 2 +- 17 files changed, 527 insertions(+), 25 deletions(-) create mode 160000 lib/aave-v3-origin create mode 100644 test/base-fork/Aave.t.sol create mode 100644 test/mainnet-fork/Aave.t.sol diff --git a/.gitmodules b/.gitmodules index e64c5c3..daa52ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "lib/metamorpho"] path = lib/metamorpho url = https://github.com/morpho-org/metamorpho +[submodule "lib/aave-v3-origin"] + path = lib/aave-v3-origin + url = https://github.com/aave-dao/aave-v3-origin diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol index e99a042..ac9089d 100644 --- a/deploy/ControllerInit.sol +++ b/deploy/ControllerInit.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { CCTPForwarder } from "lib/xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { ForeignController } from "src/ForeignController.sol"; import { MainnetController } from "src/MainnetController.sol"; diff --git a/foundry.toml b/foundry.toml index dce0f71..810c90a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,6 +12,7 @@ remappings = [ "usds/=lib/usds/", "sdai/=lib/sdai/", "spark-psm/=lib/spark-psm/", + "spark-address-registry/=lib/spark-address-registry/", "xchain-helpers/=lib/xchain-helpers/", ] fs_permissions = [ diff --git a/lib/aave-v3-origin b/lib/aave-v3-origin new file mode 160000 index 0000000..e627c74 --- /dev/null +++ b/lib/aave-v3-origin @@ -0,0 +1 @@ +Subproject commit e627c7428cbb358b9c84b601a009a86b4b871c08 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6a9021b..481e55a 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; import "forge-std/Script.sol"; -import { Base } from "lib/spark-address-registry/src/Base.sol"; -import { Ethereum } from "lib/spark-address-registry/src/Ethereum.sol"; +import { Base } from "spark-address-registry/src/Base.sol"; +import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; import { ControllerInstance } from "../deploy/ControllerInstance.sol"; diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol index 7f42775..9023c30 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/DeployEthereum.t.sol @@ -10,13 +10,13 @@ import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { Usds } from "lib/usds/src/Usds.sol"; -import { SUsds } from "lib/sdai/src/SUsds.sol"; +import { Usds } from "usds/src/Usds.sol"; +import { SUsds } from "sdai/src/SUsds.sol"; -import { AllocatorVault } from "lib/dss-allocator/src/AllocatorVault.sol"; -import { AllocatorBuffer } from "lib/dss-allocator/src/AllocatorBuffer.sol"; -import { AllocatorRegistry } from "lib/dss-allocator/src/AllocatorRegistry.sol"; -import { AllocatorRoles } from "lib/dss-allocator/src/AllocatorRoles.sol"; +import { AllocatorVault } from "dss-allocator/src/AllocatorVault.sol"; +import { AllocatorBuffer } from "dss-allocator/src/AllocatorBuffer.sol"; +import { AllocatorRegistry } from "dss-allocator/src/AllocatorRegistry.sol"; +import { AllocatorRoles } from "dss-allocator/src/AllocatorRoles.sol"; import { IRateLimits } from "src/interfaces/IRateLimits.sol"; @@ -26,8 +26,8 @@ import { MainnetController } from "src/MainnetController.sol"; import { RateLimits } from "src/RateLimits.sol"; import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; -import { PSM3, IERC20 } from "lib/spark-psm/src/PSM3.sol"; -import { IRateProviderLike } from "lib/spark-psm/src/interfaces/IRateProviderLike.sol"; +import { PSM3, IERC20 } from "spark-psm/src/PSM3.sol"; +import { IRateProviderLike } from "spark-psm/src/interfaces/IRateProviderLike.sol"; interface IVatLike { function can(address, address) external view returns (uint256); diff --git a/src/ForeignController.sol b/src/ForeignController.sol index c0b832c..e851bcf 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; +import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; +import { IPool as IAavePool } from "aave-v3-origin/src/core/contracts/interfaces/IPool.sol"; + import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; @@ -14,6 +17,10 @@ import { IRateLimits } from "src/interfaces/IRateLimits.sol"; import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +interface IATokenWithPool is IAToken { + function POOL() external view returns(address); +} + contract ForeignController is AccessControl { /**********************************************************************************************/ @@ -41,11 +48,12 @@ contract ForeignController is AccessControl { bytes32 public constant FREEZER = keccak256("FREEZER"); bytes32 public constant RELAYER = keccak256("RELAYER"); + bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); + bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); bytes32 public constant LIMIT_PSM_DEPOSIT = keccak256("LIMIT_PSM_DEPOSIT"); bytes32 public constant LIMIT_PSM_WITHDRAW = keccak256("LIMIT_PSM_WITHDRAW"); bytes32 public constant LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP"); bytes32 public constant LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN"); - bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); IALMProxy public immutable proxy; ICCTPLike public immutable cctp; @@ -277,6 +285,54 @@ contract ForeignController is AccessControl { ); } + /**********************************************************************************************/ + /*** Relayer Aave functions ***/ + /**********************************************************************************************/ + + function depositAave(address aToken, uint256 amount) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_DEPOSIT, aToken), + amount + ) + { + IERC20 underlying = IERC20(IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS()); + IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL()); + + // Approve underlying to Aave pool from the proxy (assumes the proxy has enough underlying). + proxy.doCall( + address(underlying), + abi.encodeCall(underlying.approve, (address(pool), amount)) + ); + + // Deposit underlying into Aave pool, proxy receives aTokens + proxy.doCall( + address(pool), + abi.encodeCall(pool.supply, (address(underlying), amount, address(proxy), 0)) + ); + } + + function withdrawAave(address aToken, uint256 amount) + external onlyRole(RELAYER) isActive returns (uint256 amountWithdrawn) + { + IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL()); + + // Withdraw underlying from Aave pool, decode resulting amount withdrawn. + // Assumes proxy has adequate aTokens. + amountWithdrawn = abi.decode( + proxy.doCall( + address(pool), + abi.encodeCall( + pool.withdraw, + (IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS(), amount, address(proxy)) + ) + ), + (uint256) + ); + } + /**********************************************************************************************/ /*** Internal helper functions ***/ /**********************************************************************************************/ diff --git a/src/MainnetController.sol b/src/MainnetController.sol index b36c7e8..b4a25a1 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -1,12 +1,15 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; +import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; +import { IPool as IAavePool } from "aave-v3-origin/src/core/contracts/interfaces/IPool.sol"; + import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; -import { Ethereum } from "lib/spark-address-registry/src/Ethereum.sol"; +import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; import { IALMProxy } from "src/interfaces/IALMProxy.sol"; import { ICCTPLike } from "src/interfaces/CCTPInterfaces.sol"; @@ -49,6 +52,10 @@ interface IPSMLike { function to18ConversionFactor() external view returns (uint256); } +interface IATokenWithPool is IAToken { + function POOL() external view returns(address); +} + contract MainnetController is AccessControl { /**********************************************************************************************/ @@ -84,6 +91,7 @@ contract MainnetController is AccessControl { bytes32 public constant LIMIT_USDE_MINT = keccak256("LIMIT_USDE_MINT"); bytes32 public constant LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT"); bytes32 public constant LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC"); + bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); address public immutable buffer; @@ -288,7 +296,55 @@ contract MainnetController is AccessControl { } /**********************************************************************************************/ - /*** Ethena functions ***/ + /*** Relayer Aave functions ***/ + /**********************************************************************************************/ + + function depositAave(address aToken, uint256 amount) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_DEPOSIT, aToken), + amount + ) + { + IERC20 underlying = IERC20(IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS()); + IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL()); + + // Approve underlying to Aave pool from the proxy (assumes the proxy has enough underlying). + proxy.doCall( + address(underlying), + abi.encodeCall(underlying.approve, (address(pool), amount)) + ); + + // Deposit underlying into Aave pool, proxy receives aTokens + proxy.doCall( + address(pool), + abi.encodeCall(pool.supply, (address(underlying), amount, address(proxy), 0)) + ); + } + + function withdrawAave(address aToken, uint256 amount) + external onlyRole(RELAYER) isActive returns (uint256 amountWithdrawn) + { + IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL()); + + // Withdraw underlying from Aave pool, decode resulting amount withdrawn. + // Assumes proxy has adequate aTokens. + amountWithdrawn = abi.decode( + proxy.doCall( + address(pool), + abi.encodeCall( + pool.withdraw, + (IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS(), amount, address(proxy)) + ) + ), + (uint256) + ); + } + + /**********************************************************************************************/ + /*** Relayer Ethena functions ***/ /**********************************************************************************************/ function setDelegatedSigner(address delegatedSigner) external onlyRole(RELAYER) isActive { diff --git a/src/interfaces/IALMProxy.sol b/src/interfaces/IALMProxy.sol index b747a4c..3878d20 100644 --- a/src/interfaces/IALMProxy.sol +++ b/src/interfaces/IALMProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { IAccessControl } from "lib/openzeppelin-contracts/contracts/access/IAccessControl.sol"; +import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; interface IALMProxy is IAccessControl { diff --git a/src/interfaces/IRateLimits.sol b/src/interfaces/IRateLimits.sol index 3d2cfb1..baee517 100644 --- a/src/interfaces/IRateLimits.sol +++ b/src/interfaces/IRateLimits.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { IAccessControl } from "lib/openzeppelin-contracts/contracts/access/IAccessControl.sol"; +import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; interface IRateLimits is IAccessControl { diff --git a/test/base-fork/Aave.t.sol b/test/base-fork/Aave.t.sol new file mode 100644 index 0000000..bb36f45 --- /dev/null +++ b/test/base-fork/Aave.t.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "test/base-fork/ForkTestBase.t.sol"; + +import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; + +import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; + +contract AaveV3BaseMarketTestBase is ForkTestBase { + + address constant ATOKEN_USDC = 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB; + address constant POOL = 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5; + + IAToken ausdc = IAToken(ATOKEN_USDC); + + uint256 startingAUSDCBalance; + + function setUp() public override { + super.setUp(); + + vm.startPrank(Base.SPARK_EXECUTOR); + + // NOTE: Hit SUPPLY_CAP_EXCEEDED when using 25m + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_DEPOSIT(), + ATOKEN_USDC + ), + 1_000_000e6, + uint256(1_000_000e6) / 1 days + ); + + vm.stopPrank(); + + startingAUSDCBalance = usdcBase.balanceOf(address(ausdc)); + } + +} + +contract AaveV3BaseMarketDepositFailureTests is AaveV3BaseMarketTestBase { + + function test_depositAave_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e18); + } + + function test_depositAave_frozen() external { + vm.prank(freezer); + foreignController.freeze(); + + vm.prank(relayer); + vm.expectRevert("ForeignController/not-active"); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e18); + } + + function test_depositAave_usdcRateLimitedBoundary() external { + deal(Base.USDC, address(almProxy), 1_000_000e6 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + vm.startPrank(relayer); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6 + 1); + + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + } + +} + +contract AaveV3BaseMarketDepositSuccessTests is AaveV3BaseMarketTestBase { + + function test_depositAave_usdc() public { + deal(Base.USDC, address(almProxy), 1_000_000e6); + + assertEq(usdcBase.allowance(address(almProxy), POOL), 0); + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance); + + vm.prank(relayer); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + + assertEq(usdcBase.allowance(address(almProxy), POOL), 0); + + assertEq(ausdc.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdcBase.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + } + +} + +contract AaveV3BaseMarketWithdrawFailureTests is AaveV3BaseMarketTestBase { + + function test_withdrawAave_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e18); + } + + function test_withdrawAave_frozen() external { + vm.prank(freezer); + foreignController.freeze(); + + vm.prank(relayer); + vm.expectRevert("ForeignController/not-active"); + foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e18); + } + +} + +contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { + + function test_withdrawAave_usdc() public { + deal(Base.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + + skip(1 days); + + uint256 fullBalance = ausdc.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdcBase.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + + // Partial withdraw + vm.prank(relayer); + assertEq(foreignController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6); + assertEq(usdcBase.balanceOf(address(almProxy)), 400_000e6); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 600_000e6); // 1m - 400k + + // Withdraw all + vm.prank(relayer); + assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + + // Interest accrued was withdrawn, reducing cash balance + assertLe(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance); + } + +} diff --git a/test/base-fork/DeployAndInit.t.sol b/test/base-fork/DeployAndInit.t.sol index b0c48d1..fdaabe9 100644 --- a/test/base-fork/DeployAndInit.t.sol +++ b/test/base-fork/DeployAndInit.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; -import { CCTPForwarder } from "lib/xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import "test/base-fork/ForkTestBase.t.sol"; diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index b6b1d3b..602a106 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; -import { IERC20 } from "lib/forge-std/src/interfaces/IERC20.sol"; +import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; -import { Base } from "spark-address-registry/Base.sol"; +import { Base } from "spark-address-registry/src/Base.sol"; import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol"; import { IPSM3 } from "spark-psm/src/PSM3.sol"; -import { CCTPForwarder } from "lib/xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; import { ControllerInstance } from "deploy/ControllerInstance.sol"; @@ -22,7 +22,7 @@ import { ForeignControllerInit, RateLimitData } from "deploy/ControllerInit.sol"; -import { CCTPForwarder } from "lib/xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; import { ControllerInstance } from "deploy/ControllerInstance.sol"; diff --git a/test/base-fork/Morpho.t.sol b/test/base-fork/Morpho.t.sol index 1d237f8..4799fc1 100644 --- a/test/base-fork/Morpho.t.sol +++ b/test/base-fork/Morpho.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import "test/base-fork/ForkTestBase.t.sol"; -import { IERC4626 } from "lib/forge-std/src/interfaces/IERC4626.sol"; +import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol new file mode 100644 index 0000000..43c591d --- /dev/null +++ b/test/mainnet-fork/Aave.t.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "test/mainnet-fork/ForkTestBase.t.sol"; + +import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; + +contract AaveV3MainMarketBaseTest is ForkTestBase { + + address constant ATOKEN_USDS = 0x32a6268f9Ba3642Dda7892aDd74f1D34469A4259; + address constant ATOKEN_USDC = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c; + address constant POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + IAToken ausds = IAToken(ATOKEN_USDS); + IAToken ausdc = IAToken(ATOKEN_USDC); + + uint256 startingAUSDSBalance; + uint256 startingAUSDCBalance; + + function setUp() public override { + super.setUp(); + + vm.startPrank(Ethereum.SPARK_PROXY); + + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_DEPOSIT(), + ATOKEN_USDS + ), + 25_000_000e18, + uint256(5_000_000e18) / 1 days + ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_DEPOSIT(), + ATOKEN_USDC + ), + 25_000_000e6, + uint256(5_000_000e6) / 1 days + ); + + vm.stopPrank(); + + startingAUSDCBalance = usdc.balanceOf(address(ausdc)); + startingAUSDSBalance = usds.balanceOf(address(ausds)); + } + +} + +// NOTE: Only testing USDS for non-rate limit failures as it doesn't matter which asset is used + +contract AaveV3MainMarketDepositFailureTests is AaveV3MainMarketBaseTest { + + function test_depositAave_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); + } + + function test_depositAave_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); + } + + function test_depositAave_usdsRateLimitedBoundary() external { + deal(Ethereum.USDS, address(almProxy), 25_000_000e18 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + vm.startPrank(relayer); + mainnetController.depositAave(ATOKEN_USDS, 25_000_000e18 + 1); + + mainnetController.depositAave(ATOKEN_USDS, 25_000_000e18); + } + + function test_depositAave_usdcRateLimitedBoundary() external { + deal(Ethereum.USDC, address(almProxy), 25_000_000e6 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + vm.startPrank(relayer); + mainnetController.depositAave(ATOKEN_USDC, 25_000_000e6 + 1); + + mainnetController.depositAave(ATOKEN_USDC, 25_000_000e6); + } + +} + +contract AaveV3MainMarketDepositSuccessTests is AaveV3MainMarketBaseTest { + + function test_depositAave_usds() public { + deal(Ethereum.USDS, address(almProxy), 1_000_000e18); + + assertEq(usds.allowance(address(almProxy), POOL), 0); + + assertEq(ausds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance); + + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); + + assertEq(usds.allowance(address(almProxy), POOL), 0); + + assertEq(ausds.balanceOf(address(almProxy)), 1_000_000e18); + assertEq(usds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18); + } + + function test_depositAave_usdc() public { + deal(Ethereum.USDC, address(almProxy), 1_000_000e6); + + assertEq(usdc.allowance(address(almProxy), POOL), 0); + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance); + + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDC, 1_000_000e6); + + assertEq(usdc.allowance(address(almProxy), POOL), 0); + + assertEq(ausdc.balanceOf(address(almProxy)), 1_000_000e6); + assertEq(usdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + } + +} + +contract AaveV3MainMarketWithdrawFailureTests is AaveV3MainMarketBaseTest { + + function test_withdrawAave_notRelayer() external { + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + address(this), + RELAYER + )); + mainnetController.withdrawAave(ATOKEN_USDS, 1_000_000e18); + } + + function test_withdrawAave_frozen() external { + vm.prank(freezer); + mainnetController.freeze(); + + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.withdrawAave(ATOKEN_USDS, 1_000_000e18); + } + +} + +contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { + + function test_withdrawAave_usds() public { + deal(Ethereum.USDS, address(almProxy), 1_000_000e18); + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); + + skip(1 days); + + uint256 fullBalance = ausds.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e18); + + assertEq(ausds.balanceOf(address(almProxy)), fullBalance); + assertEq(usds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18); + + // Partial withdraw + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDS, 400_000e18), 400_000e18); + + assertEq(ausds.balanceOf(address(almProxy)), fullBalance - 400_000e18); + assertEq(usds.balanceOf(address(almProxy)), 400_000e18); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 600_000e18); // 1m - 400k + + // Withdraw all + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDS, type(uint256).max), fullBalance - 400_000e18); + + assertEq(ausds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(almProxy)), fullBalance); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18 - fullBalance); + + // Interest accrued was withdrawn, reducing cash balance + assertLe(usds.balanceOf(address(ausds)), startingAUSDSBalance); + } + + function test_withdrawAave_usdc() public { + deal(Ethereum.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDC, 1_000_000e6); + + skip(1 days); + + uint256 fullBalance = ausdc.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + + // Partial withdraw + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6); + assertEq(usdc.balanceOf(address(almProxy)), 400_000e6); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 600_000e6); // 1m - 400k + + // Withdraw all + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + + // Interest accrued was withdrawn, reducing cash balance + assertLe(usdc.balanceOf(address(ausdc)), startingAUSDCBalance); + } + +} diff --git a/test/mainnet-fork/CCTPCalls.t.sol b/test/mainnet-fork/CCTPCalls.t.sol index b47576c..0ded0ec 100644 --- a/test/mainnet-fork/CCTPCalls.t.sol +++ b/test/mainnet-fork/CCTPCalls.t.sol @@ -3,11 +3,11 @@ pragma solidity >=0.8.0; import "test/mainnet-fork/ForkTestBase.t.sol"; -import { IERC20 } from "lib/forge-std/src/interfaces/IERC20.sol"; +import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; -import { Base } from "spark-address-registry/Base.sol"; +import { Base } from "spark-address-registry/src/Base.sol"; import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol"; import { IPSM3 } from "spark-psm/src/PSM3.sol"; diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 4775ab5..29ba067 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -17,7 +17,7 @@ import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { ISUsds } from "sdai/src/ISUsds.sol"; -import { Ethereum } from "spark-address-registry/Ethereum.sol"; +import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; From f81a7366f339d806a07a992c3aef2afe9a063e13 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Fri, 29 Nov 2024 09:39:55 -0500 Subject: [PATCH 05/22] feat: Update to add compromised relayer attack recovery (#60) --- test/mainnet-fork/Attacks.t.sol | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/mainnet-fork/Attacks.t.sol diff --git a/test/mainnet-fork/Attacks.t.sol b/test/mainnet-fork/Attacks.t.sol new file mode 100644 index 0000000..e057cb0 --- /dev/null +++ b/test/mainnet-fork/Attacks.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "test/mainnet-fork/ForkTestBase.t.sol"; + +contract CompromisedRelayerTests is ForkTestBase { + + address newRelayer = makeAddr("newRelayer"); + bytes32 key; + + function setUp() public override { + super.setUp(); + + key = mainnetController.LIMIT_SUSDE_COOLDOWN(); + + vm.prank(SPARK_PROXY); + rateLimits.setRateLimitData(key, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + } + + function test_compromisedRelayer_lockingFundsInEthenaSilo() external { + deal(address(susde), address(almProxy), 1_000_000e18); + + address silo = susde.silo(); + + uint256 startingSiloBalance = usde.balanceOf(silo); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(1_000_000e18); + + skip(7 days); + + // Relayer is now compromised and wants to lock funds in the silo + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(1); + + // Relayer cannot withdraw when they want to + vm.prank(relayer); + vm.expectRevert(abi.encodeWithSignature("InvalidCooldown()")); + mainnetController.unstakeSUSDe(); + + vm.prank(freezer); + mainnetController.freeze(); + + skip(7 days); + + // Compromised relayer cannot perform attack + vm.prank(relayer); + vm.expectRevert("MainnetController/not-active"); + mainnetController.cooldownAssetsSUSDe(1); + + // Action taken through spell to grant access to safe new relayer, and reactivates the system + vm.startPrank(SPARK_PROXY); + mainnetController.grantRole(mainnetController.RELAYER(), newRelayer); + mainnetController.revokeRole(mainnetController.RELAYER(), relayer); + mainnetController.reactivate(); + vm.stopPrank(); + + // Compromised relayer cannot perform attack on unfrozen system + vm.prank(relayer); + vm.expectRevert(abi.encodeWithSignature( + "AccessControlUnauthorizedAccount(address,bytes32)", + relayer, + RELAYER + )); + mainnetController.cooldownAssetsSUSDe(1); + + // Funds have been locked in the silo this whole time + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(silo), startingSiloBalance + 1_000_000e18 + 1); // 1 wei deposit as well + + // New relayer can unstake the funds + vm.prank(newRelayer); + mainnetController.unstakeSUSDe(); + + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 + 1); + assertEq(usde.balanceOf(silo), startingSiloBalance); + } + +} From 2644da013f7d0d7868b3ab1b5ee744a6fb16fd7f Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 4 Dec 2024 21:55:04 -0500 Subject: [PATCH 06/22] fix: Refactor to use relative paths for src (SC-844) (#64) * fix: refactor to use relative paths for src * fix: update and reorder imports * fix: rm dup import --- deploy/ControllerDeploy.sol | 8 ++++---- deploy/ControllerInit.sol | 10 +++++----- script/staging/DeployEthereum.s.sol | 2 +- script/staging/StagingDeploymentBase.sol | 4 ++-- script/staging/test/DeployEthereum.t.sol | 12 ++++++------ src/ALMProxy.sol | 2 +- src/ForeignController.sol | 8 ++++---- src/MainnetController.sol | 8 ++++---- src/RateLimits.sol | 2 +- test/base-fork/Aave.t.sol | 6 +++--- test/base-fork/DeployAndInit.t.sol | 12 ++++++------ test/base-fork/ForkTestBase.t.sol | 25 ++++++++---------------- test/base-fork/Morpho.t.sol | 8 ++++---- test/base-fork/PsmCalls.t.sol | 4 ++-- test/mainnet-fork/4626Calls.t.sol | 2 +- test/mainnet-fork/Aave.t.sol | 4 ++-- test/mainnet-fork/Attacks.t.sol | 2 +- test/mainnet-fork/CCTPCalls.t.sol | 18 ++++++++--------- test/mainnet-fork/DeployAndInit.t.sol | 8 ++++---- test/mainnet-fork/Ethena.t.sol | 2 +- test/mainnet-fork/ForkTestBase.t.sol | 14 ++++++------- test/mainnet-fork/PsmCalls.t.sol | 2 +- test/mainnet-fork/VaultCalls.t.sol | 2 +- test/unit/controllers/Admin.t.sol | 14 ++++++------- test/unit/controllers/Constructor.t.sol | 16 +++++++-------- test/unit/controllers/Freeze.t.sol | 16 +++++++-------- test/unit/deployments/Deploy.t.sol | 12 ++++++------ test/unit/proxy/Constructor.t.sol | 4 ++-- test/unit/proxy/DoCall.t.sol | 6 +++--- test/unit/proxy/ReceiveEth.t.sol | 4 ++-- test/unit/rate-limits/RateLimits.t.sol | 4 ++-- 31 files changed, 116 insertions(+), 125 deletions(-) diff --git a/deploy/ControllerDeploy.sol b/deploy/ControllerDeploy.sol index da4eaf2..4fc7bb8 100644 --- a/deploy/ControllerDeploy.sol +++ b/deploy/ControllerDeploy.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { ALMProxy } from "src/ALMProxy.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; -import { RateLimits } from "src/RateLimits.sol"; +import { ALMProxy } from "../src/ALMProxy.sol"; +import { ForeignController } from "../src/ForeignController.sol"; +import { MainnetController } from "../src/MainnetController.sol"; +import { RateLimits } from "../src/RateLimits.sol"; import { ControllerInstance } from "./ControllerInstance.sol"; diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol index ac9089d..f1f86a2 100644 --- a/deploy/ControllerInit.sol +++ b/deploy/ControllerInit.sol @@ -3,12 +3,12 @@ pragma solidity >=0.8.0; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import { ForeignController } from "../src/ForeignController.sol"; +import { MainnetController } from "../src/MainnetController.sol"; +import { RateLimitHelpers } from "../src/RateLimitHelpers.sol"; -import { IALMProxy } from "src/interfaces/IALMProxy.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { IALMProxy } from "../src/interfaces/IALMProxy.sol"; +import { IRateLimits } from "../src/interfaces/IRateLimits.sol"; import { ControllerInstance } from "./ControllerInstance.sol"; diff --git a/script/staging/DeployEthereum.s.sol b/script/staging/DeployEthereum.s.sol index a34bef2..7d19503 100644 --- a/script/staging/DeployEthereum.s.sol +++ b/script/staging/DeployEthereum.s.sol @@ -5,7 +5,7 @@ import { ScriptTools } from "dss-test/ScriptTools.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { Domain, StagingDeploymentBase } from "script/staging/StagingDeploymentBase.sol"; +import { Domain, StagingDeploymentBase } from "./StagingDeploymentBase.sol"; contract DeployEthereumStaging is StagingDeploymentBase { diff --git a/script/staging/StagingDeploymentBase.sol b/script/staging/StagingDeploymentBase.sol index 6f7ed57..7cbc19e 100644 --- a/script/staging/StagingDeploymentBase.sol +++ b/script/staging/StagingDeploymentBase.sol @@ -35,14 +35,14 @@ import { ForeignControllerDeploy, MainnetController, MainnetControllerDeploy -} from "deploy/ControllerDeploy.sol"; +} from "../../deploy/ControllerDeploy.sol"; import { ForeignControllerInit, MainnetControllerInit, MintRecipient, RateLimitData -} from "deploy/ControllerInit.sol"; +} from "../../deploy/ControllerInit.sol"; import { MockDaiUsds } from "./mocks/MockDaiUsds.sol"; import { MockJug } from "./mocks/MockJug.sol"; diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol index 9023c30..2d2728e 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/DeployEthereum.t.sol @@ -18,13 +18,13 @@ import { AllocatorBuffer } from "dss-allocator/src/AllocatorBuffer.sol"; import { AllocatorRegistry } from "dss-allocator/src/AllocatorRegistry.sol"; import { AllocatorRoles } from "dss-allocator/src/AllocatorRoles.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { IRateLimits } from "../../../src/interfaces/IRateLimits.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; -import { RateLimits } from "src/RateLimits.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import { ALMProxy } from "../../../src/ALMProxy.sol"; +import { ForeignController } from "../../../src/ForeignController.sol"; +import { MainnetController } from "../../../src/MainnetController.sol"; +import { RateLimits } from "../../../src/RateLimits.sol"; +import { RateLimitHelpers } from "../../../src/RateLimitHelpers.sol"; import { PSM3, IERC20 } from "spark-psm/src/PSM3.sol"; import { IRateProviderLike } from "spark-psm/src/interfaces/IRateProviderLike.sol"; diff --git a/src/ALMProxy.sol b/src/ALMProxy.sol index a348908..ee31930 100644 --- a/src/ALMProxy.sol +++ b/src/ALMProxy.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.21; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; import { Address } from "openzeppelin-contracts/contracts/utils/Address.sol"; -import { IALMProxy } from "src/interfaces/IALMProxy.sol"; +import { IALMProxy } from "./interfaces/IALMProxy.sol"; contract ALMProxy is IALMProxy, AccessControl { diff --git a/src/ForeignController.sol b/src/ForeignController.sol index e851bcf..722d88a 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -11,11 +11,11 @@ import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessCon import { IPSM3 } from "spark-psm/src/interfaces/IPSM3.sol"; -import { IALMProxy } from "src/interfaces/IALMProxy.sol"; -import { ICCTPLike } from "src/interfaces/CCTPInterfaces.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { IALMProxy } from "./interfaces/IALMProxy.sol"; +import { ICCTPLike } from "./interfaces/CCTPInterfaces.sol"; +import { IRateLimits } from "./interfaces/IRateLimits.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import { RateLimitHelpers } from "./RateLimitHelpers.sol"; interface IATokenWithPool is IAToken { function POOL() external view returns(address); diff --git a/src/MainnetController.sol b/src/MainnetController.sol index b4a25a1..5582ca5 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -11,11 +11,11 @@ import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessCon import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; -import { IALMProxy } from "src/interfaces/IALMProxy.sol"; -import { ICCTPLike } from "src/interfaces/CCTPInterfaces.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { IALMProxy } from "./interfaces/IALMProxy.sol"; +import { ICCTPLike } from "./interfaces/CCTPInterfaces.sol"; +import { IRateLimits } from "./interfaces/IRateLimits.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import { RateLimitHelpers } from "./RateLimitHelpers.sol"; interface IDaiUsdsLike { function dai() external view returns(address); diff --git a/src/RateLimits.sol b/src/RateLimits.sol index 7d6ba72..832f030 100644 --- a/src/RateLimits.sol +++ b/src/RateLimits.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { IRateLimits } from "./interfaces/IRateLimits.sol"; contract RateLimits is IRateLimits, AccessControl { diff --git a/test/base-fork/Aave.t.sol b/test/base-fork/Aave.t.sol index bb36f45..061d2bd 100644 --- a/test/base-fork/Aave.t.sol +++ b/test/base-fork/Aave.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/base-fork/ForkTestBase.t.sol"; - import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; + +import "./ForkTestBase.t.sol"; contract AaveV3BaseMarketTestBase is ForkTestBase { diff --git a/test/base-fork/DeployAndInit.t.sol b/test/base-fork/DeployAndInit.t.sol index fdaabe9..6ce4fb8 100644 --- a/test/base-fork/DeployAndInit.t.sol +++ b/test/base-fork/DeployAndInit.t.sol @@ -5,16 +5,16 @@ import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Moc import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import "test/base-fork/ForkTestBase.t.sol"; +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; +import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { ForeignControllerInit, RateLimitData, MintRecipient } from "../../deploy/ControllerInit.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; -import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; -import { ForeignControllerInit, RateLimitData, MintRecipient } from "deploy/ControllerInit.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import "./ForkTestBase.t.sol"; // Necessary to get error message assertions to work contract LibraryWrapper { diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 602a106..16c555a 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -14,27 +14,18 @@ import { IPSM3 } from "spark-psm/src/PSM3.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; +import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; -import { ForeignControllerInit, +import { + ForeignControllerInit, MintRecipient, RateLimitData -} from "deploy/ControllerInit.sol"; +} from "../../deploy/ControllerInit.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; - -import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; - -import { ForeignControllerInit, - MintRecipient, - RateLimitData -} from "deploy/ControllerInit.sol"; - -import { ALMProxy } from "src/ALMProxy.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { RateLimits } from "src/RateLimits.sol"; +import { ALMProxy } from "../../src/ALMProxy.sol"; +import { ForeignController } from "../../src/ForeignController.sol"; +import { RateLimits } from "../../src/RateLimits.sol"; contract ForkTestBase is Test { diff --git a/test/base-fork/Morpho.t.sol b/test/base-fork/Morpho.t.sol index 4799fc1..c345de7 100644 --- a/test/base-fork/Morpho.t.sol +++ b/test/base-fork/Morpho.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/base-fork/ForkTestBase.t.sol"; - import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; - import { IMetaMorpho, Id } from "metamorpho/interfaces/IMetaMorpho.sol"; import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; + +import "./ForkTestBase.t.sol"; + contract MorphoBaseTest is ForkTestBase { address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; diff --git a/test/base-fork/PsmCalls.t.sol b/test/base-fork/PsmCalls.t.sol index 3b9936b..f9146c6 100644 --- a/test/base-fork/PsmCalls.t.sol +++ b/test/base-fork/PsmCalls.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/base-fork/ForkTestBase.t.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import "./ForkTestBase.t.sol"; contract ForeignControllerPSMSuccessTestBase is ForkTestBase { diff --git a/test/mainnet-fork/4626Calls.t.sol b/test/mainnet-fork/4626Calls.t.sol index fb8e6b8..ba0bfc6 100644 --- a/test/mainnet-fork/4626Calls.t.sol +++ b/test/mainnet-fork/4626Calls.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; +import "./ForkTestBase.t.sol"; contract SUSDSTestBase is ForkTestBase { diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol index 43c591d..68577c3 100644 --- a/test/mainnet-fork/Aave.t.sol +++ b/test/mainnet-fork/Aave.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; - import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; +import "./ForkTestBase.t.sol"; + contract AaveV3MainMarketBaseTest is ForkTestBase { address constant ATOKEN_USDS = 0x32a6268f9Ba3642Dda7892aDd74f1D34469A4259; diff --git a/test/mainnet-fork/Attacks.t.sol b/test/mainnet-fork/Attacks.t.sol index e057cb0..8ed2838 100644 --- a/test/mainnet-fork/Attacks.t.sol +++ b/test/mainnet-fork/Attacks.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; +import "./ForkTestBase.t.sol"; contract CompromisedRelayerTests is ForkTestBase { diff --git a/test/mainnet-fork/CCTPCalls.t.sol b/test/mainnet-fork/CCTPCalls.t.sol index 0ded0ec..41b9401 100644 --- a/test/mainnet-fork/CCTPCalls.t.sol +++ b/test/mainnet-fork/CCTPCalls.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; - import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; @@ -16,18 +14,20 @@ import { MockRateProvider } from "spark-psm/test/mocks/MockRateProvider.sol"; import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; +import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; import { ForeignControllerInit, MintRecipient, RateLimitData -} from "deploy/ControllerInit.sol"; +} from "../../deploy/ControllerInit.sol"; + +import { ALMProxy } from "../../src/ALMProxy.sol"; +import { ForeignController } from "../../src/ForeignController.sol"; +import { RateLimits } from "../../src/RateLimits.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { RateLimits } from "src/RateLimits.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +import "./ForkTestBase.t.sol"; contract MainnetControllerTransferUSDCToCCTPFailureTests is ForkTestBase { diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/DeployAndInit.t.sol index 4f479ff..38ff7d1 100644 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ b/test/mainnet-fork/DeployAndInit.t.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; - -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; - import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; @@ -14,6 +10,10 @@ import { MintRecipient } from "../../deploy/ControllerInit.sol"; +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; + +import "./ForkTestBase.t.sol"; + // Necessary to get error message assertions to work contract LibraryWrapper { diff --git a/test/mainnet-fork/Ethena.t.sol b/test/mainnet-fork/Ethena.t.sol index 95b2d9e..69419aa 100644 --- a/test/mainnet-fork/Ethena.t.sol +++ b/test/mainnet-fork/Ethena.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; +import "./ForkTestBase.t.sol"; interface IEthenaMinterLike { function delegatedSigner(address signer, address owner) external view returns (uint8); diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 29ba067..13fe2d9 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -23,19 +23,19 @@ import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; -import { MainnetControllerDeploy } from "deploy/ControllerDeploy.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; +import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; import { MainnetControllerInit, MintRecipient, RateLimitData -} from "deploy/ControllerInit.sol"; +} from "../../deploy/ControllerInit.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; -import { RateLimits } from "src/RateLimits.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; -import { MainnetController } from "src/MainnetController.sol"; +import { ALMProxy } from "../../src/ALMProxy.sol"; +import { RateLimits } from "../../src/RateLimits.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; +import { MainnetController } from "../../src/MainnetController.sol"; interface IChainlogLike { function getAddress(bytes32) external view returns (address); diff --git a/test/mainnet-fork/PsmCalls.t.sol b/test/mainnet-fork/PsmCalls.t.sol index 6540927..7e1ac19 100644 --- a/test/mainnet-fork/PsmCalls.t.sol +++ b/test/mainnet-fork/PsmCalls.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import "test/mainnet-fork/ForkTestBase.t.sol"; +import "./ForkTestBase.t.sol"; interface IPSM is IPSMLike { function buf() external view returns (uint256); diff --git a/test/mainnet-fork/VaultCalls.t.sol b/test/mainnet-fork/VaultCalls.t.sol index 6b640de..2b8cfb4 100644 --- a/test/mainnet-fork/VaultCalls.t.sol +++ b/test/mainnet-fork/VaultCalls.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/mainnet-fork/ForkTestBase.t.sol"; +import "./ForkTestBase.t.sol"; contract MainnetControllerMintUSDSTests is ForkTestBase { diff --git a/test/unit/controllers/Admin.t.sol b/test/unit/controllers/Admin.t.sol index ea82cb6..ec19fb0 100644 --- a/test/unit/controllers/Admin.t.sol +++ b/test/unit/controllers/Admin.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import { ForeignController } from "../../../src/ForeignController.sol"; +import { MainnetController } from "../../../src/MainnetController.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; +import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; +import { MockPSM } from "../mocks/MockPSM.sol"; +import { MockSUsds } from "../mocks/MockSUsds.sol"; +import { MockVault } from "../mocks/MockVault.sol"; -import { MockDaiUsds } from "test/unit/mocks/MockDaiUsds.sol"; -import { MockPSM } from "test/unit/mocks/MockPSM.sol"; -import { MockSUsds } from "test/unit/mocks/MockSUsds.sol"; -import { MockVault } from "test/unit/mocks/MockVault.sol"; +import "../UnitTestBase.t.sol"; contract MainnetControllerAdminTests is UnitTestBase { diff --git a/test/unit/controllers/Constructor.t.sol b/test/unit/controllers/Constructor.t.sol index a82cd44..4d55441 100644 --- a/test/unit/controllers/Constructor.t.sol +++ b/test/unit/controllers/Constructor.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import { ForeignController } from "../../../src/ForeignController.sol"; +import { MainnetController } from "../../../src/MainnetController.sol"; -import { ForeignController } from "src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; +import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; +import { MockPSM } from "../mocks/MockPSM.sol"; +import { MockPSM3 } from "../mocks/MockPSM3.sol"; +import { MockSUsds } from "../mocks/MockSUsds.sol"; +import { MockVault } from "../mocks/MockVault.sol"; -import { MockDaiUsds } from "test/unit/mocks/MockDaiUsds.sol"; -import { MockPSM } from "test/unit/mocks/MockPSM.sol"; -import { MockPSM3 } from "test/unit/mocks/MockPSM3.sol"; -import { MockSUsds } from "test/unit/mocks/MockSUsds.sol"; -import { MockVault } from "test/unit/mocks/MockVault.sol"; +import "../UnitTestBase.t.sol"; contract MainnetControllerConstructorTests is UnitTestBase { diff --git a/test/unit/controllers/Freeze.t.sol b/test/unit/controllers/Freeze.t.sol index 9ba8f9b..d944e13 100644 --- a/test/unit/controllers/Freeze.t.sol +++ b/test/unit/controllers/Freeze.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import { MainnetController } from "../../../src/MainnetController.sol"; +import { ForeignController } from "../../../src/ForeignController.sol"; -import { MainnetController } from "src/MainnetController.sol"; -import { ForeignController } from "src/ForeignController.sol"; +import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; +import { MockPSM } from "../mocks/MockPSM.sol"; +import { MockPSM3 } from "../mocks/MockPSM3.sol"; +import { MockSUsds } from "../mocks/MockSUsds.sol"; +import { MockVault } from "../mocks/MockVault.sol"; -import { MockDaiUsds } from "test/unit/mocks/MockDaiUsds.sol"; -import { MockPSM } from "test/unit/mocks/MockPSM.sol"; -import { MockPSM3 } from "test/unit/mocks/MockPSM3.sol"; -import { MockSUsds } from "test/unit/mocks/MockSUsds.sol"; -import { MockVault } from "test/unit/mocks/MockVault.sol"; +import "../UnitTestBase.t.sol"; interface IBaseControllerLike { function active() external view returns (bool); diff --git a/test/unit/deployments/Deploy.t.sol b/test/unit/deployments/Deploy.t.sol index 709f359..b85cf0a 100644 --- a/test/unit/deployments/Deploy.t.sol +++ b/test/unit/deployments/Deploy.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import "../../../deploy/ControllerDeploy.sol"; // All imports needed so not importing explicitly -import "deploy/ControllerDeploy.sol"; // All imports needed so not importing explicitly +import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; +import { MockPSM } from "../mocks/MockPSM.sol"; +import { MockSUsds } from "../mocks/MockSUsds.sol"; +import { MockVault } from "../mocks/MockVault.sol"; -import { MockDaiUsds } from "test/unit/mocks/MockDaiUsds.sol"; -import { MockPSM } from "test/unit/mocks/MockPSM.sol"; -import { MockSUsds } from "test/unit/mocks/MockSUsds.sol"; -import { MockVault } from "test/unit/mocks/MockVault.sol"; +import "../UnitTestBase.t.sol"; contract ForeignControllerDeployTests is UnitTestBase { diff --git a/test/unit/proxy/Constructor.t.sol b/test/unit/proxy/Constructor.t.sol index 51e2297..f066c57 100644 --- a/test/unit/proxy/Constructor.t.sol +++ b/test/unit/proxy/Constructor.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import { ALMProxy } from "../../../src/ALMProxy.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; +import "../UnitTestBase.t.sol"; contract ALMProxyConstructorTests is UnitTestBase { diff --git a/test/unit/proxy/DoCall.t.sol b/test/unit/proxy/DoCall.t.sol index 6144a9b..c3fbe07 100644 --- a/test/unit/proxy/DoCall.t.sol +++ b/test/unit/proxy/DoCall.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import { ALMProxy } from "src/ALMProxy.sol"; +import { ALMProxy } from "../../../src/ALMProxy.sol"; -import "test/unit/UnitTestBase.t.sol"; +import { MockTarget } from "../mocks/MockTarget.sol"; -import { MockTarget } from "test/unit/mocks/MockTarget.sol"; +import "../UnitTestBase.t.sol"; contract ALMProxyCallTestBase is UnitTestBase { diff --git a/test/unit/proxy/ReceiveEth.t.sol b/test/unit/proxy/ReceiveEth.t.sol index fd3f122..de4e0c3 100644 --- a/test/unit/proxy/ReceiveEth.t.sol +++ b/test/unit/proxy/ReceiveEth.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import { ALMProxy } from "../../../src/ALMProxy.sol"; -import { ALMProxy } from "src/ALMProxy.sol"; +import "../UnitTestBase.t.sol"; contract ALMProxyReceiveEthTests is UnitTestBase { diff --git a/test/unit/rate-limits/RateLimits.t.sol b/test/unit/rate-limits/RateLimits.t.sol index 9068b05..3beb617 100644 --- a/test/unit/rate-limits/RateLimits.t.sol +++ b/test/unit/rate-limits/RateLimits.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import "test/unit/UnitTestBase.t.sol"; +import "../UnitTestBase.t.sol"; -import { RateLimits, IRateLimits } from "src/RateLimits.sol"; +import { RateLimits, IRateLimits } from "../../../src/RateLimits.sol"; contract RateLimitsTestBase is UnitTestBase { From 698cac7f0258f8d4aa77ef57d2d9d31db15c0508 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Thu, 5 Dec 2024 14:57:19 -0500 Subject: [PATCH 07/22] feat: Add rate limited withdrawals (SC-858) (#65) * feat: all tests passing * feat: add testing for all functions * fix: cleanup --- src/ForeignController.sol | 23 ++++++- src/MainnetController.sol | 25 ++++++- test/base-fork/Aave.t.sol | 65 +++++++++++++++-- test/base-fork/Morpho.t.sol | 111 ++++++++++++++++++++++++++++++ test/mainnet-fork/4626Calls.t.sol | 98 ++++++++++++++++++++++++++ test/mainnet-fork/Aave.t.sol | 71 +++++++++++++++++++ 6 files changed, 384 insertions(+), 9 deletions(-) diff --git a/src/ForeignController.sol b/src/ForeignController.sol index 722d88a..922bd3d 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -49,7 +49,9 @@ contract ForeignController is AccessControl { bytes32 public constant RELAYER = keccak256("RELAYER"); bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); + bytes32 public constant LIMIT_4626_WITHDRAW = keccak256("LIMIT_4626_WITHDRAW"); bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); + bytes32 public constant LIMIT_AAVE_WITHDRAW = keccak256("LIMIT_AAVE_WITHDRAW"); bytes32 public constant LIMIT_PSM_DEPOSIT = keccak256("LIMIT_PSM_DEPOSIT"); bytes32 public constant LIMIT_PSM_WITHDRAW = keccak256("LIMIT_PSM_WITHDRAW"); bytes32 public constant LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP"); @@ -258,7 +260,14 @@ contract ForeignController is AccessControl { } function withdrawERC4626(address token, uint256 amount) - external onlyRole(RELAYER) isActive returns (uint256 shares) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + amount + ) + returns (uint256 shares) { // Withdraw asset from a token, decode resulting shares. // Assumes proxy has adequate token shares. @@ -271,6 +280,7 @@ contract ForeignController is AccessControl { ); } + // NOTE: !!! Rate limited at end of function !!! function redeemERC4626(address token, uint256 shares) external onlyRole(RELAYER) isActive returns (uint256 assets) { @@ -283,6 +293,11 @@ contract ForeignController is AccessControl { ), (uint256) ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + assets + ); } /**********************************************************************************************/ @@ -314,6 +329,7 @@ contract ForeignController is AccessControl { ); } + // NOTE: !!! Rate limited at end of function !!! function withdrawAave(address aToken, uint256 amount) external onlyRole(RELAYER) isActive returns (uint256 amountWithdrawn) { @@ -331,6 +347,11 @@ contract ForeignController is AccessControl { ), (uint256) ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_WITHDRAW, aToken), + amountWithdrawn + ); } /**********************************************************************************************/ diff --git a/src/MainnetController.sol b/src/MainnetController.sol index 5582ca5..1b0d03e 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -84,6 +84,9 @@ contract MainnetController is AccessControl { bytes32 public constant RELAYER = keccak256("RELAYER"); bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT"); + bytes32 public constant LIMIT_4626_WITHDRAW = keccak256("LIMIT_4626_WITHDRAW"); + bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); + bytes32 public constant LIMIT_AAVE_WITHDRAW = keccak256("LIMIT_AAVE_WITHDRAW"); bytes32 public constant LIMIT_SUSDE_COOLDOWN = keccak256("LIMIT_SUSDE_COOLDOWN"); bytes32 public constant LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP"); bytes32 public constant LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN"); @@ -91,7 +94,6 @@ contract MainnetController is AccessControl { bytes32 public constant LIMIT_USDE_MINT = keccak256("LIMIT_USDE_MINT"); bytes32 public constant LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT"); bytes32 public constant LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC"); - bytes32 public constant LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT"); address public immutable buffer; @@ -268,7 +270,14 @@ contract MainnetController is AccessControl { } function withdrawERC4626(address token, uint256 amount) - external onlyRole(RELAYER) isActive returns (uint256 shares) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + amount + ) + returns (uint256 shares) { // Withdraw asset from a token, decode resulting shares. // Assumes proxy has adequate token shares. @@ -281,6 +290,7 @@ contract MainnetController is AccessControl { ); } + // NOTE: !!! Rate limited at end of function !!! function redeemERC4626(address token, uint256 shares) external onlyRole(RELAYER) isActive returns (uint256 assets) { @@ -293,6 +303,11 @@ contract MainnetController is AccessControl { ), (uint256) ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + assets + ); } /**********************************************************************************************/ @@ -324,6 +339,7 @@ contract MainnetController is AccessControl { ); } + // NOTE: !!! Rate limited at end of function !!! function withdrawAave(address aToken, uint256 amount) external onlyRole(RELAYER) isActive returns (uint256 amountWithdrawn) { @@ -341,6 +357,11 @@ contract MainnetController is AccessControl { ), (uint256) ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_WITHDRAW, aToken), + amountWithdrawn + ); } /**********************************************************************************************/ diff --git a/test/base-fork/Aave.t.sol b/test/base-fork/Aave.t.sol index 061d2bd..d13d1e4 100644 --- a/test/base-fork/Aave.t.sol +++ b/test/base-fork/Aave.t.sol @@ -30,6 +30,14 @@ contract AaveV3BaseMarketTestBase is ForkTestBase { 1_000_000e6, uint256(1_000_000e6) / 1 days ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ), + 1_000_000e6, + uint256(5_000_000e6) / 1 days + ); vm.stopPrank(); @@ -58,6 +66,12 @@ contract AaveV3BaseMarketDepositFailureTests is AaveV3BaseMarketTestBase { foreignController.depositAave(ATOKEN_USDC, 1_000_000e18); } + function test_depositAave_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + foreignController.depositAave(makeAddr("fake-token"), 1e18); + } + function test_depositAave_usdcRateLimitedBoundary() external { deal(Base.USDC, address(almProxy), 1_000_000e6 + 1); @@ -113,24 +127,63 @@ contract AaveV3BaseMarketWithdrawFailureTests is AaveV3BaseMarketTestBase { foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e18); } + function test_withdrawAave_zeroMaxAmount() external { + // Longer setup because rate limit revert is at the end of the function + vm.startPrank(Base.SPARK_EXECUTOR); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ), + 0, + 0 + ); + vm.stopPrank(); + + deal(Base.USDC, address(almProxy), 1_000_000e6); + + vm.startPrank(relayer); + + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + + vm.expectRevert("RateLimits/zero-maxAmount"); + foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6); + } + + function test_withdrawAave_usdcRateLimitedBoundary() external { + deal(Base.USDC, address(almProxy), 2_000_000e6); + + // Warp to get past rate limit + vm.startPrank(relayer); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + skip(1 days); + foreignController.depositAave(ATOKEN_USDC, 100_000e6); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6 + 1); + + foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6); + } + } contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { function test_withdrawAave_usdc() public { - deal(Base.USDC, address(almProxy), 1_000_000e6); + // NOTE: Using lower amount to not hit rate limit + deal(Base.USDC, address(almProxy), 500_000e6); vm.prank(relayer); - foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + foreignController.depositAave(ATOKEN_USDC, 500_000e6); skip(1 days); uint256 fullBalance = ausdc.balanceOf(address(almProxy)); - assertGe(fullBalance, 1_000_000e6); + assertGe(fullBalance, 500_000e6); assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); assertEq(usdcBase.balanceOf(address(almProxy)), 0); - assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6); // Partial withdraw vm.prank(relayer); @@ -138,7 +191,7 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6); assertEq(usdcBase.balanceOf(address(almProxy)), 400_000e6); - assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 600_000e6); // 1m - 400k + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 100_000e6); // 500k - 400k // Withdraw all vm.prank(relayer); @@ -146,7 +199,7 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { assertEq(ausdc.balanceOf(address(almProxy)), 0); assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance); - assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6 - fullBalance); // Interest accrued was withdrawn, reducing cash balance assertLe(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance); diff --git a/test/base-fork/Morpho.t.sol b/test/base-fork/Morpho.t.sol index c345de7..ddee575 100644 --- a/test/base-fork/Morpho.t.sol +++ b/test/base-fork/Morpho.t.sol @@ -62,6 +62,7 @@ contract MorphoBaseTest is ForkTestBase { Id[] memory supplyQueueUSDS = new Id[](1); supplyQueueUSDS[0] = MarketParamsLib.id(usdsParams); IMetaMorpho(MORPHO_VAULT_USDS).setSupplyQueue(supplyQueueUSDS); + Id[] memory supplyQueueUSDC = new Id[](1); supplyQueueUSDC[0] = MarketParamsLib.id(usdcParams); IMetaMorpho(MORPHO_VAULT_USDC).setSupplyQueue(supplyQueueUSDC); @@ -82,6 +83,22 @@ contract MorphoBaseTest is ForkTestBase { 25_000_000e6, uint256(5_000_000e6) / 1 days ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_4626_WITHDRAW(), + MORPHO_VAULT_USDS + ), + 10_000_000e18, + uint256(5_000_000e18) / 1 days + ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_4626_WITHDRAW(), + MORPHO_VAULT_USDC + ), + 10_000_000e6, + uint256(5_000_000e6) / 1 days + ); vm.stopPrank(); } @@ -93,6 +110,7 @@ contract MorphoBaseTest is ForkTestBase { } // NOTE: Only testing USDS for non-rate limit failures as it doesn't matter which asset is used +// TODO: Refactor tests here to be generic 4626, testing morpho as a subset, rename file and functions contract MorphoDepositFailureTests is MorphoBaseTest { @@ -114,6 +132,12 @@ contract MorphoDepositFailureTests is MorphoBaseTest { foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); } + function test_morpho_deposit_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + foreignController.depositERC4626(makeAddr("fake-token"), 1e18); + } + function test_morpho_usds_deposit_rateLimitedBoundary() external { deal(Base.USDS, address(almProxy), 25_000_000e18 + 1); @@ -190,6 +214,34 @@ contract MorphoWithdrawFailureTests is MorphoBaseTest { foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 1_000_000e18); } + function test_morpho_withdraw_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + foreignController.withdrawERC4626(makeAddr("fake-token"), 1_000_000e18); + } + + function test_morpho_usds_withdraw_rateLimitBoundary() external { + deal(Base.USDS, address(almProxy), 10_000_000e18 + 1); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 10_000_000e18 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 10_000_000e18 + 1); + + foreignController.withdrawERC4626(MORPHO_VAULT_USDS, 10_000_000e18); + } + + function test_morpho_usdc_withdraw_rateLimitBoundary() external { + deal(Base.USDC, address(almProxy), 10_000_000e18 + 1); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDC, 10_000_000e6 + 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + foreignController.withdrawERC4626(MORPHO_VAULT_USDC, 10_000_000e6 + 1); + + foreignController.withdrawERC4626(MORPHO_VAULT_USDC, 10_000_000e6); + } + } contract MorphoWithdrawSuccessTests is MorphoBaseTest { @@ -246,6 +298,65 @@ contract MorphoRedeemFailureTests is MorphoBaseTest { foreignController.redeemERC4626(MORPHO_VAULT_USDS, 1_000_000e18); } + function test_morpho_redeem_zeroMaxAmount() external { + // Longer setup because rate limit revert is at the end of the function + vm.startPrank(Base.SPARK_EXECUTOR); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_4626_WITHDRAW(), + MORPHO_VAULT_USDS + ), + 0, + 0 + ); + vm.stopPrank(); + + deal(Base.USDS, address(almProxy), 1_000_000e18); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + + vm.expectRevert("RateLimits/zero-maxAmount"); + foreignController.redeemERC4626(MORPHO_VAULT_USDS, 1_000_000e18); + } + + function test_morpho_usds_redeem_rateLimitBoundary() external { + deal(Base.USDS, address(almProxy), 20_000_000e18); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDS, 20_000_000e18); + + IERC4626 vault = IERC4626(MORPHO_VAULT_USDS); + + uint256 overBoundaryShares = vault.convertToShares(10_000_000e18 + 1); + uint256 atBoundaryShares = vault.convertToShares(10_000_000e18); + + assertEq(vault.previewRedeem(overBoundaryShares), 10_000_000e18 + 1); + assertEq(vault.previewRedeem(atBoundaryShares), 10_000_000e18); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + foreignController.redeemERC4626(MORPHO_VAULT_USDS, overBoundaryShares); + + foreignController.redeemERC4626(MORPHO_VAULT_USDS, atBoundaryShares); + } + + function test_morpho_usdc_redeem_rateLimitBoundary() external { + deal(Base.USDC, address(almProxy), 20_000_000e18); + vm.startPrank(relayer); + foreignController.depositERC4626(MORPHO_VAULT_USDC, 20_000_000e6); + + IERC4626 vault = IERC4626(MORPHO_VAULT_USDC); + + uint256 overBoundaryShares = vault.convertToShares(10_000_000e6 + 1); + uint256 atBoundaryShares = vault.convertToShares(10_000_000e6); + + assertEq(vault.previewRedeem(overBoundaryShares), 10_000_000e6 + 1); + assertEq(vault.previewRedeem(atBoundaryShares), 10_000_000e6); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + foreignController.redeemERC4626(MORPHO_VAULT_USDC, overBoundaryShares); + + foreignController.redeemERC4626(MORPHO_VAULT_USDC, atBoundaryShares); + } + } contract MorphoRedeemSuccessTests is MorphoBaseTest { diff --git a/test/mainnet-fork/4626Calls.t.sol b/test/mainnet-fork/4626Calls.t.sol index ba0bfc6..dc55a57 100644 --- a/test/mainnet-fork/4626Calls.t.sol +++ b/test/mainnet-fork/4626Calls.t.sol @@ -16,6 +16,17 @@ contract SUSDSTestBase is ForkTestBase { function setUp() override public { super.setUp(); + vm.startPrank(Ethereum.SPARK_PROXY); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_4626_WITHDRAW(), + Ethereum.SUSDS + ), + 5_000_000e18, + uint256(5_000_000e18) / 1 days + ); + vm.stopPrank(); + SUSDS_CONVERTED_ASSETS = susds.convertToAssets(1e18); SUSDS_CONVERTED_SHARES = susds.convertToShares(1e18); @@ -55,6 +66,26 @@ contract MainnetControllerDepositERC4626FailureTests is SUSDSTestBase { mainnetController.depositERC4626(address(susds), 1e18); } + function test_depositERC4626_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + mainnetController.depositERC4626(makeAddr("fake-token"), 1e18); + } + + function test_depositERC4626_rateLimitBoundary() external { + vm.startPrank(relayer); + mainnetController.mintUSDS(5_000_000e18); + + // Have to warp to get back above rate limit + skip(1 minutes); + mainnetController.mintUSDS(1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.depositERC4626(address(susds), 5_000_000e18 + 1); + + mainnetController.depositERC4626(address(susds), 5_000_000e18); + } + } contract MainnetControllerDepositERC4626Tests is SUSDSTestBase { @@ -113,6 +144,28 @@ contract MainnetControllerWithdrawERC4626FailureTests is SUSDSTestBase { mainnetController.withdrawERC4626(address(susds), 1e18); } + function test_withdrawERC4626_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + mainnetController.withdrawERC4626(makeAddr("fake-token"), 1e18); + } + + function test_withdrawERC4626_rateLimitBoundary() external { + vm.startPrank(relayer); + mainnetController.mintUSDS(5_000_000e18); + mainnetController.depositERC4626(address(susds), 5_000_000e18); + + // Have to warp to get back above rate limit + skip(1 minutes); + mainnetController.mintUSDS(1); + mainnetController.depositERC4626(address(susds), 1); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.withdrawERC4626(address(susds), 5_000_000e18 + 1); + + mainnetController.withdrawERC4626(address(susds), 5_000_000e18); + } + } contract MainnetControllerWithdrawERC4626Tests is SUSDSTestBase { @@ -174,6 +227,51 @@ contract MainnetControllerRedeemERC4626FailureTests is SUSDSTestBase { mainnetController.redeemERC4626(address(susds), 1e18); } + function test_redeemERC4626_zeroMaxAmount() external { + // Longer setup because rate limit revert is at the end of the function + vm.startPrank(Ethereum.SPARK_PROXY); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_4626_WITHDRAW(), + Ethereum.SUSDS + ), + 0, + 0 + ); + vm.stopPrank(); + + vm.startPrank(relayer); + mainnetController.mintUSDS(100e18); + mainnetController.depositERC4626(address(susds), 100e18); + vm.stopPrank(); + + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + mainnetController.redeemERC4626(address(susds), 1e18); + } + + function test_redeemERC4626_rateLimitBoundary() external { + vm.startPrank(relayer); + mainnetController.mintUSDS(5_000_000e18); + mainnetController.depositERC4626(address(susds), 5_000_000e18); + + // Have to warp to get back above rate limit + skip(10 minutes); + mainnetController.mintUSDS(100e18); + mainnetController.depositERC4626(address(susds), 100e18); + + uint256 overBoundaryShares = susds.convertToShares(5_000_000e18 + 2); + uint256 atBoundaryShares = susds.convertToShares(5_000_000e18 + 1); // Still rounds down + + assertEq(susds.previewRedeem(overBoundaryShares), 5_000_000e18 + 1); + assertEq(susds.previewRedeem(atBoundaryShares), 5_000_000e18); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.redeemERC4626(address(susds), overBoundaryShares); + + mainnetController.redeemERC4626(address(susds), atBoundaryShares); + } + } contract MainnetControllerRedeemERC4626Tests is SUSDSTestBase { diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol index 68577c3..e140be7 100644 --- a/test/mainnet-fork/Aave.t.sol +++ b/test/mainnet-fork/Aave.t.sol @@ -38,6 +38,22 @@ contract AaveV3MainMarketBaseTest is ForkTestBase { 25_000_000e6, uint256(5_000_000e6) / 1 days ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDS + ), + 10_000_000e18, + uint256(5_000_000e18) / 1 days + ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ), + 10_000_000e6, + uint256(5_000_000e6) / 1 days + ); vm.stopPrank(); @@ -69,6 +85,12 @@ contract AaveV3MainMarketDepositFailureTests is AaveV3MainMarketBaseTest { mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); } + function test_depositAave_zeroMaxAmount() external { + vm.prank(relayer); + vm.expectRevert("RateLimits/zero-maxAmount"); + mainnetController.depositAave(makeAddr("fake-token"), 1e18); + } + function test_depositAave_usdsRateLimitedBoundary() external { deal(Ethereum.USDS, address(almProxy), 25_000_000e18 + 1); @@ -153,6 +175,55 @@ contract AaveV3MainMarketWithdrawFailureTests is AaveV3MainMarketBaseTest { mainnetController.withdrawAave(ATOKEN_USDS, 1_000_000e18); } + function test_withdrawAave_zeroMaxAmount() external { + // Longer setup because rate limit revert is at the end of the function + vm.startPrank(Ethereum.SPARK_PROXY); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ), + 0, + 0 + ); + vm.stopPrank(); + + deal(Ethereum.USDC, address(almProxy), 1_000_000e6); + + vm.startPrank(relayer); + + mainnetController.depositAave(ATOKEN_USDC, 1_000_000e6); + + vm.expectRevert("RateLimits/zero-maxAmount"); + mainnetController.withdrawAave(ATOKEN_USDC, 1_000_000e6); + } + + function test_withdrawAave_usdsRateLimitedBoundary() external { + deal(Ethereum.USDS, address(almProxy), 15_000_000e18); + + vm.startPrank(relayer); + + mainnetController.depositAave(ATOKEN_USDS, 15_000_000e18); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.withdrawAave(ATOKEN_USDS, 10_000_000e18 + 1); + + mainnetController.withdrawAave(ATOKEN_USDS, 10_000_000e18); + } + + function test_withdrawAave_usdcRateLimitedBoundary() external { + deal(Ethereum.USDC, address(almProxy), 15_000_000e6); + + vm.startPrank(relayer); + + mainnetController.depositAave(ATOKEN_USDC, 15_000_000e6); + + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.withdrawAave(ATOKEN_USDC, 10_000_000e6 + 1); + + mainnetController.withdrawAave(ATOKEN_USDC, 10_000_000e6); + } + } contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { From ad4391c37aa262d3c578a757700c1b6e86a96060 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Fri, 6 Dec 2024 12:30:37 -0500 Subject: [PATCH 08/22] feat: Add JSONs to deployments (SC-859) (#63) * feat: add jsons to deployments * feat: update prod addresses * fix: update scripts and json * feat: update json names * feat: update addresses in json * fix: update addresses --- Makefile | 14 +- script/Deploy.s.sol | 144 ++++++++++++++---- script/input/1/base-production.json | 10 ++ script/input/1/base-staging.json | 10 ++ script/input/1/base.json | 5 - script/input/1/mainnet-production.json | 15 ++ script/input/1/mainnet-staging.json | 15 ++ script/input/1/mainnet.json | 10 -- .../1/base-production-release-20241023.json | 5 + ...son => base-staging-release-20241022.json} | 0 .../mainnet-production-release-20241023.json | 5 + ... => mainnet-staging-release-20241022.json} | 0 12 files changed, 185 insertions(+), 48 deletions(-) create mode 100644 script/input/1/base-production.json create mode 100644 script/input/1/base-staging.json delete mode 100644 script/input/1/base.json create mode 100644 script/input/1/mainnet-production.json create mode 100644 script/input/1/mainnet-staging.json delete mode 100644 script/input/1/mainnet.json create mode 100644 script/output/1/base-production-release-20241023.json rename script/output/1/{base-release-20241022.json => base-staging-release-20241022.json} (100%) create mode 100644 script/output/1/mainnet-production-release-20241023.json rename script/output/1/{mainnet-release-20241022.json => mainnet-staging-release-20241022.json} (100%) diff --git a/Makefile b/Makefile index 74735db..e8703e0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ # Staging Deployments -deploy-sepolia-staging :; forge script script/staging/DeploySepolia.s.sol:DeploySepoliaStaging --sender ${ETH_FROM} --broadcast --slow -deploy-ethereum-staging :; forge script script/staging/DeployEthereum.s.sol:DeployEthereumStaging --sender ${ETH_FROM} --broadcast --slow --verify +deploy-mainnet-staging-full :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetFull --sender ${ETH_FROM} --broadcast --verify +deploy-mainnet-staging-controller :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetController --sender ${ETH_FROM} --broadcast --verify + +deploy-base-staging-full :; CHAIN=base ENV=staging forge script script/Deploy.s.sol:DeployForeignFull --sender ${ETH_FROM} --broadcast --verify +deploy-base-staging-controller :; CHAIN=base ENV=staging forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify # Production Deployments -deploy-base :; forge script script/Deploy.s.sol:DeployBaseFull --sender ${ETH_FROM} --broadcast --verify -deploy-ethereum :; forge script script/Deploy.s.sol:DeployMainnetFull --sender ${ETH_FROM} --broadcast --verify +deploy-mainnet-production-full :; ENV=production forge script script/Deploy.s.sol:DeployMainnetFull --sender ${ETH_FROM} --broadcast --verify +deploy-mainnet-production-controller :; ENV=production forge script script/Deploy.s.sol:DeployMainnetController --sender ${ETH_FROM} --broadcast --verify + +deploy-base-production-full :; CHAIN=base ENV=production forge script script/Deploy.s.sol:DeployForeignFull --sender ${ETH_FROM} --broadcast --verify +deploy-base-production-controller :; CHAIN=base ENV=production forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 481e55a..78939ea 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.0; -import "forge-std/Script.sol"; +import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { Base } from "spark-address-registry/src/Base.sol"; -import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; +import "forge-std/Script.sol"; import { ControllerInstance } from "../deploy/ControllerInstance.sol"; @@ -12,20 +11,30 @@ import { ForeignControllerDeploy, MainnetControllerDeploy } from "../deploy/Cont contract DeployMainnetFull is Script { + using stdJson for string; + using ScriptTools for string; + function run() external { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + vm.createSelectFork(getChain("mainnet").rpcUrl); console.log("Deploying Mainnet ALMProxy, Controller and RateLimits..."); + string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV"))); + vm.startBroadcast(); + string memory config = ScriptTools.loadConfig(fileSlug); + ControllerInstance memory instance = MainnetControllerDeploy.deployFull({ - admin : Ethereum.SPARK_PROXY, - vault : Ethereum.ALLOCATOR_VAULT, - psm : Ethereum.PSM, - daiUsds : Ethereum.DAI_USDS, - cctp : Ethereum.CCTP_TOKEN_MESSENGER, - susds : Ethereum.SUSDS + admin : config.readAddress(".admin"), + vault : config.readAddress(".allocatorVault"), + psm : config.readAddress(".psm"), + daiUsds : config.readAddress(".daiUsds"), + cctp : config.readAddress(".cctpTokenMessenger"), + susds : config.readAddress(".susds") }); vm.stopBroadcast(); @@ -33,48 +42,125 @@ contract DeployMainnetFull is Script { console.log("ALMProxy deployed at", instance.almProxy); console.log("Controller deployed at", instance.controller); console.log("RateLimits deployed at", instance.rateLimits); + + ScriptTools.exportContract(fileSlug, "almProxy", instance.almProxy); + ScriptTools.exportContract(fileSlug, "controller", instance.controller); + ScriptTools.exportContract(fileSlug, "rateLimits", instance.rateLimits); + } + +} + +contract DeployMainnetController is Script { + + using stdJson for string; + using ScriptTools for string; + + function run() external { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + vm.createSelectFork(getChain("mainnet").rpcUrl); + + console.log("Deploying Mainnet Controller..."); + + string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV"))); + + vm.startBroadcast(); + + string memory config = ScriptTools.loadConfig(fileSlug); + + address controller = MainnetControllerDeploy.deployController({ + admin : config.readAddress(".admin"), + almProxy : config.readAddress(".almProxy"), + rateLimits : config.readAddress(".rateLimits"), + vault : config.readAddress(".allocatorVault"), + psm : config.readAddress(".psm"), + daiUsds : config.readAddress(".daiUsds"), + cctp : config.readAddress(".cctpTokenMessenger"), + susds : config.readAddress(".susds") + }); + + vm.stopBroadcast(); + + console.log("Controller deployed at", controller); + + ScriptTools.exportContract(fileSlug, "controller", controller); } } contract DeployForeignFull is Script { - function deploy( - string memory remoteRpcUrl, - address admin, - address psm, - address usdc, - address cctp - ) - internal - { - vm.createSelectFork(remoteRpcUrl); + using stdJson for string; + using ScriptTools for string; - console.log("Deploying Mainnet ALMProxy, Controller and RateLimits..."); + function run() external { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + string memory chainName = vm.envString("CHAIN"); + string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV"))); + string memory config = ScriptTools.loadConfig(fileSlug); + + vm.createSelectFork(getChain(chainName).rpcUrl); + + console.log(string(abi.encodePacked("Deploying ", chainName, " ALMProxy, Controller and RateLimits..."))); vm.startBroadcast(); - ControllerInstance memory instance - = ForeignControllerDeploy.deployFull(admin, psm, usdc, cctp); + ControllerInstance memory instance = ForeignControllerDeploy.deployFull({ + admin : config.readAddress(".admin"), + psm : config.readAddress(".psm"), + usdc : config.readAddress(".usdc"), + cctp : config.readAddress(".cctpTokenMessenger") + }); vm.stopBroadcast(); console.log("ALMProxy deployed at", instance.almProxy); console.log("Controller deployed at", instance.controller); console.log("RateLimits deployed at", instance.rateLimits); + + ScriptTools.exportContract(fileSlug, "almProxy", instance.almProxy); + ScriptTools.exportContract(fileSlug, "controller", instance.controller); + ScriptTools.exportContract(fileSlug, "rateLimits", instance.rateLimits); } + } -contract DeployBaseFull is DeployForeignFull { +contract DeployForeignController is Script { + + using stdJson for string; + using ScriptTools for string; function run() external { - deploy({ - remoteRpcUrl : getChain("base").rpcUrl, - admin : Base.SPARK_EXECUTOR, - psm : Base.PSM3, - usdc : Base.USDC, - cctp : Base.CCTP_TOKEN_MESSENGER + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + string memory chainName = vm.envString("CHAIN"); + string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV"))); + string memory config = ScriptTools.loadConfig(fileSlug); + + vm.createSelectFork(getChain(chainName).rpcUrl); + + console.log(string(abi.encodePacked("Deploying ", chainName, " Controller..."))); + + vm.startBroadcast(); + + address controller = ForeignControllerDeploy.deployController({ + admin : config.readAddress(".admin"), + almProxy : config.readAddress(".almProxy"), + rateLimits : config.readAddress(".rateLimits"), + psm : config.readAddress(".psm"), + usdc : config.readAddress(".usdc"), + cctp : config.readAddress(".cctpTokenMessenger") }); + + vm.stopBroadcast(); + + console.log("Controller deployed at", controller); + + ScriptTools.exportContract(fileSlug, "controller", controller); } } diff --git a/script/input/1/base-production.json b/script/input/1/base-production.json new file mode 100644 index 0000000..fdbd491 --- /dev/null +++ b/script/input/1/base-production.json @@ -0,0 +1,10 @@ +{ + "admin": "0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579", + "almProxy": "0x2917956eFF0B5eaF030abDB4EF4296DF775009cA", + "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", + "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74", + "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB", + "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431", + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" +} diff --git a/script/input/1/base-staging.json b/script/input/1/base-staging.json new file mode 100644 index 0000000..164c213 --- /dev/null +++ b/script/input/1/base-staging.json @@ -0,0 +1,10 @@ +{ + "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", + "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE", + "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", + "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b", + "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" +} diff --git a/script/input/1/base.json b/script/input/1/base.json deleted file mode 100644 index d5f4ab9..0000000 --- a/script/input/1/base.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", - "safe": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", - "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" -} diff --git a/script/input/1/mainnet-production.json b/script/input/1/mainnet-production.json new file mode 100644 index 0000000..ae39f0d --- /dev/null +++ b/script/input/1/mainnet-production.json @@ -0,0 +1,15 @@ +{ + "admin": "0x3300f198988e4C9C63F75dF86De36421f06af8c4", + "allocatorVault": "0x691a6c29e9e96dd897718305427Ad5D534db16BA", + "almProxy": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042", + "rateLimits": "0x7A5FD5cf045e010e62147F065cEAe59e5344b188", + "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB", + "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431", + "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F" +} diff --git a/script/input/1/mainnet-staging.json b/script/input/1/mainnet-staging.json new file mode 100644 index 0000000..7c41094 --- /dev/null +++ b/script/input/1/mainnet-staging.json @@ -0,0 +1,15 @@ +{ + "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", + "allocatorVault": "0x8E20650287635aE6e20ce38EcD3E795919D52354", + "almProxy": "0xC29D06ce81137E6B3C3DC090713636d81600a347", + "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8", + "rateLimits": "0x9A140AC56CC28A00B2c036F454F202c2459ca84c", + "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F" +} diff --git a/script/input/1/mainnet.json b/script/input/1/mainnet.json deleted file mode 100644 index b903daf..0000000 --- a/script/input/1/mainnet.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", - "safe": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", - "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", - "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", - "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", - "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", - "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042" -} diff --git a/script/output/1/base-production-release-20241023.json b/script/output/1/base-production-release-20241023.json new file mode 100644 index 0000000..9196ca1 --- /dev/null +++ b/script/output/1/base-production-release-20241023.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0x2917956eFF0B5eaF030abDB4EF4296DF775009cA", + "controller": "0xc07f705D0C0e9F8C79C5fbb748aC1246BBCC37Ba", + "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74" +} diff --git a/script/output/1/base-release-20241022.json b/script/output/1/base-staging-release-20241022.json similarity index 100% rename from script/output/1/base-release-20241022.json rename to script/output/1/base-staging-release-20241022.json diff --git a/script/output/1/mainnet-production-release-20241023.json b/script/output/1/mainnet-production-release-20241023.json new file mode 100644 index 0000000..2d2c3b6 --- /dev/null +++ b/script/output/1/mainnet-production-release-20241023.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "controller": "0xb960F71ca3f1f57799F6e14501607f64f9B36F11", + "rateLimits": "0x7A5FD5cf045e010e62147F065cEAe59e5344b188" +} diff --git a/script/output/1/mainnet-release-20241022.json b/script/output/1/mainnet-staging-release-20241022.json similarity index 100% rename from script/output/1/mainnet-release-20241022.json rename to script/output/1/mainnet-staging-release-20241022.json From 153310a27f5aecd3730acd2cb662abe6c6756e05 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 11 Dec 2024 11:26:08 -0500 Subject: [PATCH 09/22] feat: Update constructor to remove unused susds (#67) Co-authored-by: Lucas --- deploy/ControllerDeploy.sol | 12 ++++-------- deploy/ControllerInit.sol | 1 - script/Deploy.s.sol | 6 ++---- script/staging/StagingDeploymentBase.sol | 3 +-- script/staging/test/DeployEthereum.t.sol | 1 - src/MainnetController.sol | 11 ++--------- test/mainnet-fork/DeployAndInit.t.sol | 24 ++++++------------------ test/mainnet-fork/ForkTestBase.t.sol | 3 +-- test/unit/controllers/Admin.t.sol | 5 +---- test/unit/controllers/Constructor.t.sol | 7 +------ test/unit/controllers/Freeze.t.sol | 5 +---- test/unit/deployments/Deploy.t.sol | 14 ++------------ test/unit/mocks/MockSUsds.sol | 12 ------------ 13 files changed, 21 insertions(+), 83 deletions(-) delete mode 100644 test/unit/mocks/MockSUsds.sol diff --git a/deploy/ControllerDeploy.sol b/deploy/ControllerDeploy.sol index 4fc7bb8..b3489e6 100644 --- a/deploy/ControllerDeploy.sol +++ b/deploy/ControllerDeploy.sol @@ -62,8 +62,7 @@ library MainnetControllerDeploy { address vault, address psm, address daiUsds, - address cctp, - address susds + address cctp ) internal returns (address controller) { @@ -74,8 +73,7 @@ library MainnetControllerDeploy { vault_ : vault, psm_ : psm, daiUsds_ : daiUsds, - cctp_ : cctp, - susds_ : susds + cctp_ : cctp })); } @@ -84,8 +82,7 @@ library MainnetControllerDeploy { address vault, address psm, address daiUsds, - address cctp, - address susds + address cctp ) internal returns (ControllerInstance memory instance) { @@ -99,8 +96,7 @@ library MainnetControllerDeploy { vault_ : vault, psm_ : psm, daiUsds_ : daiUsds, - cctp_ : cctp, - susds_ : susds + cctp_ : cctp })); } diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol index f1f86a2..0a32ba0 100644 --- a/deploy/ControllerInit.sol +++ b/deploy/ControllerInit.sol @@ -96,7 +96,6 @@ library MainnetControllerInit { require(address(controller.psm()) == addresses.psm, "MainnetControllerInit/incorrect-psm"); require(address(controller.daiUsds()) == addresses.daiUsds, "MainnetControllerInit/incorrect-daiUsds"); require(address(controller.cctp()) == addresses.cctpMessenger, "MainnetControllerInit/incorrect-cctpMessenger"); - require(address(controller.susds()) == addresses.susds, "MainnetControllerInit/incorrect-susds"); require(address(controller.dai()) == addresses.dai, "MainnetControllerInit/incorrect-dai"); require(address(controller.usdc()) == addresses.usdc, "MainnetControllerInit/incorrect-usdc"); require(address(controller.usds()) == addresses.usds, "MainnetControllerInit/incorrect-usds"); diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 78939ea..8194b3d 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -33,8 +33,7 @@ contract DeployMainnetFull is Script { vault : config.readAddress(".allocatorVault"), psm : config.readAddress(".psm"), daiUsds : config.readAddress(".daiUsds"), - cctp : config.readAddress(".cctpTokenMessenger"), - susds : config.readAddress(".susds") + cctp : config.readAddress(".cctpTokenMessenger") }); vm.stopBroadcast(); @@ -76,8 +75,7 @@ contract DeployMainnetController is Script { vault : config.readAddress(".allocatorVault"), psm : config.readAddress(".psm"), daiUsds : config.readAddress(".daiUsds"), - cctp : config.readAddress(".cctpTokenMessenger"), - susds : config.readAddress(".susds") + cctp : config.readAddress(".cctpTokenMessenger") }); vm.stopBroadcast(); diff --git a/script/staging/StagingDeploymentBase.sol b/script/staging/StagingDeploymentBase.sol index 7cbc19e..15f1da1 100644 --- a/script/staging/StagingDeploymentBase.sol +++ b/script/staging/StagingDeploymentBase.sol @@ -275,8 +275,7 @@ contract StagingDeploymentBase is Script { vault : vault, psm : psm, daiUsds : daiUsds, - cctp : CCTP_TOKEN_MESSENGER_MAINNET, - susds : susds + cctp : CCTP_TOKEN_MESSENGER_MAINNET }); mainnetAlmProxy = instance.almProxy; diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol index 2d2728e..9de25de 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/DeployEthereum.t.sol @@ -166,7 +166,6 @@ contract DeployEthereumTest is Test { assertEq(address(mainnetController.psm()), outputMainnet.readAddress(".psm")); assertEq(address(mainnetController.daiUsds()), outputMainnet.readAddress(".daiUsds")); assertEq(address(mainnetController.cctp()), inputMainnet.readAddress(".cctpTokenMessenger")); - assertEq(address(mainnetController.susds()), outputMainnet.readAddress(".susds")); assertEq(address(mainnetController.dai()), outputMainnet.readAddress(".dai")); assertEq(address(mainnetController.usdc()), outputMainnet.readAddress(".usdc")); assertEq(address(mainnetController.usds()), outputMainnet.readAddress(".usds")); diff --git a/src/MainnetController.sol b/src/MainnetController.sol index 1b0d03e..9f355b0 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -28,10 +28,6 @@ interface IEthenaMinterLike { function removeDelegatedSigner(address delegateSigner) external; } -interface ISUSDSLike is IERC4626 { - function usds() external view returns(address); -} - interface ISUSDELike is IERC4626 { function cooldownAssets(uint256 usdeAmount) external; function cooldownShares(uint256 susdeAmount) external; @@ -110,7 +106,6 @@ contract MainnetController is AccessControl { IERC20 public immutable usde; IERC20 public immutable usdc; ISUSDELike public immutable susde; - ISUSDSLike public immutable susds; uint256 public immutable psmTo18ConversionFactor; @@ -129,8 +124,7 @@ contract MainnetController is AccessControl { address vault_, address psm_, address daiUsds_, - address cctp_, - address susds_ + address cctp_ ) { _grantRole(DEFAULT_ADMIN_ROLE, admin_); @@ -145,10 +139,9 @@ contract MainnetController is AccessControl { ethenaMinter = IEthenaMinterLike(Ethereum.ETHENA_MINTER); susde = ISUSDELike(Ethereum.SUSDE); - susds = ISUSDSLike(susds_); dai = IERC20(daiUsds.dai()); usdc = IERC20(psm.gem()); - usds = IERC20(susds.usds()); + usds = IERC20(Ethereum.USDS); usde = IERC20(Ethereum.USDE); psmTo18ConversionFactor = psm.to18ConversionFactor(); diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/DeployAndInit.t.sol index 38ff7d1..14493a9 100644 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ b/test/mainnet-fork/DeployAndInit.t.sol @@ -134,8 +134,7 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); MintRecipient[] memory mintRecipients_ = new MintRecipient[](1); @@ -234,11 +233,6 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-cctpMessenger")); } - function test_init_incorrectSUsds() external { - addresses.susds = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-susds")); - } - function test_init_incorrectDai() external { addresses.dai = mismatchAddress; _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-dai")); @@ -438,8 +432,7 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); addresses.oldController = address(mainnetController); @@ -492,8 +485,7 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); // Overwrite storage for all previous deployments in setUp and assert deployment @@ -513,7 +505,6 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn assertEq(address(mainnetController.psm()), PSM); assertEq(address(mainnetController.daiUsds()), DAI_USDS); assertEq(address(mainnetController.cctp()), CCTP_MESSENGER); - assertEq(address(mainnetController.susds()), address(susds)); assertEq(address(mainnetController.dai()), address(dai)); assertEq(address(mainnetController.usdc()), address(usdc)); assertEq(address(mainnetController.usds()), address(usds)); @@ -591,8 +582,7 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); // Overwrite storage for all previous deployments in setUp and assert deployment @@ -655,8 +645,7 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); ( @@ -683,8 +672,7 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn vault, PSM, DAI_USDS, - CCTP_MESSENGER, - address(susds) + CCTP_MESSENGER ); // Overwrite storage for all previous deployments in setUp and assert deployment diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 13fe2d9..3726a30 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -208,8 +208,7 @@ contract ForkTestBase is DssTest { vault : ilkInst.vault, psm : Ethereum.PSM, daiUsds: Ethereum.DAI_USDS, - cctp : Ethereum.CCTP_TOKEN_MESSENGER, - susds : Ethereum.SUSDS + cctp : Ethereum.CCTP_TOKEN_MESSENGER }); almProxy = ALMProxy(payable(controllerInst.almProxy)); diff --git a/test/unit/controllers/Admin.t.sol b/test/unit/controllers/Admin.t.sol index ec19fb0..90ee41b 100644 --- a/test/unit/controllers/Admin.t.sol +++ b/test/unit/controllers/Admin.t.sol @@ -6,7 +6,6 @@ import { MainnetController } from "../../../src/MainnetController.sol"; import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; import { MockPSM } from "../mocks/MockPSM.sol"; -import { MockSUsds } from "../mocks/MockSUsds.sol"; import { MockVault } from "../mocks/MockVault.sol"; import "../UnitTestBase.t.sol"; @@ -23,7 +22,6 @@ contract MainnetControllerAdminTests is UnitTestBase { function setUp() public { MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai")); MockPSM psm = new MockPSM(makeAddr("usdc")); - MockSUsds susds = new MockSUsds(makeAddr("susds")); MockVault vault = new MockVault(makeAddr("buffer")); mainnetController = new MainnetController( @@ -33,8 +31,7 @@ contract MainnetControllerAdminTests is UnitTestBase { address(vault), address(psm), address(daiUsds), - makeAddr("cctp"), - address(susds) + makeAddr("cctp") ); } diff --git a/test/unit/controllers/Constructor.t.sol b/test/unit/controllers/Constructor.t.sol index 4d55441..33f96f1 100644 --- a/test/unit/controllers/Constructor.t.sol +++ b/test/unit/controllers/Constructor.t.sol @@ -7,7 +7,6 @@ import { MainnetController } from "../../../src/MainnetController.sol"; import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; import { MockPSM } from "../mocks/MockPSM.sol"; import { MockPSM3 } from "../mocks/MockPSM3.sol"; -import { MockSUsds } from "../mocks/MockSUsds.sol"; import { MockVault } from "../mocks/MockVault.sol"; import "../UnitTestBase.t.sol"; @@ -17,7 +16,6 @@ contract MainnetControllerConstructorTests is UnitTestBase { function test_constructor() public { MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai")); MockPSM psm = new MockPSM(makeAddr("usdc")); - MockSUsds susds = new MockSUsds(makeAddr("usds")); MockVault vault = new MockVault(makeAddr("buffer")); MainnetController mainnetController = new MainnetController( @@ -27,8 +25,7 @@ contract MainnetControllerConstructorTests is UnitTestBase { address(vault), address(psm), address(daiUsds), - makeAddr("cctp"), - address(susds) + makeAddr("cctp") ); assertEq(mainnetController.hasRole(DEFAULT_ADMIN_ROLE, admin), true); @@ -40,10 +37,8 @@ contract MainnetControllerConstructorTests is UnitTestBase { assertEq(address(mainnetController.psm()), address(psm)); assertEq(address(mainnetController.daiUsds()), address(daiUsds)); assertEq(address(mainnetController.cctp()), makeAddr("cctp")); - assertEq(address(mainnetController.susds()), address(susds)); assertEq(address(mainnetController.dai()), makeAddr("dai")); // Dai param in MockDaiUsds assertEq(address(mainnetController.usdc()), makeAddr("usdc")); // Gem param in MockPSM - assertEq(address(mainnetController.usds()), makeAddr("usds")); // Usds param in MockSUsds assertEq(mainnetController.psmTo18ConversionFactor(), psm.to18ConversionFactor()); assertEq(mainnetController.psmTo18ConversionFactor(), 1e12); diff --git a/test/unit/controllers/Freeze.t.sol b/test/unit/controllers/Freeze.t.sol index d944e13..c1206c2 100644 --- a/test/unit/controllers/Freeze.t.sol +++ b/test/unit/controllers/Freeze.t.sol @@ -7,7 +7,6 @@ import { ForeignController } from "../../../src/ForeignController.sol"; import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; import { MockPSM } from "../mocks/MockPSM.sol"; import { MockPSM3 } from "../mocks/MockPSM3.sol"; -import { MockSUsds } from "../mocks/MockSUsds.sol"; import { MockVault } from "../mocks/MockVault.sol"; import "../UnitTestBase.t.sol"; @@ -26,7 +25,6 @@ contract ControllerTestBase is UnitTestBase { function setUp() public virtual { MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai")); MockPSM psm = new MockPSM(makeAddr("usdc")); - MockSUsds susds = new MockSUsds(makeAddr("susds")); MockVault vault = new MockVault(makeAddr("buffer")); // Default to mainnet controller for tests and override with foreign controller @@ -37,8 +35,7 @@ contract ControllerTestBase is UnitTestBase { address(vault), address(psm), address(daiUsds), - makeAddr("cctp"), - address(susds) + makeAddr("cctp") ))); _setRoles(); diff --git a/test/unit/deployments/Deploy.t.sol b/test/unit/deployments/Deploy.t.sol index b85cf0a..213bec8 100644 --- a/test/unit/deployments/Deploy.t.sol +++ b/test/unit/deployments/Deploy.t.sol @@ -5,7 +5,6 @@ import "../../../deploy/ControllerDeploy.sol"; // All imports needed so not imp import { MockDaiUsds } from "../mocks/MockDaiUsds.sol"; import { MockPSM } from "../mocks/MockPSM.sol"; -import { MockSUsds } from "../mocks/MockSUsds.sol"; import { MockVault } from "../mocks/MockVault.sol"; import "../UnitTestBase.t.sol"; @@ -76,7 +75,6 @@ contract MainnetControllerDeployTests is UnitTestBase { struct TestVars { address daiUsds; address psm; - address susds; address admin; address vault; address cctp; @@ -87,7 +85,6 @@ contract MainnetControllerDeployTests is UnitTestBase { vars.daiUsds = address(new MockDaiUsds(makeAddr("dai"))); vars.psm = address(new MockPSM(makeAddr("usdc"))); - vars.susds = address(new MockSUsds(makeAddr("usds"))); vars.vault = address(new MockVault(makeAddr("buffer"))); vars.admin = makeAddr("admin"); @@ -104,8 +101,7 @@ contract MainnetControllerDeployTests is UnitTestBase { vars.vault, vars.psm, vars.daiUsds, - vars.cctp, - vars.susds + vars.cctp ) ); @@ -118,10 +114,8 @@ contract MainnetControllerDeployTests is UnitTestBase { assertEq(address(controller.psm()), vars.psm); assertEq(address(controller.daiUsds()), vars.daiUsds); assertEq(address(controller.cctp()), vars.cctp); - assertEq(address(controller.susds()), vars.susds); assertEq(address(controller.dai()), makeAddr("dai")); // Dai param in MockDaiUsds assertEq(address(controller.usdc()), makeAddr("usdc")); // Gem param in MockPSM - assertEq(address(controller.usds()), makeAddr("usds")); // Usds param in MockSUsds assertEq(controller.psmTo18ConversionFactor(), 1e12); assertEq(controller.active(), true); @@ -132,7 +126,6 @@ contract MainnetControllerDeployTests is UnitTestBase { vars.daiUsds = address(new MockDaiUsds(makeAddr("dai"))); vars.psm = address(new MockPSM(makeAddr("usdc"))); - vars.susds = address(new MockSUsds(makeAddr("usds"))); vars.vault = address(new MockVault(makeAddr("buffer"))); vars.admin = makeAddr("admin"); @@ -143,8 +136,7 @@ contract MainnetControllerDeployTests is UnitTestBase { vars.vault, vars.psm, vars.daiUsds, - vars.cctp, - vars.susds + vars.cctp ); ALMProxy almProxy = ALMProxy(payable(instance.almProxy)); @@ -162,10 +154,8 @@ contract MainnetControllerDeployTests is UnitTestBase { assertEq(address(controller.psm()), vars.psm); assertEq(address(controller.daiUsds()), vars.daiUsds); assertEq(address(controller.cctp()), vars.cctp); - assertEq(address(controller.susds()), vars.susds); assertEq(address(controller.dai()), makeAddr("dai")); // Dai param in MockDaiUsds assertEq(address(controller.usdc()), makeAddr("usdc")); // Gem param in MockPSM - assertEq(address(controller.usds()), makeAddr("usds")); // Usds param in MockSUsds assertEq(controller.psmTo18ConversionFactor(), 1e12); assertEq(controller.active(), true); diff --git a/test/unit/mocks/MockSUsds.sol b/test/unit/mocks/MockSUsds.sol deleted file mode 100644 index e9cc52d..0000000 --- a/test/unit/mocks/MockSUsds.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -contract MockSUsds { - - address public usds; - - constructor(address _usds) { - usds = _usds; - } - -} From 22bb50ede680e83d80c69c3cc9c60eed750cdad8 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Wed, 11 Dec 2024 14:26:32 -0500 Subject: [PATCH 10/22] feat: Refactor staging to simplify and new deploy (SC-873) (#66) * refactor staging to most up to date and do another deploy * remove custom psm in favour of real one * new deploy and add sUSDe rate limits * formating and fixed some modifiers * split out into deps and release json * formatting --- Makefile | 5 +- deploy/ControllerInit.sol | 35 ++- script/input/1/base-staging.json | 9 +- script/input/1/common.json | 5 - script/input/1/mainnet-staging.json | 7 +- .../1/base-staging-deps-release-20241022.json | 8 + .../1/base-staging-deps-release-20241210.json | 5 + .../1/base-staging-release-20241022.json | 8 +- .../1/base-staging-release-20241210.json | 5 + ...mainnet-staging-deps-release-20241022.json | 18 ++ ...mainnet-staging-deps-release-20241210.json | 19 ++ .../1/mainnet-staging-release-20241022.json | 18 +- .../1/mainnet-staging-release-20241210.json | 5 + script/staging/DeployEthereum.s.sol | 37 --- ...oymentBase.sol => FullStagingDeploy.s.sol} | 268 +++++++----------- script/staging/mocks/MockDaiUsds.sol | 27 -- script/staging/mocks/MockPSM.sol | 43 --- script/staging/mocks/MockRateProvider.sol | 10 - script/staging/mocks/MockSUsds.sol | 14 - test/base-fork/DeployAndInit.t.sol | 6 +- test/mainnet-fork/DeployAndInit.t.sol | 2 +- 21 files changed, 193 insertions(+), 361 deletions(-) delete mode 100644 script/input/1/common.json create mode 100644 script/output/1/base-staging-deps-release-20241022.json create mode 100644 script/output/1/base-staging-deps-release-20241210.json create mode 100644 script/output/1/base-staging-release-20241210.json create mode 100644 script/output/1/mainnet-staging-deps-release-20241022.json create mode 100644 script/output/1/mainnet-staging-deps-release-20241210.json create mode 100644 script/output/1/mainnet-staging-release-20241210.json delete mode 100644 script/staging/DeployEthereum.s.sol rename script/staging/{StagingDeploymentBase.sol => FullStagingDeploy.s.sol} (64%) delete mode 100644 script/staging/mocks/MockDaiUsds.sol delete mode 100644 script/staging/mocks/MockPSM.sol delete mode 100644 script/staging/mocks/MockRateProvider.sol delete mode 100644 script/staging/mocks/MockSUsds.sol diff --git a/Makefile b/Makefile index e8703e0..9f1b5c4 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ +# Staging Full Deployment with Dependencies +deploy-staging-full :; forge script script/staging/FullStagingDeploy.s.sol:FullStagingDeploy --sender ${ETH_FROM} --broadcast --verify + # Staging Deployments -deploy-mainnet-staging-full :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetFull --sender ${ETH_FROM} --broadcast --verify deploy-mainnet-staging-controller :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetController --sender ${ETH_FROM} --broadcast --verify -deploy-base-staging-full :; CHAIN=base ENV=staging forge script script/Deploy.s.sol:DeployForeignFull --sender ${ETH_FROM} --broadcast --verify deploy-base-staging-controller :; CHAIN=base ENV=staging forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify # Production Deployments diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol index 0a32ba0..46ab465 100644 --- a/deploy/ControllerInit.sol +++ b/deploy/ControllerInit.sol @@ -134,11 +134,11 @@ library MainnetControllerInit { addresses.susds ); - _setRateLimitData(controller.LIMIT_USDS_MINT(), rateLimits, data.usdsMintData, "usdsMintData", 18); - _setRateLimitData(controller.LIMIT_USDS_TO_USDC(), rateLimits, data.usdsToUsdcData, "usdsToUsdcData", 6); - _setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); - _setRateLimitData(domainKeyBase, rateLimits, data.cctpToBaseDomainData, "cctpToBaseDomainData", 6); - _setRateLimitData(susdsKey, rateLimits, data.susdsDepositData, "susdsDepositData", 18); + setRateLimitData(controller.LIMIT_USDS_MINT(), rateLimits, data.usdsMintData, "usdsMintData", 18); + setRateLimitData(controller.LIMIT_USDS_TO_USDC(), rateLimits, data.usdsToUsdcData, "usdsToUsdcData", 6); + setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); + setRateLimitData(domainKeyBase, rateLimits, data.cctpToBaseDomainData, "cctpToBaseDomainData", 6); + setRateLimitData(susdsKey, rateLimits, data.susdsDepositData, "susdsDepositData", 18); // Step 4: Configure the mint recipients on other domains @@ -175,7 +175,7 @@ library MainnetControllerInit { IPSMLike(psm).kiss(almProxy); // To allow using no fee functionality } - function _setRateLimitData( + function setRateLimitData( bytes32 key, IRateLimits rateLimits, RateLimitData memory data, @@ -295,14 +295,15 @@ library ForeignControllerInit { CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM ); - _setRateLimitData(_makeKey(depositKey, addresses.usdc), rateLimits, data.usdcDepositData, "usdcDepositData", 6); - _setRateLimitData(_makeKey(withdrawKey, addresses.usdc), rateLimits, data.usdcWithdrawData, "usdcWithdrawData", 6); - _setRateLimitData(_makeKey(depositKey, addresses.usds), rateLimits, data.usdsDepositData, "usdsDepositData", 18); - _setRateLimitData(_makeKey(withdrawKey, addresses.usds), rateLimits, data.usdsWithdrawData, "usdsWithdrawData", 18); - _setRateLimitData(_makeKey(depositKey, addresses.susds), rateLimits, data.susdsDepositData, "susdsDepositData", 18); - _setRateLimitData(_makeKey(withdrawKey, addresses.susds), rateLimits, data.susdsWithdrawData, "susdsWithdrawData", 18); - _setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); - _setRateLimitData(domainKeyEthereum, rateLimits, data.cctpToEthereumDomainData, "cctpToEthereumDomainData", 6); + setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.usdc), rateLimits, data.usdcDepositData, "usdcDepositData", 6); + setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.usdc), rateLimits, data.usdcWithdrawData, "usdcWithdrawData", 6); + setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.usds), rateLimits, data.usdsDepositData, "usdsDepositData", 18); + setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.usds), rateLimits, data.usdsWithdrawData, "usdsWithdrawData", 18); + setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.susds), rateLimits, data.susdsDepositData, "susdsDepositData", 18); + setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.susds), rateLimits, data.susdsWithdrawData, "susdsWithdrawData", 18); + + setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); + setRateLimitData(domainKeyEthereum, rateLimits, data.cctpToEthereumDomainData, "cctpToEthereumDomainData", 6); // Step 3: Configure the mint recipients on other domains @@ -311,11 +312,7 @@ library ForeignControllerInit { } } - function _makeKey(bytes32 actionKey, address asset) internal pure returns (bytes32) { - return RateLimitHelpers.makeAssetKey(actionKey, asset); - } - - function _setRateLimitData( + function setRateLimitData( bytes32 key, IRateLimits rateLimits, RateLimitData memory data, diff --git a/script/input/1/base-staging.json b/script/input/1/base-staging.json index 164c213..904585b 100644 --- a/script/input/1/base-staging.json +++ b/script/input/1/base-staging.json @@ -1,10 +1,9 @@ { - "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", - "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE", "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", - "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", - "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b", "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", - "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a" } diff --git a/script/input/1/common.json b/script/input/1/common.json deleted file mode 100644 index 2b7a667..0000000 --- a/script/input/1/common.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ilk": "ALLOCATOR-SPARK-A", - "usdcUnitSize": 10, - "usdsUnitSize": 10 -} diff --git a/script/input/1/mainnet-staging.json b/script/input/1/mainnet-staging.json index 7c41094..294d050 100644 --- a/script/input/1/mainnet-staging.json +++ b/script/input/1/mainnet-staging.json @@ -1,12 +1,11 @@ { - "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", - "allocatorVault": "0x8E20650287635aE6e20ce38EcD3E795919D52354", - "almProxy": "0xC29D06ce81137E6B3C3DC090713636d81600a347", + "ilk": "ALLOCATOR-SPARK-A", + "usdcUnitSize": 10, + "usdsUnitSize": 10, "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8", - "rateLimits": "0x9A140AC56CC28A00B2c036F454F202c2459ca84c", "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", diff --git a/script/output/1/base-staging-deps-release-20241022.json b/script/output/1/base-staging-deps-release-20241022.json new file mode 100644 index 0000000..ce097ad --- /dev/null +++ b/script/output/1/base-staging-deps-release-20241022.json @@ -0,0 +1,8 @@ +{ + "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", + "psm": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7", + "susds": "0x4ae97016a03C132d2F600444E2493C62B01C9497", + "safe": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "usds": "0x4e9BEe8F2b33d8893a5A219854AC52e9518ee328" +} diff --git a/script/output/1/base-staging-deps-release-20241210.json b/script/output/1/base-staging-deps-release-20241210.json new file mode 100644 index 0000000..d80ca76 --- /dev/null +++ b/script/output/1/base-staging-deps-release-20241210.json @@ -0,0 +1,5 @@ +{ + "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", + "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047" +} diff --git a/script/output/1/base-staging-release-20241022.json b/script/output/1/base-staging-release-20241022.json index edb474c..98aa25a 100644 --- a/script/output/1/base-staging-release-20241022.json +++ b/script/output/1/base-staging-release-20241022.json @@ -1,11 +1,5 @@ { - "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE", "controller": "0xD26112Ce8f7BE0834dBcfd018042bF76d68Ff42a", - "psm": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7", - "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b", - "susds": "0x4ae97016a03C132d2F600444E2493C62B01C9497", - "safe": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", - "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", - "usds": "0x4e9BEe8F2b33d8893a5A219854AC52e9518ee328" + "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b" } diff --git a/script/output/1/base-staging-release-20241210.json b/script/output/1/base-staging-release-20241210.json new file mode 100644 index 0000000..ea6f4df --- /dev/null +++ b/script/output/1/base-staging-release-20241210.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0x2627d5D0AF0B88Ee58BD7346F20A429f67a73e00", + "controller": "0xa3091Dfa6c02B6611250733852c95A59a127E00F", + "rateLimits": "0xAe20F9093eB3301b2D83871A3505935eFc8498C6" +} diff --git a/script/output/1/mainnet-staging-deps-release-20241022.json b/script/output/1/mainnet-staging-deps-release-20241022.json new file mode 100644 index 0000000..0dea83b --- /dev/null +++ b/script/output/1/mainnet-staging-deps-release-20241022.json @@ -0,0 +1,18 @@ +{ + "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", + "allocatorBuffer": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7", + "allocatorOracle": "0x4ae97016a03C132d2F600444E2493C62B01C9497", + "allocatorRegistry": "0x6f75E221ccd8D7C496a48Ff4fd854C2319F2DaE2", + "allocatorRoles": "0x87E8A7537875661d12a31912FAaF07f50043e3D7", + "allocatorVault": "0x8E20650287635aE6e20ce38EcD3E795919D52354", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "jug": "0x464c46b3bFCf261ABFe440F90f08a08A39a59DD4", + "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8", + "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + "safe": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + "usdsJoin": "0xA1CCf21b7012874fB2CD81704e0eeeF083DDe1b8", + "vat": "0xea53A79Fc3e024C887f37E844FA24B3DEbEC84B4" +} diff --git a/script/output/1/mainnet-staging-deps-release-20241210.json b/script/output/1/mainnet-staging-deps-release-20241210.json new file mode 100644 index 0000000..9144c31 --- /dev/null +++ b/script/output/1/mainnet-staging-deps-release-20241210.json @@ -0,0 +1,19 @@ +{ + "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", + "allocatorBuffer": "0x36138584868028D1913bf01359D7c736E6773008", + "allocatorOracle": "0xfC0E1fFBF9cCd82688c775b1587c45506cebDBdf", + "allocatorRegistry": "0xfd0A671c07309f14b05ec72c741C86AEA02e873c", + "allocatorRoles": "0xF954e125E979e104974882ca94063B4f088cf71D", + "allocatorVault": "0xAB0d4019B1182021C4cAa2F3D078EFe55cD5B5A6", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "jug": "0xf999576B81c53BFf473550354eeD98cD7b126184", + "psm": "0x8ac160e388a3F975c9Db1D41DeB76C574702CFa9", + "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + "usdsJoin": "0xAd309294c38EB44C929a9bB2A9452B1C8D96965e", + "vat": "0x373E0699D8bFDdd99d78248e5b993E6a02061B0e" +} diff --git a/script/output/1/mainnet-staging-release-20241022.json b/script/output/1/mainnet-staging-release-20241022.json index 4656819..909bd36 100644 --- a/script/output/1/mainnet-staging-release-20241022.json +++ b/script/output/1/mainnet-staging-release-20241022.json @@ -1,21 +1,5 @@ { - "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", - "allocatorBuffer": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7", - "allocatorOracle": "0x4ae97016a03C132d2F600444E2493C62B01C9497", - "allocatorRegistry": "0x6f75E221ccd8D7C496a48Ff4fd854C2319F2DaE2", - "allocatorRoles": "0x87E8A7537875661d12a31912FAaF07f50043e3D7", - "allocatorVault": "0x8E20650287635aE6e20ce38EcD3E795919D52354", "almProxy": "0xC29D06ce81137E6B3C3DC090713636d81600a347", "controller": "0xcc0c5ADF6649256d3cE6084eCf94AF5D01440b6C", - "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", - "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", - "jug": "0x464c46b3bFCf261ABFe440F90f08a08A39a59DD4", - "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8", - "rateLimits": "0x9A140AC56CC28A00B2c036F454F202c2459ca84c", - "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", - "safe": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", - "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", - "usdsJoin": "0xA1CCf21b7012874fB2CD81704e0eeeF083DDe1b8", - "vat": "0xea53A79Fc3e024C887f37E844FA24B3DEbEC84B4" + "rateLimits": "0x9A140AC56CC28A00B2c036F454F202c2459ca84c" } diff --git a/script/output/1/mainnet-staging-release-20241210.json b/script/output/1/mainnet-staging-release-20241210.json new file mode 100644 index 0000000..1c9379d --- /dev/null +++ b/script/output/1/mainnet-staging-release-20241210.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0x675fc95BF2b42Fc61FF0f2E9969d9Ab19b65cda5", + "controller": "0x08c830bc14b52A65E7e62aBc7365e1C53933D4Bf", + "rateLimits": "0x449F100E37CF9CC4631c044efC4726609Be26766" +} diff --git a/script/staging/DeployEthereum.s.sol b/script/staging/DeployEthereum.s.sol deleted file mode 100644 index 7d19503..0000000 --- a/script/staging/DeployEthereum.s.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { ScriptTools } from "dss-test/ScriptTools.sol"; - -import { stdJson } from "forge-std/StdJson.sol"; - -import { Domain, StagingDeploymentBase } from "./StagingDeploymentBase.sol"; - -contract DeployEthereumStaging is StagingDeploymentBase { - - using stdJson for string; - using ScriptTools for string; - - function run() public { - vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); - vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); - - deployer = msg.sender; - - mainnet = Domain({ - name : "mainnet", - config : ScriptTools.loadConfig("mainnet"), - forkId : vm.createFork(getChain("mainnet").rpcUrl), - admin : deployer - }); - base = Domain({ - name : "base", - config : ScriptTools.loadConfig("base"), - forkId : vm.createFork(getChain("base").rpcUrl), - admin : deployer - }); - - _runFullDeployment({ useLiveContracts: true }); - } - -} diff --git a/script/staging/StagingDeploymentBase.sol b/script/staging/FullStagingDeploy.s.sol similarity index 64% rename from script/staging/StagingDeploymentBase.sol rename to script/staging/FullStagingDeploy.s.sol index 15f1da1..05b6cd6 100644 --- a/script/staging/StagingDeploymentBase.sol +++ b/script/staging/FullStagingDeploy.s.sol @@ -19,14 +19,10 @@ import { AllocatorVault } from "dss-allocator/src/AllocatorVault.sol"; import { ScriptTools } from "dss-test/ScriptTools.sol"; -import { MockERC20 } from "erc20-helpers/MockERC20.sol"; - import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { Script } from "forge-std/Script.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol"; - import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { @@ -44,39 +40,27 @@ import { RateLimitData } from "../../deploy/ControllerInit.sol"; -import { MockDaiUsds } from "./mocks/MockDaiUsds.sol"; +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; + import { MockJug } from "./mocks/MockJug.sol"; -import { MockPSM } from "./mocks/MockPSM.sol"; -import { MockRateProvider } from "./mocks/MockRateProvider.sol"; -import { MockSUsds } from "./mocks/MockSUsds.sol"; import { MockUsdsJoin } from "./mocks/MockUsdsJoin.sol"; import { MockVat } from "./mocks/MockVat.sol"; import { PSMWrapper } from "./mocks/PSMWrapper.sol"; struct Domain { string name; + string nameDeps; string config; uint256 forkId; address admin; } -contract StagingDeploymentBase is Script { +contract FullStagingDeploy is Script { using stdJson for string; using ScriptTools for string; - /**********************************************************************************************/ - /*** Existing addresses (populated from JSON) ***/ - /**********************************************************************************************/ - - address CCTP_TOKEN_MESSENGER_BASE; - address CCTP_TOKEN_MESSENGER_MAINNET; - - address SAFE_MAINNET; - address SAFE_BASE; - address USDC; - address USDC_BASE; - /**********************************************************************************************/ /*** Mainnet existing/mock deployments ***/ /**********************************************************************************************/ @@ -87,6 +71,7 @@ contract StagingDeploymentBase is Script { address psm; address susds; address usds; + address usdc; // Mocked MCD contracts address jug; @@ -114,15 +99,6 @@ contract StagingDeploymentBase is Script { address mainnetAlmProxy; address mainnetController; - /**********************************************************************************************/ - /*** Base dependency deployments ***/ - /**********************************************************************************************/ - - address usdsBase; - address susdsBase; - - address psmBase; - /**********************************************************************************************/ /*** Deployment-specific variables ***/ /**********************************************************************************************/ @@ -140,51 +116,22 @@ contract StagingDeploymentBase is Script { /*** Helper functions ***/ /**********************************************************************************************/ - function _setUpDependencies(bool useLiveContracts) internal { + function _setUpDependencies() internal { vm.selectFork(mainnet.forkId); vm.startBroadcast(); - // Step 1: Deploy or use existing contracts for tokens, DaiUsds and PSM - if (useLiveContracts) _useLiveContracts(); - else _setUpMocks(); - - // Step 2: Deploy mocked MCD contracts - - vat = address(new MockVat(mainnet.admin)); - usdsJoin = address(new MockUsdsJoin(mainnet.admin, vat, usds)); - jug = address(new MockJug()); + // Step 1: Use existing contracts for tokens, DaiUsds and PSM - // Step 3: Transfer USDS into the join contract - - require(IERC20(usds).balanceOf(deployer) >= USDS_UNIT_SIZE, "USDS balance too low"); - - IERC20(usds).transfer(usdsJoin, USDS_UNIT_SIZE); - - vm.stopBroadcast(); - - // Step 4: Export all deployed addresses - - ScriptTools.exportContract(mainnet.name, "dai", dai); - ScriptTools.exportContract(mainnet.name, "daiUsds", daiUsds); - ScriptTools.exportContract(mainnet.name, "jug", jug); - ScriptTools.exportContract(mainnet.name, "psm", psm); - ScriptTools.exportContract(mainnet.name, "susds", susds); - ScriptTools.exportContract(mainnet.name, "usdc", USDC); - ScriptTools.exportContract(mainnet.name, "usds", usds); - ScriptTools.exportContract(mainnet.name, "usdsJoin", usdsJoin); - ScriptTools.exportContract(mainnet.name, "vat", vat); - } - - function _useLiveContracts() internal { dai = mainnet.config.readAddress(".dai"); usds = mainnet.config.readAddress(".usds"); susds = mainnet.config.readAddress(".susds"); + usdc = mainnet.config.readAddress(".usdc"); daiUsds = mainnet.config.readAddress(".daiUsds"); livePsm = mainnet.config.readAddress(".psm"); // This contract is necessary to get past the `kiss` requirement from the pause proxy. // It wraps the `noFee` calls with regular PSM swap calls. - psm = address(new PSMWrapper(USDC, dai, livePsm)); + psm = address(new PSMWrapper(usdc, dai, livePsm)); // NOTE: This is a HACK to make sure that `fill` doesn't get called until the call reverts. // Because this PSM contract is a wrapper over the real PSM, the controller queries @@ -192,28 +139,32 @@ contract StagingDeploymentBase is Script { // fills the live PSM NOT the wrapper, so the while loop will continue until the // function reverts. Dealing DAI into the wrapper will prevent fill from being called. IERC20(dai).transfer(psm, USDS_UNIT_SIZE); - } - function _setUpMocks() internal { - require(IERC20(USDC).balanceOf(deployer) >= USDC_UNIT_SIZE * 10, "USDC balance too low"); + // Step 2: Deploy mocked MCD contracts + + vat = address(new MockVat(mainnet.admin)); + usdsJoin = address(new MockUsdsJoin(mainnet.admin, vat, usds)); + jug = address(new MockJug()); + + // Step 3: Transfer USDS into the join contract - dai = address(new MockERC20("DAI", "DAI", 18)); - usds = address(new MockERC20("USDS", "USDS", 18)); - susds = address(new MockSUsds(usds)); + require(IERC20(usds).balanceOf(deployer) >= USDS_UNIT_SIZE, "USDS balance too low"); - daiUsds = address(new MockDaiUsds(mainnet.admin, dai, usds)); - psm = address(new MockPSM(mainnet.admin, USDC, dai)); + IERC20(usds).transfer(usdsJoin, USDS_UNIT_SIZE); - // Mint USDS into deployer so it can be transferred into usdsJoin - MockERC20(usds).mint(deployer, USDS_UNIT_SIZE); + vm.stopBroadcast(); - // Fill the psm with dai and usdc - IERC20(USDC).transfer(psm, USDC_UNIT_SIZE * 10); - MockERC20(dai).mint(psm, USDS_UNIT_SIZE); + // Step 4: Export all deployed addresses - // Fill the DaiUsds contract with both tokens - MockERC20(dai).mint(daiUsds, USDS_UNIT_SIZE); - MockERC20(usds).mint(daiUsds, USDS_UNIT_SIZE); + ScriptTools.exportContract(mainnet.nameDeps, "dai", dai); + ScriptTools.exportContract(mainnet.nameDeps, "daiUsds", daiUsds); + ScriptTools.exportContract(mainnet.nameDeps, "jug", jug); + ScriptTools.exportContract(mainnet.nameDeps, "psm", psm); + ScriptTools.exportContract(mainnet.nameDeps, "susds", susds); + ScriptTools.exportContract(mainnet.nameDeps, "usdc", usdc); + ScriptTools.exportContract(mainnet.nameDeps, "usds", usds); + ScriptTools.exportContract(mainnet.nameDeps, "usdsJoin", usdsJoin); + ScriptTools.exportContract(mainnet.nameDeps, "vat", vat); } function _setUpAllocationSystem() internal { @@ -256,12 +207,12 @@ contract StagingDeploymentBase is Script { // Step 4: Export all deployed addresses - ScriptTools.exportContract(mainnet.name, "allocatorOracle", oracle); - ScriptTools.exportContract(mainnet.name, "allocatorRegistry", registry); - ScriptTools.exportContract(mainnet.name, "allocatorRoles", roles); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorOracle", oracle); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorRegistry", registry); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorRoles", roles); - ScriptTools.exportContract(mainnet.name, "allocatorBuffer", buffer); - ScriptTools.exportContract(mainnet.name, "allocatorVault", vault); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorBuffer", buffer); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorVault", vault); } function _setUpALMController() internal { @@ -275,7 +226,7 @@ contract StagingDeploymentBase is Script { vault : vault, psm : psm, daiUsds : daiUsds, - cctp : CCTP_TOKEN_MESSENGER_MAINNET + cctp : mainnet.config.readAddress(".cctpTokenMessenger") }); mainnetAlmProxy = instance.almProxy; @@ -303,16 +254,16 @@ contract StagingDeploymentBase is Script { MainnetControllerInit.subDaoInitFull({ addresses: MainnetControllerInit.AddressParams({ admin : mainnet.admin, - freezer : makeAddr("freezer"), - relayer : SAFE_MAINNET, + freezer : mainnet.config.readAddress(".freezer"), + relayer : mainnet.config.readAddress(".relayer"), oldController : address(0), psm : psm, vault : vault, buffer : buffer, - cctpMessenger : CCTP_TOKEN_MESSENGER_MAINNET, + cctpMessenger : mainnet.config.readAddress(".cctpTokenMessenger"), dai : dai, daiUsds : daiUsds, - usdc : USDC, + usdc : usdc, usds : usds, susds : susds }), @@ -327,6 +278,26 @@ contract StagingDeploymentBase is Script { mintRecipients: mintRecipients }); + // Extra rate limit configuration + bytes32 mintKey = MainnetController(instance.controller).LIMIT_USDE_MINT(); + bytes32 burnKey = MainnetController(instance.controller).LIMIT_USDE_BURN(); + + bytes32 susdsDepositKey = RateLimitHelpers.makeAssetKey( + MainnetController(instance.controller).LIMIT_4626_DEPOSIT(), + address(MainnetController(instance.controller).susde()) + ); + + bytes32 susdsWithdrawKey = RateLimitHelpers.makeAssetKey( + MainnetController(instance.controller).LIMIT_4626_WITHDRAW(), + address(MainnetController(instance.controller).susde()) + ); + + // Extra rate limit configuration + MainnetControllerInit.setRateLimitData(mintKey, IRateLimits(instance.rateLimits), rateLimitData6, "usdeMintData", 6); + MainnetControllerInit.setRateLimitData(burnKey, IRateLimits(instance.rateLimits), rateLimitData18, "usdeBurnData", 18); + MainnetControllerInit.setRateLimitData(susdsDepositKey, IRateLimits(instance.rateLimits), rateLimitData18, "susdsDepositData", 18); + MainnetControllerInit.setRateLimitData(susdsWithdrawKey, IRateLimits(instance.rateLimits), rateLimitData18, "susdsWithdrawData", 18); + // Step 3: Transfer ownership of mock usdsJoin to the vault (able to mint usds) MockUsdsJoin(usdsJoin).transferOwnership(vault); @@ -335,38 +306,14 @@ contract StagingDeploymentBase is Script { // Step 4: Export all deployed addresses - ScriptTools.exportContract(mainnet.name, "safe", SAFE_MAINNET); + ScriptTools.exportContract(mainnet.nameDeps, "freezer", mainnet.config.readAddress(".freezer")); + ScriptTools.exportContract(mainnet.nameDeps, "relayer", mainnet.config.readAddress(".relayer")); + ScriptTools.exportContract(mainnet.name, "almProxy", instance.almProxy); ScriptTools.exportContract(mainnet.name, "controller", instance.controller); ScriptTools.exportContract(mainnet.name, "rateLimits", instance.rateLimits); } - function _setUpBasePSM() public { - vm.selectFork(base.forkId); - vm.startBroadcast(); - - usdsBase = address(new MockERC20("USDS", "USDS", 18)); - susdsBase = address(new MockERC20("sUSDS", "sUSDS", 18)); - - // Mint enough for seeded deposit - MockERC20(usdsBase).mint(deployer, 1e18); - - psmBase = PSM3Deploy.deploy({ - owner : deployer, - usdc : USDC_BASE, - usds : usdsBase, - susds : susdsBase, - rateProvider : address(new MockRateProvider()) - }); - - vm.stopBroadcast(); - - ScriptTools.exportContract(base.name, "usds", usdsBase); - ScriptTools.exportContract(base.name, "susds", susdsBase); - ScriptTools.exportContract(base.name, "usdc", USDC_BASE); - ScriptTools.exportContract(base.name, "psm", psmBase); - } - function _setUpBaseALMController() public { vm.selectFork(base.forkId); vm.startBroadcast(); @@ -375,9 +322,9 @@ contract StagingDeploymentBase is Script { ControllerInstance memory instance = ForeignControllerDeploy.deployFull({ admin : base.admin, - psm : address(psmBase), - usdc : USDC_BASE, - cctp : CCTP_TOKEN_MESSENGER_BASE + psm : base.config.readAddress(".psm"), + usdc : base.config.readAddress(".usdc"), + cctp : base.config.readAddress(".cctpTokenMessenger") }); baseAlmProxy = instance.almProxy; @@ -408,14 +355,14 @@ contract StagingDeploymentBase is Script { ForeignControllerInit.init({ addresses: ForeignControllerInit.AddressParams({ admin : base.admin, - freezer : makeAddr("freezer"), - relayer : SAFE_BASE, + freezer : base.config.readAddress(".freezer"), + relayer : base.config.readAddress(".relayer"), oldController : address(0), - psm : psmBase, - cctpMessenger : CCTP_TOKEN_MESSENGER_BASE, - usdc : USDC_BASE, - usds : usdsBase, - susds : susdsBase + psm : base.config.readAddress(".psm"), + cctpMessenger : base.config.readAddress(".cctpTokenMessenger"), + usdc : base.config.readAddress(".usdc"), + usds : base.config.readAddress(".usds"), + susds : base.config.readAddress(".susds") }), controllerInst: instance, data: ForeignControllerInit.InitRateLimitData({ @@ -431,16 +378,13 @@ contract StagingDeploymentBase is Script { mintRecipients: mintRecipients }); - // Step 3: Seed ALM Proxy with initial amounts of USDS and sUSDS - - MockERC20(usdsBase).mint(baseAlmProxy, USDS_UNIT_SIZE); - MockERC20(susdsBase).mint(baseAlmProxy, USDS_UNIT_SIZE); - vm.stopBroadcast(); - // Step 4: Export all deployed addresses + // Step 3: Export all deployed addresses + + ScriptTools.exportContract(base.nameDeps, "freezer", base.config.readAddress(".freezer")); + ScriptTools.exportContract(base.nameDeps, "relayer", base.config.readAddress(".relayer")); - ScriptTools.exportContract(base.name, "safe", SAFE_BASE); ScriptTools.exportContract(base.name, "almProxy", instance.almProxy); ScriptTools.exportContract(base.name, "controller", instance.controller); ScriptTools.exportContract(base.name, "rateLimits", instance.rateLimits); @@ -458,52 +402,42 @@ contract StagingDeploymentBase is Script { vm.stopBroadcast(); } - function _transferOwnershipOfMocks() internal { - vm.selectFork(mainnet.forkId); - vm.startBroadcast(); - - MockDaiUsds(daiUsds).transferOwnership(mainnetAlmProxy); - MockPSM(psm).transferOwnership(mainnetAlmProxy); + function run() public { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); - vm.stopBroadcast(); - } + deployer = msg.sender; - function _runFullDeployment(bool useLiveContracts) internal { - // Step 1: Load general configuration - - string memory common = ScriptTools.loadConfig("common"); - - ilk = common.readString(".ilk").stringToBytes32(); + mainnet = Domain({ + name : "mainnet-staging", + nameDeps : "mainnet-staging-deps", + config : ScriptTools.loadConfig("mainnet-staging"), + forkId : vm.createFork(getChain("mainnet").rpcUrl), + admin : deployer + }); + base = Domain({ + name : "base-staging", + nameDeps : "base-staging-deps", + config : ScriptTools.loadConfig("base-staging"), + forkId : vm.createFork(getChain("base").rpcUrl), + admin : deployer + }); // Ballpark sizing of rate limits, tokens in PSMs, etc // Ballpark sizing of USDS to put in the join contracts, PSMs, etc - USDC_UNIT_SIZE = common.readUint(".usdcUnitSize") * 1e6; - USDS_UNIT_SIZE = common.readUint(".usdsUnitSize") * 1e18; - - // Step 2: Load domain-specific configurations + USDC_UNIT_SIZE = mainnet.config.readUint(".usdcUnitSize") * 1e6; + USDS_UNIT_SIZE = mainnet.config.readUint(".usdsUnitSize") * 1e18; - CCTP_TOKEN_MESSENGER_MAINNET = mainnet.config.readAddress(".cctpTokenMessenger"); - CCTP_TOKEN_MESSENGER_BASE = base.config.readAddress(".cctpTokenMessenger"); + // Run deployment scripts after setting storage variables - SAFE_MAINNET = mainnet.config.readAddress(".safe"); - USDC = mainnet.config.readAddress(".usdc"); - - SAFE_BASE = base.config.readAddress(".safe"); - USDC_BASE = base.config.readAddress(".usdc"); - - // Step 3: Run deployment scripts after setting storage variables - - _setUpDependencies(useLiveContracts); + _setUpDependencies(); _setUpAllocationSystem(); _setUpALMController(); - _setUpBasePSM(); _setUpBaseALMController(); _setBaseMintRecipient(); - if (!useLiveContracts) _transferOwnershipOfMocks(); - - ScriptTools.exportContract(mainnet.name, "admin", deployer); - ScriptTools.exportContract(base.name, "admin", deployer); + ScriptTools.exportContract(mainnet.nameDeps, "admin", deployer); + ScriptTools.exportContract(base.nameDeps, "admin", deployer); } } diff --git a/script/staging/mocks/MockDaiUsds.sol b/script/staging/mocks/MockDaiUsds.sol deleted file mode 100644 index 6e64f00..0000000 --- a/script/staging/mocks/MockDaiUsds.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { IERC20 } from "forge-std/interfaces/IERC20.sol"; - -import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol"; - -contract MockDaiUsds is Ownable { - - IERC20 public immutable dai; - IERC20 public immutable usds; - - constructor(address owner_, address dai_, address usds_) Ownable(owner_) { - dai = IERC20(dai_); - usds = IERC20(usds_); - } - - function daiToUsds(address usr, uint256 wad) external onlyOwner { - dai.transferFrom(usr, address(this), wad); - usds.transfer(usr, wad); - } - - function usdsToDai(address usr, uint256 wad) external onlyOwner { - usds.transferFrom(usr, address(this), wad); - dai.transfer(usr, wad); - } -} diff --git a/script/staging/mocks/MockPSM.sol b/script/staging/mocks/MockPSM.sol deleted file mode 100644 index c8a3fe7..0000000 --- a/script/staging/mocks/MockPSM.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { IERC20 } from "forge-std/interfaces/IERC20.sol"; - -import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol"; - -contract MockPSM is Ownable { - - IERC20 public immutable gem; - IERC20 public immutable dai; - - constructor(address owner_, address gem_, address dai_) Ownable(owner_) { - gem = IERC20(gem_); - dai = IERC20(dai_); - } - - function buyGemNoFee(address usr, uint256 usdcAmount) external onlyOwner returns (uint256 daiAmount) { - daiAmount = usdcAmount * 1e12; - - dai.transferFrom(usr, address(this), daiAmount); - gem.transfer(usr, usdcAmount); - } - - function sellGemNoFee(address usr, uint256 usdcAmount) external onlyOwner returns (uint256 daiAmount) { - daiAmount = usdcAmount * 1e12; - - gem.transferFrom(usr, address(this), usdcAmount); - dai.transfer(usr, daiAmount); - } - - function pocket() external view returns(address) { - return address(this); - } - - function to18ConversionFactor() external pure returns (uint256) { - return 1e12; - } - - function fill() external { - } - -} diff --git a/script/staging/mocks/MockRateProvider.sol b/script/staging/mocks/MockRateProvider.sol deleted file mode 100644 index 7eaf424..0000000 --- a/script/staging/mocks/MockRateProvider.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -contract MockRateProvider { - - function getConversionRate() external pure returns (uint256) { - return 1.2e27; - } - -} diff --git a/script/staging/mocks/MockSUsds.sol b/script/staging/mocks/MockSUsds.sol deleted file mode 100644 index 81b5f9e..0000000 --- a/script/staging/mocks/MockSUsds.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { MockERC20 } from "erc20-helpers/MockERC20.sol"; - -contract MockSUsds is MockERC20 { - - address public usds; - - constructor(address usds_) MockERC20("sUSDS", "sUSDS", 18) { - usds = usds_; - } - -} diff --git a/test/base-fork/DeployAndInit.t.sol b/test/base-fork/DeployAndInit.t.sol index 6ce4fb8..776c768 100644 --- a/test/base-fork/DeployAndInit.t.sol +++ b/test/base-fork/DeployAndInit.t.sol @@ -868,7 +868,7 @@ contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAn assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); } - function _assertDepositRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal { + function _assertDepositRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal view { bytes32 assetKey = RateLimitHelpers.makeAssetKey( foreignController.LIMIT_PSM_DEPOSIT(), address(asset) @@ -877,7 +877,7 @@ contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAn _assertRateLimitData(assetKey, expectedData); } - function _assertWithdrawRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal { + function _assertWithdrawRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal view { bytes32 assetKey = RateLimitHelpers.makeAssetKey( foreignController.LIMIT_PSM_WITHDRAW(), address(asset) @@ -886,7 +886,7 @@ contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAn _assertRateLimitData(assetKey, expectedData); } - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal { + function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal view { IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); assertEq(data.maxAmount, expectedData.maxAmount); diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/DeployAndInit.t.sol index 14493a9..28c4344 100644 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ b/test/mainnet-fork/DeployAndInit.t.sol @@ -708,7 +708,7 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); } - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal { + function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal view { IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); assertEq(data.maxAmount, expectedData.maxAmount); From 29d2fb379272c426306bd046d7be1093125afaf8 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 11 Dec 2024 14:45:17 -0500 Subject: [PATCH 11/22] feat: Use return value from cooldown (SC-864) (#69) * fix: update to use cooldown * feat: update test * test: add return val assertions --------- Co-authored-by: Lucas --- src/MainnetController.sol | 23 +++++++++++++---------- test/mainnet-fork/Ethena.t.sol | 16 +++++++++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/MainnetController.sol b/src/MainnetController.sol index 9f355b0..e19dd97 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -403,19 +403,22 @@ contract MainnetController is AccessControl { ); } - function cooldownSharesSUSDe(uint256 susdeAmount) + // NOTE: !!! Rate limited at end of function !!! + function cooldownSharesSUSDe(uint256 susdeAmount) external - onlyRole(RELAYER) - isActive - rateLimited( - LIMIT_SUSDE_COOLDOWN, - susde.convertToAssets(susdeAmount) - ) + onlyRole(RELAYER) + isActive + returns (uint256 cooldownAmount) { - proxy.doCall( - address(susde), - abi.encodeCall(susde.cooldownShares, (susdeAmount)) + cooldownAmount = abi.decode( + proxy.doCall( + address(susde), + abi.encodeCall(susde.cooldownShares, (susdeAmount)) + ), + (uint256) ); + + rateLimits.triggerRateLimitDecrease(LIMIT_SUSDE_COOLDOWN, cooldownAmount); } function unstakeSUSDe() external onlyRole(RELAYER) isActive { diff --git a/test/mainnet-fork/Ethena.t.sol b/test/mainnet-fork/Ethena.t.sol index 69419aa..c60e7c9 100644 --- a/test/mainnet-fork/Ethena.t.sol +++ b/test/mainnet-fork/Ethena.t.sol @@ -401,8 +401,8 @@ contract MainnetControllerCooldownSharesSUSDeFailureTests is ForkTestBase { uint256 boundaryShares = susde.convertToShares(100e18 + 1); // Demonstrate how rounding works - assertEq(susde.convertToAssets(overBoundaryShares), 100e18 + 1); - assertEq(susde.convertToAssets(boundaryShares), 100e18); + assertEq(susde.previewRedeem(overBoundaryShares), 100e18 + 1); + assertEq(susde.previewRedeem(boundaryShares), 100e18); vm.prank(relayer); vm.expectRevert("RateLimits/rate-limit-exceeded"); @@ -450,7 +450,9 @@ contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { vm.prank(relayer); vm.expectEmit(address(susde)); emit Withdraw(address(almProxy), silo, address(almProxy), assets, 100e18); - mainnetController.cooldownSharesSUSDe(100e18); + uint256 returnedAssets = mainnetController.cooldownSharesSUSDe(100e18); + + assertEq(returnedAssets, assets); assertEq(susde.balanceOf(address(almProxy)), 0); assertEq(usde.balanceOf(silo), startingSiloBalance + assets); @@ -463,10 +465,12 @@ contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18); vm.prank(relayer); - mainnetController.cooldownSharesSUSDe(4_000_000e18); + uint256 returnedAssets = mainnetController.cooldownSharesSUSDe(4_000_000e18); uint256 assets1 = susde.convertToAssets(4_000_000e18); + assertEq(returnedAssets, assets1); + assertGe(assets1, 4_000_000e18); assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1); @@ -476,10 +480,12 @@ contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1 + (1_000_000e18 - 6400)); // Rounding vm.prank(relayer); - mainnetController.cooldownSharesSUSDe(600_000e18); + returnedAssets = mainnetController.cooldownSharesSUSDe(600_000e18); uint256 assets2 = susde.convertToAssets(600_000e18); + assertEq(returnedAssets, assets2); + assertGe(assets2, 600_000e18); assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1 + (1_000_000e18 - 6400) - assets2); From 0d4e899f8d4b3bd3ba235bd72a934d88b47cad03 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Thu, 12 Dec 2024 11:03:20 -0500 Subject: [PATCH 12/22] feat: Add trust assumptions, new functionality to README (SC-863) (#70) * feat: add to readme * fix: add usdc note * fix: update to remove protocol-specific trust assumptions * fix: add bolding * remove the specifics --------- Co-authored-by: Lucas Co-authored-by: Sam MacPherson --- README.md | 26 ++++++++++++++++++++++++-- src/MainnetController.sol | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d33e5ab..759c16e 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,14 @@ All functions below change the balance of funds in the ALMProxy contract and are - `ForeignController`: This contract currently implements logic to: - Deposit and withdraw on EVM compliant L2 PSM3 contracts (see [spark-psm](https://github.com/marsfoundation/spark-psm) for implementation). - Initiate a transfer of USDC to other domains using CCTP. + - Deposit, withdraw, and redeem from ERC4626 contracts. + - Deposit and withdraw from AAVE. - `MainnetController`: This contract currently implements logic to: - - Mint and burn USDS on Ethereum mainnet. - - Deposit, withdraw, redeem in the sUSDS contract. + - Mint and burn USDS. + - Deposit, withdraw, redeem from ERC4626 contracts. + - Deposit and withdraw from AAVE. + - Mint and burn USDe. + - Cooldown and unstake from sUSDe. - Swap USDS to USDC and vice versa using the mainnet PSM. - Transfer USDC to other domains using CCTP. @@ -68,6 +73,23 @@ The rate limit is calculated as follows: This is a linear rate limit that increases over time with a maximum limit. This rate limit is derived from these values which can be set by and admin OR updated by the `CONTROLLER` role. The `CONTROLLER` updates these values to increase/decrease the rate limit based on the functionality within the contract (e.g., decrease the rate limit after minting USDS by the minted amount by decrementing `lastAmount` and setting `lastUpdated` to `block.timestamp`). +## Trust Assumptions and Attack Mitigation +Below are all stated trust assumptions for using this contract in production: +- The `DEFAULT_ADMIN_ROLE` is fully trusted, to be run by governance. +- The `RELAYER` role is assumed to be able to be fully compromised by a malicious actor. **This should be a major consideration during auditing engagements.** + - The logic in the smart contracts must prevent the movement of value anywhere outside of the ALM system of contracts. + - Any action must be limited to "reasonable" slippage/losses/opportunity cost by rate limits. + - The `FREEZER` must be able to stop the compromised `RELAYER` from performing more harmful actions within the max rate limits by using the `freeze()` function. +- A compromised `RELAYER` can DOS Ethena unstaking, but this can be mitigated by freezing the Controller and reassigning the `RELAYER`. This is outlined in a test `test_compromisedRelayer_lockingFundsInEthenaSilo`. + +## Operational Requirements +- All ERC-4626 vaults that are onboarded MUST have an initial burned shares amount that prevents rounding-based frontrunning attacks. These shares have to be unrecoverable so that they cannot be removed at a later date. +- All ERC-20 tokens are to be non-rebasing with sufficiently high decimal precision. +- Rate limits must be configured for specific ERC-4626 vaults and AAVE aTokens (vaults without rate limits set will revert). Unlimited rate limits can be used as an onboarding tool. +- Rate limits must take into account: + - Risk tolerance for a given protocol + - Griefing attacks (e.g., repetitive transactions with high slippage by malicious relayer). + ## Testing To run all tests, run the following command: diff --git a/src/MainnetController.sol b/src/MainnetController.sol index e19dd97..6414da8 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -375,7 +375,7 @@ contract MainnetController is AccessControl { ); } - // Note that 2m per block includes other users + // Note that Ethena's mint/redeem per-block limits include other users function prepareUSDeMint(uint256 usdcAmount) external onlyRole(RELAYER) isActive rateLimited(LIMIT_USDE_MINT, usdcAmount) { From 0edbf2bd5ff82ca3b719033dbaef74d470a9260a Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Thu, 12 Dec 2024 11:07:21 -0500 Subject: [PATCH 13/22] feat: Add infinite withdraw test coverage for AAVE (SC-862) (#68) * feat: add infinite withdraw test coverage for aave * remove double paren --------- Co-authored-by: Lucas Co-authored-by: Sam MacPherson --- test/base-fork/Aave.t.sol | 46 ++++++++++++++++++ test/mainnet-fork/Aave.t.sol | 91 ++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/test/base-fork/Aave.t.sol b/test/base-fork/Aave.t.sol index d13d1e4..26765ae 100644 --- a/test/base-fork/Aave.t.sol +++ b/test/base-fork/Aave.t.sol @@ -170,6 +170,11 @@ contract AaveV3BaseMarketWithdrawFailureTests is AaveV3BaseMarketTestBase { contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { function test_withdrawAave_usdc() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ); + // NOTE: Using lower amount to not hit rate limit deal(Base.USDC, address(almProxy), 500_000e6); vm.prank(relayer); @@ -185,6 +190,8 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { assertEq(usdcBase.balanceOf(address(almProxy)), 0); assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6); + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6); + // Partial withdraw vm.prank(relayer); assertEq(foreignController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); @@ -193,6 +200,8 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { assertEq(usdcBase.balanceOf(address(almProxy)), 400_000e6); assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 100_000e6); // 500k - 400k + assertEq(rateLimits.getCurrentRateLimit(key), 600_000e6); + // Withdraw all vm.prank(relayer); assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); @@ -201,8 +210,45 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance); assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6 - fullBalance); + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6 - fullBalance); + // Interest accrued was withdrawn, reducing cash balance assertLe(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance); } + function test_withdrawAave_usdc_unlimitedRateLimit() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ); + vm.prank(Base.SPARK_EXECUTOR); + rateLimits.setUnlimitedRateLimitData(key); + + deal(Base.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + foreignController.depositAave(ATOKEN_USDC, 1_000_000e6); + + skip(1 days); + + uint256 fullBalance = ausdc.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e6); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdcBase.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + + // Partial withdraw + vm.prank(relayer); + assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); // No change + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + } + } diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol index e140be7..c15d2ff 100644 --- a/test/mainnet-fork/Aave.t.sol +++ b/test/mainnet-fork/Aave.t.sol @@ -229,6 +229,11 @@ contract AaveV3MainMarketWithdrawFailureTests is AaveV3MainMarketBaseTest { contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { function test_withdrawAave_usds() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDS + ); + deal(Ethereum.USDS, address(almProxy), 1_000_000e18); vm.prank(relayer); mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); @@ -243,6 +248,8 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertEq(usds.balanceOf(address(almProxy)), 0); assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18); + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e18); + // Partial withdraw vm.prank(relayer); assertEq(mainnetController.withdrawAave(ATOKEN_USDS, 400_000e18), 400_000e18); @@ -251,10 +258,14 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertEq(usds.balanceOf(address(almProxy)), 400_000e18); assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 600_000e18); // 1m - 400k + assertEq(rateLimits.getCurrentRateLimit(key), 9_600_000e18); + // Withdraw all vm.prank(relayer); assertEq(mainnetController.withdrawAave(ATOKEN_USDS, type(uint256).max), fullBalance - 400_000e18); + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e18 - fullBalance); + assertEq(ausds.balanceOf(address(almProxy)), 0); assertEq(usds.balanceOf(address(almProxy)), fullBalance); assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18 - fullBalance); @@ -263,7 +274,46 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertLe(usds.balanceOf(address(ausds)), startingAUSDSBalance); } + function test_withdrawAave_usds_unlimitedRateLimit() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDS + ); + vm.prank(Ethereum.SPARK_PROXY); + rateLimits.setUnlimitedRateLimitData(key); + + deal(Ethereum.USDS, address(almProxy), 1_000_000e18); + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDS, 1_000_000e18); + + skip(1 days); + + uint256 fullBalance = ausds.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e18); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); + + assertEq(ausds.balanceOf(address(almProxy)), fullBalance); + assertEq(usds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18); + + // Partial withdraw + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDS, type(uint256).max), fullBalance); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); // No change + + assertEq(ausds.balanceOf(address(almProxy)), 0); + assertEq(usds.balanceOf(address(almProxy)), fullBalance); + assertEq(usds.balanceOf(address(ausds)), startingAUSDSBalance + 1_000_000e18 - fullBalance); + } + function test_withdrawAave_usdc() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ); deal(Ethereum.USDC, address(almProxy), 1_000_000e6); vm.prank(relayer); mainnetController.depositAave(ATOKEN_USDC, 1_000_000e6); @@ -278,6 +328,8 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertEq(usdc.balanceOf(address(almProxy)), 0); assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6); + // Partial withdraw vm.prank(relayer); assertEq(mainnetController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); @@ -286,6 +338,8 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertEq(usdc.balanceOf(address(almProxy)), 400_000e6); assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 600_000e6); // 1m - 400k + assertEq(rateLimits.getCurrentRateLimit(key), 9_600_000e6); + // Withdraw all vm.prank(relayer); assertEq(mainnetController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); @@ -294,8 +348,45 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { assertEq(usdc.balanceOf(address(almProxy)), fullBalance); assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6 - fullBalance); + // Interest accrued was withdrawn, reducing cash balance assertLe(usdc.balanceOf(address(ausdc)), startingAUSDCBalance); } + function test_withdrawAave_usdc_unlimitedRateLimit() public { + bytes32 key = RateLimitHelpers.makeAssetKey( + mainnetController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ); + vm.prank(Ethereum.SPARK_PROXY); + rateLimits.setUnlimitedRateLimitData(key); + + deal(Ethereum.USDC, address(almProxy), 1_000_000e6); + vm.prank(relayer); + mainnetController.depositAave(ATOKEN_USDC, 1_000_000e6); + + skip(1 days); + + uint256 fullBalance = ausdc.balanceOf(address(almProxy)); + + assertGe(fullBalance, 1_000_000e6); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6); + + // Partial withdraw + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance); + + assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); // No change + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + assertEq(usdc.balanceOf(address(almProxy)), fullBalance); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + } + } From 2bb2680893aa3e42210c8f907ec4d5778ace9fe6 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 13 Dec 2024 09:35:27 -0500 Subject: [PATCH 14/22] add ethena minter 50bps limit (#71) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 759c16e..d4e1a56 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ Below are all stated trust assumptions for using this contract in production: - Any action must be limited to "reasonable" slippage/losses/opportunity cost by rate limits. - The `FREEZER` must be able to stop the compromised `RELAYER` from performing more harmful actions within the max rate limits by using the `freeze()` function. - A compromised `RELAYER` can DOS Ethena unstaking, but this can be mitigated by freezing the Controller and reassigning the `RELAYER`. This is outlined in a test `test_compromisedRelayer_lockingFundsInEthenaSilo`. +- Ethena USDe Mint/Burn is trusted to not honor requests with over 50bps slippage from a delegated signer. ## Operational Requirements - All ERC-4626 vaults that are onboarded MUST have an initial burned shares amount that prevents rounding-based frontrunning attacks. These shares have to be unrecoverable so that they cannot be removed at a later date. From 561092fd109a9709d6c13f2951c4edb88a0d0765 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Tue, 17 Dec 2024 11:27:24 -0500 Subject: [PATCH 15/22] test: Refactor to use getBlock (SC-887) (#72) * test: refactor to use getBlock * test: fupdate aave blocks --------- Co-authored-by: Lucas Manuel --- foundry.toml | 2 +- test/base-fork/Aave.t.sol | 14 +++-- test/base-fork/ForkTestBase.t.sol | 1 + test/mainnet-fork/Aave.t.sol | 14 +++-- test/mainnet-fork/Ethena.t.sol | 87 +++++++++++++++------------- test/mainnet-fork/ForkTestBase.t.sol | 7 ++- 6 files changed, 73 insertions(+), 52 deletions(-) diff --git a/foundry.toml b/foundry.toml index 810c90a..4a3974d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ["lib"] solc_version = '0.8.21' optimizer = true optimizer_runs = 200 -assertions_revert = false +# assertions_revert = false remappings = [ "dss-allocator/=lib/dss-allocator/", "dss-test/=lib/dss-test/src/", diff --git a/test/base-fork/Aave.t.sol b/test/base-fork/Aave.t.sol index 26765ae..b8cfc9e 100644 --- a/test/base-fork/Aave.t.sol +++ b/test/base-fork/Aave.t.sol @@ -44,6 +44,10 @@ contract AaveV3BaseMarketTestBase is ForkTestBase { startingAUSDCBalance = usdcBase.balanceOf(address(ausdc)); } + function _getBlock() internal pure override returns (uint256) { + return 22841965; // November 24, 2024 + } + } contract AaveV3BaseMarketDepositFailureTests is AaveV3BaseMarketTestBase { @@ -196,7 +200,7 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { vm.prank(relayer); assertEq(foreignController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); - assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6); + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6 - 1); assertEq(usdcBase.balanceOf(address(almProxy)), 400_000e6); assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 100_000e6); // 500k - 400k @@ -204,13 +208,13 @@ contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase { // Withdraw all vm.prank(relayer); - assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); + assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6 - 1); assertEq(ausdc.balanceOf(address(almProxy)), 0); - assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance); - assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6 - fullBalance); + assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance - 1); + assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6 - fullBalance + 1); - assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6 - fullBalance); + assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6 - fullBalance + 1); // Interest accrued was withdrawn, reducing cash balance assertLe(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance); diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 16c555a..4ec6caf 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -167,6 +167,7 @@ contract ForkTestBase is Test { vm.stopPrank(); } + // Default configuration for the fork, can be overridden in inheriting tests function _getBlock() internal virtual pure returns (uint256) { return 20782500; // October 8, 2024 } diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol index c15d2ff..e369d66 100644 --- a/test/mainnet-fork/Aave.t.sol +++ b/test/mainnet-fork/Aave.t.sol @@ -61,6 +61,10 @@ contract AaveV3MainMarketBaseTest is ForkTestBase { startingAUSDSBalance = usds.balanceOf(address(ausds)); } + function _getBlock() internal pure override returns (uint256) { + return 21417200; // Dec 16, 2024 + } + } // NOTE: Only testing USDS for non-rate limit failures as it doesn't matter which asset is used @@ -334,7 +338,7 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { vm.prank(relayer); assertEq(mainnetController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6); - assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6); + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6 + 1); // Rounding assertEq(usdc.balanceOf(address(almProxy)), 400_000e6); assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 600_000e6); // 1m - 400k @@ -342,13 +346,13 @@ contract AaveV3MainMarketWithdrawSuccessTests is AaveV3MainMarketBaseTest { // Withdraw all vm.prank(relayer); - assertEq(mainnetController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6); + assertEq(mainnetController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6 + 1); // Rounding assertEq(ausdc.balanceOf(address(almProxy)), 0); - assertEq(usdc.balanceOf(address(almProxy)), fullBalance); - assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance); + assertEq(usdc.balanceOf(address(almProxy)), fullBalance + 1); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance - 1); // Rounding - assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6 - fullBalance); + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6 - fullBalance - 1); // Rounding // Interest accrued was withdrawn, reducing cash balance assertLe(usdc.balanceOf(address(ausdc)), startingAUSDCBalance); diff --git a/test/mainnet-fork/Ethena.t.sol b/test/mainnet-fork/Ethena.t.sol index c60e7c9..b5fbedc 100644 --- a/test/mainnet-fork/Ethena.t.sol +++ b/test/mainnet-fork/Ethena.t.sol @@ -7,7 +7,14 @@ interface IEthenaMinterLike { function delegatedSigner(address signer, address owner) external view returns (uint8); } -contract MainnetControllerSetDelegatedSignerFailureTests is ForkTestBase { +contract EthenaTestBase is ForkTestBase { + + function _getBlock() internal pure override returns (uint256) { + return 21417200; // Dec 16, 2024 + } +} + +contract MainnetControllerSetDelegatedSignerFailureTests is EthenaTestBase { function test_setDelegatedSigner_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -29,7 +36,7 @@ contract MainnetControllerSetDelegatedSignerFailureTests is ForkTestBase { } -contract MainnetControllerSetDelegatedSignerSuccessTests is ForkTestBase { +contract MainnetControllerSetDelegatedSignerSuccessTests is EthenaTestBase { event DelegatedSignerInitiated(address indexed delegateTo, address indexed initiatedBy); @@ -50,7 +57,7 @@ contract MainnetControllerSetDelegatedSignerSuccessTests is ForkTestBase { } -contract MainnetControllerRemoveDelegatedSignerFailureTests is ForkTestBase { +contract MainnetControllerRemoveDelegatedSignerFailureTests is EthenaTestBase { function test_removeDelegatedSigner_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -72,7 +79,7 @@ contract MainnetControllerRemoveDelegatedSignerFailureTests is ForkTestBase { } -contract MainnetControllerRemoveDelegatedSignerSuccessTests is ForkTestBase { +contract MainnetControllerRemoveDelegatedSignerSuccessTests is EthenaTestBase { event DelegatedSignerRemoved(address indexed removedSigner, address indexed initiatedBy); @@ -96,7 +103,7 @@ contract MainnetControllerRemoveDelegatedSignerSuccessTests is ForkTestBase { } -contract MainnetControllerPrepareUSDeMintFailureTests is ForkTestBase { +contract MainnetControllerPrepareUSDeMintFailureTests is EthenaTestBase { function test_prepareUSDeMint_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -135,7 +142,7 @@ contract MainnetControllerPrepareUSDeMintFailureTests is ForkTestBase { } -contract MainnetControllerPrepareUSDeMintSuccessTests is ForkTestBase { +contract MainnetControllerPrepareUSDeMintSuccessTests is EthenaTestBase { bytes32 key; @@ -177,7 +184,7 @@ contract MainnetControllerPrepareUSDeMintSuccessTests is ForkTestBase { } -contract MainnetControllerPrepareUSDeBurnFailureTests is ForkTestBase { +contract MainnetControllerPrepareUSDeBurnFailureTests is EthenaTestBase { function test_prepareUSDeBurn_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -216,7 +223,7 @@ contract MainnetControllerPrepareUSDeBurnFailureTests is ForkTestBase { } -contract MainnetControllerPrepareUSDeBurnSuccessTests is ForkTestBase { +contract MainnetControllerPrepareUSDeBurnSuccessTests is EthenaTestBase { bytes32 key; @@ -258,7 +265,7 @@ contract MainnetControllerPrepareUSDeBurnSuccessTests is ForkTestBase { } -contract MainnetControllerCooldownAssetsSUSDeFailureTests is ForkTestBase { +contract MainnetControllerCooldownAssetsSUSDeFailureTests is EthenaTestBase { function test_cooldownAssetsSUSDe_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -300,7 +307,7 @@ contract MainnetControllerCooldownAssetsSUSDeFailureTests is ForkTestBase { } -contract MainnetControllerCooldownAssetsSUSDeSuccessTests is ForkTestBase { +contract MainnetControllerCooldownAssetsSUSDeSuccessTests is EthenaTestBase { event Withdraw( address indexed sender, @@ -366,7 +373,7 @@ contract MainnetControllerCooldownAssetsSUSDeSuccessTests is ForkTestBase { } -contract MainnetControllerCooldownSharesSUSDeFailureTests is ForkTestBase { +contract MainnetControllerCooldownSharesSUSDeFailureTests is EthenaTestBase { function test_cooldownSharesSUSDe_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -414,7 +421,7 @@ contract MainnetControllerCooldownSharesSUSDeFailureTests is ForkTestBase { } -contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { +contract MainnetControllerCooldownSharesSUSDeSuccessTests is EthenaTestBase { event Withdraw( address indexed sender, @@ -493,7 +500,7 @@ contract MainnetControllerCooldownSharesSUSDeSuccessTests is ForkTestBase { } -contract MainnetControllerUnstakeSUSDeFailureTests is ForkTestBase { +contract MainnetControllerUnstakeSUSDeFailureTests is EthenaTestBase { function test_unstakeSUSDe_notRelayer() external { vm.expectRevert(abi.encodeWithSignature( @@ -542,7 +549,7 @@ contract MainnetControllerUnstakeSUSDeFailureTests is ForkTestBase { } -contract MainnetControllerUnstakeSUSDeSuccessTests is ForkTestBase { +contract MainnetControllerUnstakeSUSDeSuccessTests is EthenaTestBase { function test_unstakeSUSDe() external { // Setting higher rate limit so shares can be used for cooldown @@ -579,7 +586,7 @@ contract MainnetControllerUnstakeSUSDeSuccessTests is ForkTestBase { } -contract MainnetControllerEthenaE2ETests is ForkTestBase { +contract MainnetControllerEthenaE2ETests is EthenaTestBase { address signer = makeAddr("signer"); @@ -674,7 +681,7 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { assertEq(usde.allowance(address(almProxy), address(susde)), 0); - assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 1); // Rounding assertEq(usde.balanceOf(address(susde)), startingAssets + 500_000e18); assertEq(usde.balanceOf(address(almProxy)), 500_000e18); @@ -685,33 +692,33 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { uint256 startingSiloBalance = usde.balanceOf(silo); - assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 1); // Rounding assertEq(usde.balanceOf(silo), startingSiloBalance); assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 5_000_000e18); vm.prank(relayer); - mainnetController.cooldownAssetsSUSDe(500_000e18 - 2); + mainnetController.cooldownAssetsSUSDe(500_000e18 - 1); - assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 2); + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 1); assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); - assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 1); // Step 4: Wait for cooldown window to pass then unstake sUSDe skip(7 days); - assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 1); assertEq(usde.balanceOf(address(almProxy)), 500_000e18); vm.prank(relayer); mainnetController.unstakeSUSDe(); assertEq(usde.balanceOf(silo), startingSiloBalance); - assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 1); // Step 5: Redeem USDe for USDC @@ -720,23 +727,23 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { assertEq(rateLimits.getCurrentRateLimit(burnKey), 5_000_000e18); vm.prank(relayer); - mainnetController.prepareUSDeBurn(1_000_000e18 - 2); + mainnetController.prepareUSDeBurn(1_000_000e18 - 1); - assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 2); + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 1); - assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 2); + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 1); - assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 1); assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance); assertEq(usdc.balanceOf(address(almProxy)), 0); - _simulateUsdeBurn(1_000_000e18 - 2); + _simulateUsdeBurn(1_000_000e18 - 1); assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); assertEq(usde.balanceOf(address(almProxy)), 0); - assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 2); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 1); assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6 - 1); // Rounding } @@ -793,7 +800,7 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { assertEq(usde.allowance(address(almProxy), address(susde)), 0); - assertEq(susde.convertToAssets(susdeShares), 500_000e18 - 2); // Rounding + assertEq(susde.convertToAssets(susdeShares), 500_000e18 - 1); // Rounding assertEq(usde.balanceOf(address(susde)), startingAssets + 500_000e18); assertEq(usde.balanceOf(address(almProxy)), 500_000e18); @@ -804,7 +811,7 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { uint256 startingSiloBalance = usde.balanceOf(silo); - assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 2); // Rounding + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 500_000e18 - 1); // Rounding assertEq(usde.balanceOf(silo), startingSiloBalance); @@ -813,24 +820,24 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { vm.prank(relayer); mainnetController.cooldownSharesSUSDe(susdeShares); - assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 2); + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 1); assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); - assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 1); // Step 4: Wait for cooldown window to pass then unstake sUSDe skip(7 days); - assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 2); + assertEq(usde.balanceOf(silo), startingSiloBalance + 500_000e18 - 1); assertEq(usde.balanceOf(address(almProxy)), 500_000e18); vm.prank(relayer); mainnetController.unstakeSUSDe(); assertEq(usde.balanceOf(silo), startingSiloBalance); - assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 1); // Step 5: Redeem USDe for USDC @@ -839,23 +846,23 @@ contract MainnetControllerEthenaE2ETests is ForkTestBase { assertEq(rateLimits.getCurrentRateLimit(burnKey), 5_000_000e18); vm.prank(relayer); - mainnetController.prepareUSDeBurn(1_000_000e18 - 2); + mainnetController.prepareUSDeBurn(1_000_000e18 - 1); - assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 2); + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 1); - assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 2); + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 1); - assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 2); + assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 - 1); assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance); assertEq(usdc.balanceOf(address(almProxy)), 0); - _simulateUsdeBurn(1_000_000e18 - 2); + _simulateUsdeBurn(1_000_000e18 - 1); assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); assertEq(usde.balanceOf(address(almProxy)), 0); - assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 2); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 1); assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6 - 1); // Rounding } diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 3726a30..6caf971 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -155,7 +155,7 @@ contract ForkTestBase is DssTest { /*** Step 1: Set up environment, cast addresses ***/ - source = getChain("mainnet").createSelectFork(20917850); // October 7, 2024 + source = getChain("mainnet").createSelectFork(_getBlock()); dss = MCD.loadFromChainlog(LOG); @@ -282,4 +282,9 @@ contract ForkTestBase is DssTest { vm.label(vault, "vault"); } + // Default configuration for the fork, can be overridden in inheriting tests + function _getBlock() internal virtual pure returns (uint256) { + return 20917850; // October 7, 2024 + } + } From fed3b89bd1bad7feb4f4a17d8edf787d5af0d085 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 18 Dec 2024 15:49:26 -0500 Subject: [PATCH 16/22] test: Remove staging config tests, add e2e testing (SC-890) (#73) * test: refactor to get all test working * test: add coverage for aave, ethena, and susds * test: get full coverage * fix: update file name to show diff * fix: rm unneccesary bug fix --------- Co-authored-by: Lucas Manuel Co-authored-by: Lucas Manuel --- foundry.toml | 1 - script/staging/test/DeployEthereum.t.sol | 636 ++++++++++++++--------- 2 files changed, 383 insertions(+), 254 deletions(-) diff --git a/foundry.toml b/foundry.toml index 4a3974d..7c47e3b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,6 @@ libs = ["lib"] solc_version = '0.8.21' optimizer = true optimizer_runs = 200 -# assertions_revert = false remappings = [ "dss-allocator/=lib/dss-allocator/", "dss-test/=lib/dss-test/src/", diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol index 9de25de..54c4451 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/DeployEthereum.t.sol @@ -1,23 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { ScriptTools } from "dss-test/ScriptTools.sol"; + import "forge-std/Test.sol"; -import { ScriptTools } from "dss-test/ScriptTools.sol"; +import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; + +import { IMetaMorpho, Id } from "metamorpho/interfaces/IMetaMorpho.sol"; + +import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; +import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; + +import { Usds } from "usds/src/Usds.sol"; + +import { SUsds } from "sdai/src/SUsds.sol"; + +import { Base } from "spark-address-registry/src/Base.sol"; +import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; + +import { PSM3 } from "spark-psm/src/PSM3.sol"; import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { Usds } from "usds/src/Usds.sol"; -import { SUsds } from "sdai/src/SUsds.sol"; - -import { AllocatorVault } from "dss-allocator/src/AllocatorVault.sol"; -import { AllocatorBuffer } from "dss-allocator/src/AllocatorBuffer.sol"; -import { AllocatorRegistry } from "dss-allocator/src/AllocatorRegistry.sol"; -import { AllocatorRoles } from "dss-allocator/src/AllocatorRoles.sol"; - import { IRateLimits } from "../../../src/interfaces/IRateLimits.sol"; import { ALMProxy } from "../../../src/ALMProxy.sol"; @@ -26,30 +35,38 @@ import { MainnetController } from "../../../src/MainnetController.sol"; import { RateLimits } from "../../../src/RateLimits.sol"; import { RateLimitHelpers } from "../../../src/RateLimitHelpers.sol"; -import { PSM3, IERC20 } from "spark-psm/src/PSM3.sol"; -import { IRateProviderLike } from "spark-psm/src/interfaces/IRateProviderLike.sol"; +import { MainnetControllerDeploy } from "../../../deploy/ControllerDeploy.sol"; +import { MainnetControllerInit } from "../../../deploy/ControllerInit.sol"; interface IVatLike { function can(address, address) external view returns (uint256); } -contract DeployEthereumTest is Test { +contract StagingDeploymentTestBase is Test { using stdJson for *; using DomainHelpers for *; using CCTPBridgeTesting for *; using ScriptTools for *; + // AAVE aTokens for testing + address constant AUSDS = 0x32a6268f9Ba3642Dda7892aDd74f1D34469A4259; + address constant AUSDC = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c; + bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; + uint256 constant RELEASE_DATE = 20241210; + // Common variables address admin; // Configuration data - string inputMainnet; - string outputMainnet; string inputBase; + string inputMainnet; string outputBase; + string outputBaseDeps; + string outputMainnet; + string outputMainnetDeps; // Bridging Domain mainnet; @@ -63,12 +80,8 @@ contract DeployEthereumTest is Test { IERC20 usdc; IERC20 dai; - AllocatorVault vault; - AllocatorBuffer buffer; - AllocatorRegistry registry; - AllocatorRoles roles; - - address safeMainnet; + address vault; + address relayerSafe; address usdsJoin; ALMProxy almProxy; @@ -77,7 +90,7 @@ contract DeployEthereumTest is Test { // Base contracts - address safeBase; + address relayerSafeBase; PSM3 psmBase; @@ -85,15 +98,15 @@ contract DeployEthereumTest is Test { IERC20 susdsBase; IERC20 usdcBase; - ALMProxy foreignAlmProxy; - ForeignController foreignController; - RateLimits foreignRateLimits; + ALMProxy baseAlmProxy; + ForeignController baseController; + RateLimits baseRateLimits; /**********************************************************************************************/ /**** Setup ***/ /**********************************************************************************************/ - function setUp() public { + function setUp() public virtual { vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); // Domains and bridge @@ -102,262 +115,305 @@ contract DeployEthereumTest is Test { cctpBridge = CCTPBridgeTesting.createCircleBridge(mainnet, base); // JSON data - inputMainnet = ScriptTools.readInput("mainnet"); - outputMainnet = ScriptTools.readOutput("mainnet-release", 20241022); - inputBase = ScriptTools.readInput("base"); - outputBase = ScriptTools.readOutput("base-release", 20241022); + inputBase = ScriptTools.readInput("base-staging"); + inputMainnet = ScriptTools.readInput("mainnet-staging"); + + outputBase = ScriptTools.readOutput("base-staging-release", RELEASE_DATE); + outputBaseDeps = ScriptTools.readOutput("base-staging-deps-release", RELEASE_DATE); + outputMainnet = ScriptTools.readOutput("mainnet-staging-release", RELEASE_DATE); + outputMainnetDeps = ScriptTools.readOutput("mainnet-staging-deps-release", RELEASE_DATE); // Roles - admin = outputMainnet.readAddress(".admin"); - safeMainnet = outputMainnet.readAddress(".safe"); + admin = outputMainnetDeps.readAddress(".admin"); + relayerSafe = outputMainnetDeps.readAddress(".relayer"); // Tokens - usds = Usds(outputMainnet.readAddress(".usds")); - susds = SUsds(outputMainnet.readAddress(".susds")); - usdc = IERC20(inputMainnet.readAddress(".usdc")); - dai = IERC20(inputMainnet.readAddress(".dai")); - - // Allocation system and MCD - buffer = AllocatorBuffer(outputMainnet.readAddress(".allocatorBuffer")); - registry = AllocatorRegistry(outputMainnet.readAddress(".allocatorRegistry")); - roles = AllocatorRoles(outputMainnet.readAddress(".allocatorRoles")); - vault = AllocatorVault(outputMainnet.readAddress(".allocatorVault")); - usdsJoin = outputMainnet.readAddress(".usdsJoin"); + usds = Usds(outputMainnetDeps.readAddress(".usds")); + susds = SUsds(outputMainnetDeps.readAddress(".susds")); + usdc = IERC20(outputMainnetDeps.readAddress(".usdc")); + dai = IERC20(outputMainnetDeps.readAddress(".dai")); + + // Dependencies + vault = outputMainnetDeps.readAddress(".allocatorVault"); + usdsJoin = outputMainnetDeps.readAddress(".usdsJoin"); // ALM system almProxy = ALMProxy(payable(outputMainnet.readAddress(".almProxy"))); - mainnetController = MainnetController(outputMainnet.readAddress(".controller")); rateLimits = RateLimits(outputMainnet.readAddress(".rateLimits")); + mainnetController = _reconfigureMainnetController(); // Base roles - safeBase = outputBase.readAddress(".safe"); + relayerSafeBase = outputBaseDeps.readAddress(".relayer"); // Base tokens - usdsBase = IERC20(outputBase.readAddress(".usds")); - susdsBase = IERC20(outputBase.readAddress(".susds")); - usdcBase = IERC20(outputBase.readAddress(".usdc")); + usdsBase = IERC20(inputBase.readAddress(".usds")); + susdsBase = IERC20(inputBase.readAddress(".susds")); + usdcBase = IERC20(inputBase.readAddress(".usdc")); // Base ALM system - foreignAlmProxy = ALMProxy(payable(outputBase.readAddress(".almProxy"))); - foreignController = ForeignController(outputBase.readAddress(".controller")); - foreignRateLimits = RateLimits(outputBase.readAddress(".rateLimits")); + baseAlmProxy = ALMProxy(payable(outputBase.readAddress(".almProxy"))); + baseController = ForeignController(outputBase.readAddress(".controller")); + baseRateLimits = RateLimits(outputBase.readAddress(".rateLimits")); // Base PSM - psmBase = PSM3(outputBase.readAddress(".psm")); + psmBase = PSM3(inputBase.readAddress(".psm")); mainnet.selectFork(); deal(address(usds), address(usdsJoin), 1000e18); // Ensure there is enough balance } - /**********************************************************************************************/ - /**** Tests ***/ - /**********************************************************************************************/ + // TODO: Remove this once a deployment has been done on mainnet + function _reconfigureMainnetController() internal returns (MainnetController newController) { + newController = MainnetController(MainnetControllerDeploy.deployController({ + admin : admin, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + vault : address(vault), + psm : inputMainnet.readAddress(".psm"), + daiUsds : inputMainnet.readAddress(".daiUsds"), + cctp : inputMainnet.readAddress(".cctpTokenMessenger") + })); - function skip_test_mainnetConfiguration() public { - mainnet.selectFork(); + vm.startPrank(admin); - // Mainnet controller initialization - - assertEq(address(mainnetController.proxy()), outputMainnet.readAddress(".almProxy")); - assertEq(address(mainnetController.rateLimits()), outputMainnet.readAddress(".rateLimits")); - assertEq(address(mainnetController.vault()), outputMainnet.readAddress(".allocatorVault")); - assertEq(address(mainnetController.buffer()), outputMainnet.readAddress(".allocatorBuffer")); - assertEq(address(mainnetController.psm()), outputMainnet.readAddress(".psm")); - assertEq(address(mainnetController.daiUsds()), outputMainnet.readAddress(".daiUsds")); - assertEq(address(mainnetController.cctp()), inputMainnet.readAddress(".cctpTokenMessenger")); - assertEq(address(mainnetController.dai()), outputMainnet.readAddress(".dai")); - assertEq(address(mainnetController.usdc()), outputMainnet.readAddress(".usdc")); - assertEq(address(mainnetController.usds()), outputMainnet.readAddress(".usds")); - - assertEq(mainnetController.psmTo18ConversionFactor(), 1e12); - assertEq(mainnetController.active(), true); - - assertEq( - mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), - bytes32(uint256(uint160(outputBase.readAddress(".almProxy")))) - ); + newController.grantRole(newController.FREEZER(), inputMainnet.readAddress(".freezer")); + newController.grantRole(newController.RELAYER(), inputMainnet.readAddress(".relayer")); - // ALM system roles + almProxy.grantRole(almProxy.CONTROLLER(), address(newController)); + rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController)); - assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(mainnetController.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true); + almProxy.revokeRole(almProxy.CONTROLLER(), outputMainnet.readAddress(".controller")); + rateLimits.revokeRole(rateLimits.CONTROLLER(), outputMainnet.readAddress(".controller")); - assertEq(mainnetController.hasRole(mainnetController.FREEZER(), makeAddr("freezer")), true); - assertEq(mainnetController.hasRole(mainnetController.RELAYER(), safeMainnet), true); + newController.setMintRecipient( + CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, + bytes32(uint256(uint160(address(outputBase.readAddress(".almProxy"))))) + ); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); + // Set all rate limits + + bytes32[] memory rateLimitKeys = new bytes32[](10); + + rateLimitKeys[0] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_DEPOSIT(), AUSDS); + rateLimitKeys[1] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_DEPOSIT(), AUSDC); + rateLimitKeys[2] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDS); + rateLimitKeys[3] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDE); + rateLimitKeys[4] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_WITHDRAW(), AUSDS); + rateLimitKeys[5] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_WITHDRAW(), AUSDC); + rateLimitKeys[6] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_WITHDRAW(), Ethereum.SUSDS); + rateLimitKeys[7] = newController.LIMIT_USDE_MINT(); + rateLimitKeys[8] = newController.LIMIT_USDE_BURN(); + rateLimitKeys[9] = newController.LIMIT_SUSDE_COOLDOWN(); + + for (uint256 i; i < rateLimitKeys.length; i++) { + rateLimits.setUnlimitedRateLimitData(rateLimitKeys[i]); + } - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); + vm.stopPrank(); + } +} - // Allocation system deployment and initialization +contract MainnetStagingDeploymentTests is StagingDeploymentTestBase { - bytes32 ilk = ScriptTools.readInput("common").readString(".ilk").stringToBytes32(); + function test_mintUSDS() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); - assertEq(registry.buffers(ilk), address(buffer)); - assertEq(address(vault.jug()), outputMainnet.readAddress(".jug")); + vm.prank(relayerSafe); + mainnetController.mintUSDS(10e18); - assertEq(usds.allowance(address(buffer), address(almProxy)), type(uint256).max); - assertEq(usds.allowance(address(vault), usdsJoin), type(uint256).max); + assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); + } - assertEq(roles.ilkAdmins(ilk), admin); + function test_mintAndSwapToUSDC() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); - assertEq(buffer.wards(address(admin)), 1); - assertEq(registry.wards(address(admin)), 1); - assertEq(roles.wards(address(admin)), 1); - assertEq(vault.wards(address(admin)), 1); - assertEq(vault.wards(address(almProxy)), 1); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + vm.stopPrank(); - address vat = outputMainnet.readAddress(".vat"); + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); + } - assertEq(address(vault.roles()), address(roles)); - assertEq(address(vault.buffer()), address(buffer)); - assertEq(bytes32(vault.ilk()), ilk); - assertEq(address(vault.usdsJoin()), usdsJoin); - assertEq(address(vault.vat()), vat); + function test_depositAndWithdrawUsdsFromSUsds() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); - // NOTE: Not asserting vat.can because vat is mocked in this deployment and storage doesn't - // get updated on vat.hope in vault constructor + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.depositERC4626(Ethereum.SUSDS, 10e18); + skip(1 days); + mainnetController.withdrawERC4626(Ethereum.SUSDS, 10e18); + vm.stopPrank(); - // Starting token balances + assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); - assertEq(usds.balanceOf(address(almProxy)), 0); - assertEq(usdc.balanceOf(address(almProxy)), 0); - assertEq(susds.balanceOf(address(almProxy)), 0); + assertGe(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0); // Interest earned + } - uint256 usdsUnitSize = ScriptTools.readInput("common").readUint(".usdsUnitSize"); - uint256 usdcUnitSize = ScriptTools.readInput("common").readUint(".usdcUnitSize"); + function test_depositAndRedeemUsdsFromSUsds() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); - // USDS added to join, amount added to PSM wrapper to make `fill` logic work on swaps - assertEq(usds.balanceOf(address(usdsJoin)), usdsUnitSize * 1e18); - assertEq(dai.balanceOf(outputMainnet.readAddress(".psm")), usdsUnitSize * 1e18); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.depositERC4626(Ethereum.SUSDS, 10e18); + skip(1 days); + mainnetController.redeemERC4626(Ethereum.SUSDS, IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy))); + vm.stopPrank(); - // Rate limits + assertGe(usds.balanceOf(address(almProxy)), startingBalance + 10e18); // Interest earned - uint256 max6 = usdcUnitSize * 1e6 * 5; - uint256 max18 = usdcUnitSize * 1e18 * 5; - uint256 slope6 = usdcUnitSize * 1e6 / 4 hours; - uint256 slope18 = usdcUnitSize * 1e18 / 4 hours; + assertEq(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0); + } - bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( - mainnetController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE - ); + function test_depositAndWithdrawUsdsFromAave() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); - _assertRateLimitData(mainnetController.LIMIT_USDS_MINT(), max18, slope18); - _assertRateLimitData(mainnetController.LIMIT_USDS_TO_USDC(), max6, slope6); - _assertRateLimitData(domainKeyBase, max6, slope6); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.depositAave(AUSDS, 10e6); + skip(1 days); + mainnetController.withdrawAave(AUSDS, type(uint256).max); + vm.stopPrank(); - _assertRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), type(uint256).max, 0); + assertGe(usds.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned } - function skip_test_baseConfiguration() public { - base.selectFork(); - - // PSM configuration + function test_depositAndWithdrawUsdcFromAave() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); - assertEq(address(psmBase.usdc()), outputBase.readAddress(".usdc")); - assertEq(address(psmBase.usds()), outputBase.readAddress(".usds")); - assertEq(address(psmBase.susds()), outputBase.readAddress(".susds")); - assertEq(address(psmBase.pocket()), outputBase.readAddress(".psm")); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.depositAave(AUSDC, 10e6); + skip(1 days); + mainnetController.withdrawAave(AUSDC, type(uint256).max); + vm.stopPrank(); - assertGe(psmBase.totalAssets(), 1e18); - assertGe(psmBase.totalShares(), 1e18); + assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned + } - assertEq(IRateProviderLike(psmBase.rateProvider()).getConversionRate(), 1.2e27); + function test_mintDepositCooldownAssetsBurnUsde() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); - // Foreign controller initialization + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.prepareUSDeMint(10e6); + vm.stopPrank(); - assertEq(address(foreignController.proxy()), outputBase.readAddress(".almProxy")); - assertEq(address(foreignController.rateLimits()), outputBase.readAddress(".rateLimits")); - assertEq(address(foreignController.psm()), outputBase.readAddress(".psm")); - assertEq(address(foreignController.usdc()), outputBase.readAddress(".usdc")); - assertEq(address(foreignController.cctp()), inputBase.readAddress(".cctpTokenMessenger")); + _simulateUsdeMint(10e6); - assertEq(foreignController.active(), true); + vm.startPrank(relayerSafe); + mainnetController.depositERC4626(Ethereum.SUSDE, 10e18); + skip(1 days); + mainnetController.cooldownAssetsSUSDe(10e18); + skip(7 days); + mainnetController.unstakeSUSDe(); + mainnetController.prepareUSDeBurn(10e18); + vm.stopPrank(); - assertEq( - foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), - bytes32(uint256(uint160(outputMainnet.readAddress(".almProxy")))) - ); + _simulateUsdeBurn(10e18); - // ALM System roles + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); + + assertGe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); // Interest earned + } - assertEq(foreignAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(foreignRateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(foreignController.hasRole(DEFAULT_ADMIN_ROLE, admin), true); + function test_mintDepositCooldownSharesBurnUsde() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); - assertEq(foreignController.hasRole(foreignController.FREEZER(), makeAddr("freezer")), true); - assertEq(foreignController.hasRole(foreignController.RELAYER(), safeBase), true); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.prepareUSDeMint(10e6); + vm.stopPrank(); - assertEq(foreignAlmProxy.hasRole(foreignAlmProxy.CONTROLLER(), address(foreignController)), true); + _simulateUsdeMint(10e6); - assertEq(foreignRateLimits.hasRole(foreignRateLimits.CONTROLLER(), address(foreignController)), true); + vm.startPrank(relayerSafe); + mainnetController.depositERC4626(Ethereum.SUSDE, 10e18); + skip(1 days); + uint256 usdeAmount = mainnetController.cooldownSharesSUSDe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy))); + skip(7 days); + mainnetController.unstakeSUSDe(); + mainnetController.prepareUSDeBurn(usdeAmount); + vm.stopPrank(); - // Starting token balances + _simulateUsdeBurn(usdeAmount); - uint256 usdsUnitSize = ScriptTools.readInput("common").readUint(".usdsUnitSize"); - uint256 usdcUnitSize = ScriptTools.readInput("common").readUint(".usdcUnitSize"); + assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned + + assertEq(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); + } - assertEq(usdsBase.balanceOf(address(foreignAlmProxy)), usdsUnitSize * 1e18); - assertEq(susdsBase.balanceOf(address(foreignAlmProxy)), usdsUnitSize * 1e18); + /**********************************************************************************************/ + /**** Helper functions ***/ + /**********************************************************************************************/ - // Rate limits + // NOTE: In reality these actions are performed by the signer submitting an order with an + // EIP712 signature which is verified by the ethenaMinter contract, + // minting/burning USDe into the ALMProxy. Also, for the purposes of this test, + // minting/burning is done 1:1 with USDC. - uint256 max6 = usdcUnitSize * 1e6 * 5; - uint256 max18 = usdcUnitSize * 1e18 * 5; - uint256 slope6 = usdcUnitSize * 1e6 / 4 hours; - uint256 slope18 = usdcUnitSize * 1e18 / 4 hours; + // TODO: Try doing ethena minting with EIP-712 signatures (vm.sign) - bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( - foreignController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM + function _simulateUsdeMint(uint256 amount) internal { + vm.prank(Ethereum.ETHENA_MINTER); + usdc.transferFrom(address(almProxy), Ethereum.ETHENA_MINTER, amount); + deal( + Ethereum.USDE, + address(almProxy), + IERC20(Ethereum.USDE).balanceOf(address(almProxy)) + amount * 1e12 ); + } - bytes32 cctpKey = foreignController.LIMIT_USDC_TO_CCTP(); + function _simulateUsdeBurn(uint256 amount) internal { + vm.prank(Ethereum.ETHENA_MINTER); + IERC20(Ethereum.USDE).transferFrom(address(almProxy), Ethereum.ETHENA_MINTER, amount); + deal(address(usdc), address(almProxy), usdc.balanceOf(address(almProxy)) + amount / 1e12); + } - _assertDepositRateLimitData(usdcBase, max6, slope6); - _assertDepositRateLimitData(usdsBase, max18, slope18); - _assertDepositRateLimitData(susdsBase, max18, slope18); +} - _assertWithdrawRateLimitData(usdcBase, max6, slope6); - _assertWithdrawRateLimitData(usdsBase, max18, slope18); - _assertWithdrawRateLimitData(susdsBase, max18, slope18); +contract BaseStagingDeploymentTests is StagingDeploymentTestBase { - _assertRateLimitData(address(foreignRateLimits), cctpKey, type(uint256).max, 0); + using DomainHelpers for *; + using CCTPBridgeTesting for *; - _assertRateLimitData(address(foreignRateLimits), domainKeyEthereum, max6, slope6); - } + address constant AUSDC_BASE = 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB; + address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant MORPHO_VAULT_USDC = 0x305E03Ed9ADaAB22F4A58c24515D79f2B1E2FD5D; - function skip_test_mintUSDS() public { - uint256 startingBalance = usds.balanceOf(address(almProxy)); + function setUp() public override { + super.setUp(); - vm.prank(safeMainnet); - mainnetController.mintUSDS(10e18); + base.selectFork(); - assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); - } + bytes32[] memory rateLimitKeys = new bytes32[](4); - function skip_test_mintAndSwapToUSDC() public { - uint256 startingBalance = usdc.balanceOf(address(almProxy)); + rateLimitKeys[0] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_AAVE_DEPOSIT(), AUSDC_BASE); + rateLimitKeys[1] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_4626_DEPOSIT(), MORPHO_VAULT_USDC); + rateLimitKeys[2] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_AAVE_WITHDRAW(), AUSDC_BASE); + rateLimitKeys[3] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_4626_WITHDRAW(), MORPHO_VAULT_USDC); - vm.startPrank(safeMainnet); - mainnetController.mintUSDS(10e18); - mainnetController.swapUSDSToUSDC(10e6); - vm.stopPrank(); + vm.startPrank(admin); + + for (uint256 i; i < rateLimitKeys.length; i++) { + baseRateLimits.setUnlimitedRateLimitData(rateLimitKeys[i]); + } - assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); + vm.stopPrank(); } - function skip_test_transferCCTP() public { + function test_transferCCTP() public { base.selectFork(); - uint256 startingBalance = usdcBase.balanceOf(address(foreignAlmProxy)); + uint256 startingBalance = usdcBase.balanceOf(address(baseAlmProxy)); mainnet.selectFork(); - vm.startPrank(safeMainnet); + vm.startPrank(relayerSafe); mainnetController.mintUSDS(10e18); mainnetController.swapUSDSToUSDC(10e6); mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); @@ -365,17 +421,17 @@ contract DeployEthereumTest is Test { cctpBridge.relayMessagesToDestination(true); - assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), startingBalance + 10e6); + assertEq(usdcBase.balanceOf(address(baseAlmProxy)), startingBalance + 10e6); } - function skip_test_transferToPSM() public { + function test_transferToPSM() public { base.selectFork(); uint256 startingBalance = usdcBase.balanceOf(address(psmBase)); mainnet.selectFork(); - vm.startPrank(safeMainnet); + vm.startPrank(relayerSafe); mainnetController.mintUSDS(10e18); mainnetController.swapUSDSToUSDC(10e6); mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); @@ -383,93 +439,167 @@ contract DeployEthereumTest is Test { cctpBridge.relayMessagesToDestination(true); - uint256 startingShares = psmBase.shares(address(foreignAlmProxy)); + uint256 startingShares = psmBase.shares(address(baseAlmProxy)); - vm.startPrank(safeBase); - foreignController.depositPSM(address(usdcBase), 10e6); + vm.startPrank(relayerSafeBase); + baseController.depositPSM(address(usdcBase), 10e6); vm.stopPrank(); assertEq(usdcBase.balanceOf(address(psmBase)), startingBalance + 10e6); - assertEq(psmBase.shares(address(foreignAlmProxy)), startingShares + 10e18); + assertEq(psmBase.shares(address(baseAlmProxy)), startingShares + psmBase.convertToShares(10e18)); } - function skip_test_fullRoundTrip() public { + function test_addAndRemoveFundsFromBasePSM() public { mainnet.selectFork(); - vm.startPrank(safeMainnet); - mainnetController.mintUSDS(1e18); - mainnetController.swapUSDSToUSDC(1e6); - mainnetController.transferUSDCToCCTP(1e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); vm.stopPrank(); cctpBridge.relayMessagesToDestination(true); - vm.startPrank(safeBase); - foreignController.depositPSM(address(usdcBase), 1e6); - foreignController.withdrawPSM(address(usdcBase), 1e6); - foreignController.transferUSDCToCCTP(1e6, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); + vm.startPrank(relayerSafeBase); + baseController.depositPSM(address(usdcBase), 10e6); + skip(1 days); + baseController.withdrawPSM(address(usdcBase), 10e6); + baseController.transferUSDCToCCTP(10e6 - 1, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); // Account for potential rounding vm.stopPrank(); - // There is a bug when the messenger addresses are the same - // Need to force update to skip the previous relayed message - // See: https://github.com/marsfoundation/xchain-helpers/issues/24 - cctpBridge.lastDestinationLogIndex = cctpBridge.lastSourceLogIndex; cctpBridge.relayMessagesToSource(true); - vm.startPrank(safeMainnet); - mainnetController.swapUSDCToUSDS(1e6); - mainnetController.burnUSDS(1e18); + vm.startPrank(relayerSafe); + mainnetController.swapUSDCToUSDS(10e6 - 1); + mainnetController.burnUSDS((10e6 - 1) * 1e12); vm.stopPrank(); } - /**********************************************************************************************/ - /**** Helper functions ***/ - /**********************************************************************************************/ + function test_addAndRemoveFundsFromBaseAAVE() public { + mainnet.selectFork(); - function _assertDepositRateLimitData(IERC20 asset, uint256 maxAmount, uint256 slope) - internal view - { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_DEPOSIT(), - address(asset) - ); + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.stopPrank(); + + cctpBridge.relayMessagesToDestination(true); - _assertRateLimitData(address(foreignRateLimits), assetKey, maxAmount, slope); + vm.startPrank(relayerSafeBase); + baseController.depositAave(AUSDC_BASE, 10e6); + skip(1 days); + baseController.withdrawAave(AUSDC_BASE, 10e6); + + assertEq(usdcBase.balanceOf(address(baseAlmProxy)), 10e6); + + assertGe(IERC20(AUSDC_BASE).balanceOf(address(baseAlmProxy)), 0); // Interest earned + + baseController.transferUSDCToCCTP(10e6 - 1, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); // Account for potential rounding + vm.stopPrank(); + + cctpBridge.relayMessagesToSource(true); + + vm.startPrank(relayerSafe); + mainnetController.swapUSDCToUSDS(10e6 - 1); + mainnetController.burnUSDS((10e6 - 1) * 1e12); + vm.stopPrank(); } - function _assertWithdrawRateLimitData(IERC20 asset, uint256 maxAmount, uint256 slope) - internal view - { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_WITHDRAW(), - address(asset) - ); + function test_depositWithdrawFundsFromBaseMorphoUsdc() public { + _setUpMorphoMarket(); - _assertRateLimitData(address(foreignRateLimits), assetKey, maxAmount, slope); + mainnet.selectFork(); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.stopPrank(); + + cctpBridge.relayMessagesToDestination(true); + + vm.startPrank(relayerSafeBase); + baseController.depositERC4626(MORPHO_VAULT_USDC, 10e6); + skip(1 days); + baseController.withdrawERC4626(MORPHO_VAULT_USDC, 10e6); + + assertEq(usdcBase.balanceOf(address(baseAlmProxy)), 10e6); + + assertGe(IERC20(MORPHO_VAULT_USDC).balanceOf(address(baseAlmProxy)), 0); // Interest earned + + baseController.transferUSDCToCCTP(1e6 - 1, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); // Account for potential rounding + vm.stopPrank(); + + cctpBridge.relayMessagesToSource(true); + + vm.startPrank(relayerSafe); + mainnetController.swapUSDCToUSDS(1e6 - 1); + mainnetController.burnUSDS((1e6 - 1) * 1e12); + vm.stopPrank(); } - function _assertRateLimitData(bytes32 domainKey, uint256 maxAmount, uint256 slope) - internal view - { - // If no rate limits address specified default to mainnet - _assertRateLimitData(address(rateLimits), domainKey, maxAmount, slope); + function test_depositRedeemFundsFromBaseMorphoUsdc() public { + _setUpMorphoMarket(); + + mainnet.selectFork(); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.stopPrank(); + + cctpBridge.relayMessagesToDestination(true); + + vm.startPrank(relayerSafeBase); + baseController.depositERC4626(MORPHO_VAULT_USDC, 10e6); + skip(1 days); + baseController.redeemERC4626(MORPHO_VAULT_USDC, IERC20(MORPHO_VAULT_USDC).balanceOf(address(baseAlmProxy))); + + assertGe(usdcBase.balanceOf(address(baseAlmProxy)), 10e6); // Interest earned + + assertEq(IERC20(MORPHO_VAULT_USDC).balanceOf(address(baseAlmProxy)), 0); + + baseController.transferUSDCToCCTP(1e6 - 1, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); // Account for potential rounding + vm.stopPrank(); + + cctpBridge.relayMessagesToSource(true); + + vm.startPrank(relayerSafe); + mainnetController.swapUSDCToUSDS(1e6 - 1); + mainnetController.burnUSDS((1e6 - 1) * 1e12); + vm.stopPrank(); } - function _assertRateLimitData(address rateLimits_, bytes32 key, uint256 maxAmount, uint256 slope) - internal view - { - IRateLimits.RateLimitData memory data = IRateLimits(rateLimits_).getRateLimitData(key); + // TODO: Replace this once market is live + function _setUpMorphoMarket() public { + vm.startPrank(Base.SPARK_EXECUTOR); + + // Add in the idle markets so deposits can be made + MarketParams memory usdcParams = MarketParams({ + loanToken : Base.USDC, + collateralToken : address(0), + oracle : address(0), + irm : address(0), + lltv : 0 + }); + + IMetaMorpho(MORPHO_VAULT_USDC).submitCap( + usdcParams, + type(uint184).max + ); + + skip(1 days); - assertEq(data.maxAmount, maxAmount); - assertEq(data.slope, slope); - assertEq(data.lastAmount, maxAmount); + IMetaMorpho(MORPHO_VAULT_USDC).acceptCap(usdcParams); - // Deployments are done in the past - assertLe(data.lastUpdated, block.timestamp); + Id[] memory supplyQueueUSDC = new Id[](1); + supplyQueueUSDC[0] = MarketParamsLib.id(usdcParams); + IMetaMorpho(MORPHO_VAULT_USDC).setSupplyQueue(supplyQueueUSDC); - // Deployment is assumed to be untouched - assertEq(IRateLimits(rateLimits_).getCurrentRateLimit(key), maxAmount); + vm.stopPrank(); } } From 7045e780c74b20a7b17ab0febafdb924a94e92df Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Wed, 18 Dec 2024 16:04:52 -0500 Subject: [PATCH 17/22] chore: Rename staging tests file (SC-892) (#74) * test: refactor to get all test working * test: add coverage for aave, ethena, and susds * test: get full coverage * fix: update file name to show diff * chore: update file name * fix: eof --------- Co-authored-by: Lucas Manuel Co-authored-by: Lucas Manuel --- .../test/{DeployEthereum.t.sol => StagingDeployment.t.sol} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename script/staging/test/{DeployEthereum.t.sol => StagingDeployment.t.sol} (100%) diff --git a/script/staging/test/DeployEthereum.t.sol b/script/staging/test/StagingDeployment.t.sol similarity index 100% rename from script/staging/test/DeployEthereum.t.sol rename to script/staging/test/StagingDeployment.t.sol index 54c4451..a13fd70 100644 --- a/script/staging/test/DeployEthereum.t.sol +++ b/script/staging/test/StagingDeployment.t.sol @@ -27,6 +27,9 @@ import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { MainnetControllerDeploy } from "../../../deploy/ControllerDeploy.sol"; +import { MainnetControllerInit } from "../../../deploy/ControllerInit.sol"; + import { IRateLimits } from "../../../src/interfaces/IRateLimits.sol"; import { ALMProxy } from "../../../src/ALMProxy.sol"; @@ -35,9 +38,6 @@ import { MainnetController } from "../../../src/MainnetController.sol"; import { RateLimits } from "../../../src/RateLimits.sol"; import { RateLimitHelpers } from "../../../src/RateLimitHelpers.sol"; -import { MainnetControllerDeploy } from "../../../deploy/ControllerDeploy.sol"; -import { MainnetControllerInit } from "../../../deploy/ControllerInit.sol"; - interface IVatLike { function can(address, address) external view returns (uint256); } From 6ab477dea5f35b1fd3ce40333dc233c970049131 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Fri, 27 Dec 2024 17:22:34 -0500 Subject: [PATCH 18/22] fix: Update logo (#75) * fix: update logo * fix: adjust size --------- Co-authored-by: Lucas Manuel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4e1a56..3fe8f6e 100644 --- a/README.md +++ b/README.md @@ -103,5 +103,5 @@ forge test *The IP in this repository was assigned to Mars SPC Limited in respect of the MarsOne SP*

- +

From 2bb6b14ce3d1e44eb686226abfde17e12e089c7c Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Fri, 27 Dec 2024 23:03:16 -0500 Subject: [PATCH 19/22] refactor: Future-proof init/upgrade libraries for other SubDAOs and releases (SC-888) (#76) * feat: refactor init scripts, starting point * test: create dedicated deploy file * test: finish init tests * test: get mainnet deploy, init, and upgrade tests working * feat: mainnet scripts fully tested * feat: update foreign controller init lib * test: add deploy tests * test: get init tests set up * test: update foreign init tests * test: update foreign init tests * test: refactor to use test state not mainnet * test: refactor to include upgrade tests * test: add success tests * test: refactor to use test deployed system * test: main tests passing with new libs * test: main tests passing with new libs * feat: all tests passing * fix: newlines * fix: add back cctp tests * fix: update setup * chore: rename files for better diffing * fix: stack too deep * fix: rm conversion rate check for PSM * fix: keep psm check * test: add explicit deploy admin check * fix: tidy up setups * feat: staging deployment working with tests * fix: update makefile * add new staging deploy * test: update staging date * fix: review comments * fix: update with fixes * fix: compilation issue --------- Co-authored-by: Lucas Manuel Co-authored-by: Sam MacPherson --- deploy/ControllerInit.sol | 344 ------- deploy/ForeignControllerInit.sol | 149 +++ deploy/MainnetControllerInit.sol | 159 +++ lib/spark-address-registry | 2 +- script/input/1/mainnet-staging.json | 2 +- .../1/base-staging-deps-release-20241227.json | 5 + .../1/base-staging-release-20241227.json | 5 + ...mainnet-staging-deps-release-20241227.json | 19 + .../1/mainnet-staging-release-20241227.json | 5 + script/staging/FullStagingDeploy.s.sol | 316 +++--- script/staging/test/StagingDeployment.t.sol | 82 +- src/RateLimitHelpers.sol | 43 + test/base-fork/Deploy.t.sol | 62 ++ test/base-fork/DeployAndInit.t.sol | 925 ++++++------------ test/base-fork/ForkTestBase.t.sol | 99 +- test/mainnet-fork/4626Calls.t.sol | 13 +- test/mainnet-fork/CCTPCalls.t.sol | 76 +- test/mainnet-fork/Deploy.t.sol | 73 ++ test/mainnet-fork/DeployAndInit.t.sol | 797 ++++++--------- test/mainnet-fork/ForkTestBase.t.sol | 115 ++- 20 files changed, 1491 insertions(+), 1800 deletions(-) delete mode 100644 deploy/ControllerInit.sol create mode 100644 deploy/ForeignControllerInit.sol create mode 100644 deploy/MainnetControllerInit.sol create mode 100644 script/output/1/base-staging-deps-release-20241227.json create mode 100644 script/output/1/base-staging-release-20241227.json create mode 100644 script/output/1/mainnet-staging-deps-release-20241227.json create mode 100644 script/output/1/mainnet-staging-release-20241227.json create mode 100644 test/base-fork/Deploy.t.sol create mode 100644 test/mainnet-fork/Deploy.t.sol diff --git a/deploy/ControllerInit.sol b/deploy/ControllerInit.sol deleted file mode 100644 index 46ab465..0000000 --- a/deploy/ControllerInit.sol +++ /dev/null @@ -1,344 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; - -import { ForeignController } from "../src/ForeignController.sol"; -import { MainnetController } from "../src/MainnetController.sol"; -import { RateLimitHelpers } from "../src/RateLimitHelpers.sol"; - -import { IALMProxy } from "../src/interfaces/IALMProxy.sol"; -import { IRateLimits } from "../src/interfaces/IRateLimits.sol"; - -import { ControllerInstance } from "./ControllerInstance.sol"; - -interface IBufferLike { - function approve(address, address, uint256) external; -} - -interface IPSMLike { - function kiss(address) external; -} - -interface IPSM3Like { - function totalAssets() external view returns (uint256); - function totalShares() external view returns (uint256); - function usdc() external view returns (address); - function usds() external view returns (address); - function susds() external view returns (address); -} - -interface IVaultLike { - function rely(address) external; -} - -struct RateLimitData { - uint256 maxAmount; - uint256 slope; -} - -struct MintRecipient { - uint32 domain; - bytes32 mintRecipient; -} - -library MainnetControllerInit { - - struct AddressParams { - address admin; - address freezer; - address relayer; - address oldController; - address psm; - address vault; - address buffer; - address cctpMessenger; - address dai; - address daiUsds; - address usdc; - address usds; - address susds; - } - - struct InitRateLimitData { - RateLimitData usdsMintData; - RateLimitData usdsToUsdcData; - RateLimitData usdcToCctpData; - RateLimitData cctpToBaseDomainData; - RateLimitData susdsDepositData; - } - - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - - function subDaoInitController( - AddressParams memory addresses, - ControllerInstance memory controllerInst, - InitRateLimitData memory data, - MintRecipient[] memory mintRecipients - ) - internal - { - IALMProxy almProxy = IALMProxy(controllerInst.almProxy); - IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); - - MainnetController controller = MainnetController(controllerInst.controller); - - // Step 1: Perform sanity checks - - require(almProxy.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "MainnetControllerInit/incorrect-admin-almProxy"); - require(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "MainnetControllerInit/incorrect-admin-rateLimits"); - require(controller.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "MainnetControllerInit/incorrect-admin-controller"); - - require(address(controller.proxy()) == controllerInst.almProxy, "MainnetControllerInit/incorrect-almProxy"); - require(address(controller.rateLimits()) == controllerInst.rateLimits, "MainnetControllerInit/incorrect-rateLimits"); - require(address(controller.vault()) == addresses.vault, "MainnetControllerInit/incorrect-vault"); - require(address(controller.buffer()) == addresses.buffer, "MainnetControllerInit/incorrect-buffer"); - require(address(controller.psm()) == addresses.psm, "MainnetControllerInit/incorrect-psm"); - require(address(controller.daiUsds()) == addresses.daiUsds, "MainnetControllerInit/incorrect-daiUsds"); - require(address(controller.cctp()) == addresses.cctpMessenger, "MainnetControllerInit/incorrect-cctpMessenger"); - require(address(controller.dai()) == addresses.dai, "MainnetControllerInit/incorrect-dai"); - require(address(controller.usdc()) == addresses.usdc, "MainnetControllerInit/incorrect-usdc"); - require(address(controller.usds()) == addresses.usds, "MainnetControllerInit/incorrect-usds"); - - require(controller.psmTo18ConversionFactor() == 1e12, "MainnetControllerInit/incorrect-psmTo18ConversionFactor"); - - require(controller.active(), "MainnetControllerInit/controller-not-active"); - - require(addresses.oldController != address(controller), "MainnetControllerInit/old-controller-is-new-controller"); - - // Step 2: Configure ACL permissions for controller and almProxy - - controller.grantRole(controller.FREEZER(), addresses.freezer); - controller.grantRole(controller.RELAYER(), addresses.relayer); - - almProxy.grantRole(almProxy.CONTROLLER(), address(controller)); - rateLimits.grantRole(rateLimits.CONTROLLER(), address(controller)); - - if (addresses.oldController != address(0)) { - require(almProxy.hasRole(almProxy.CONTROLLER(), addresses.oldController), "MainnetControllerInit/old-controller-not-almProxy-controller"); - require(rateLimits.hasRole(rateLimits.CONTROLLER(), addresses.oldController), "MainnetControllerInit/old-controller-not-rateLimits-controller"); - - almProxy.revokeRole(almProxy.CONTROLLER(), addresses.oldController); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.oldController); - } - - // Step 3: Configure all rate limits for controller, using Base as only domain - - bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( - controller.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE - ); - - bytes32 susdsKey = RateLimitHelpers.makeAssetKey( - controller.LIMIT_4626_DEPOSIT(), - addresses.susds - ); - - setRateLimitData(controller.LIMIT_USDS_MINT(), rateLimits, data.usdsMintData, "usdsMintData", 18); - setRateLimitData(controller.LIMIT_USDS_TO_USDC(), rateLimits, data.usdsToUsdcData, "usdsToUsdcData", 6); - setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); - setRateLimitData(domainKeyBase, rateLimits, data.cctpToBaseDomainData, "cctpToBaseDomainData", 6); - setRateLimitData(susdsKey, rateLimits, data.susdsDepositData, "susdsDepositData", 18); - - // Step 4: Configure the mint recipients on other domains - - for (uint256 i = 0; i < mintRecipients.length; i++) { - controller.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient); - } - } - - function subDaoInitFull( - AddressParams memory addresses, - ControllerInstance memory controllerInst, - InitRateLimitData memory data, - MintRecipient[] memory mintRecipients - ) - internal - { - // Step 1: Perform controller sanity checks, configure ACL permissions for controller - // and almProxy and rate limits. - - subDaoInitController( - addresses, - controllerInst, - data, - mintRecipients - ); - - // Step 2: Configure almProxy within the allocation system - - IVaultLike(addresses.vault).rely(controllerInst.almProxy); - IBufferLike(addresses.buffer).approve(addresses.usds, controllerInst.almProxy, type(uint256).max); - } - - function pauseProxyInit(address psm, address almProxy) internal { - IPSMLike(psm).kiss(almProxy); // To allow using no fee functionality - } - - function setRateLimitData( - bytes32 key, - IRateLimits rateLimits, - RateLimitData memory data, - string memory name, - uint256 decimals - ) - internal - { - // Handle setting an unlimited rate limit - if (data.maxAmount == type(uint256).max) { - require( - data.slope == 0, - string(abi.encodePacked("MainnetControllerInit/invalid-rate-limit-", name)) - ); - } - else { - require( - data.maxAmount <= 1e12 * (10 ** decimals), - string(abi.encodePacked("MainnetControllerInit/invalid-max-amount-precision-", name)) - ); - require( - data.slope <= 1e12 * (10 ** decimals) / 1 hours, - string(abi.encodePacked("MainnetControllerInit/invalid-slope-precision-", name)) - ); - } - rateLimits.setRateLimitData(key, data.maxAmount, data.slope); - } - -} - -library ForeignControllerInit { - - struct AddressParams { - address admin; - address freezer; - address relayer; - address oldController; - address psm; - address cctpMessenger; - address usdc; - address usds; - address susds; - } - - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - - struct InitRateLimitData { - RateLimitData usdcDepositData; - RateLimitData usdcWithdrawData; - RateLimitData usdsDepositData; - RateLimitData usdsWithdrawData; - RateLimitData susdsDepositData; - RateLimitData susdsWithdrawData; - RateLimitData usdcToCctpData; - RateLimitData cctpToEthereumDomainData; - } - - function init( - AddressParams memory addresses, - ControllerInstance memory controllerInst, - InitRateLimitData memory data, - MintRecipient[] memory mintRecipients - ) - internal - { - IALMProxy almProxy = IALMProxy(controllerInst.almProxy); - IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); - - ForeignController controller = ForeignController(controllerInst.controller); - - require(almProxy.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "ForeignControllerInit/incorrect-admin-almProxy"); - require(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "ForeignControllerInit/incorrect-admin-rateLimits"); - require(controller.hasRole(DEFAULT_ADMIN_ROLE, addresses.admin), "ForeignControllerInit/incorrect-admin-controller"); - - require(address(controller.proxy()) == controllerInst.almProxy, "ForeignControllerInit/incorrect-almProxy"); - require(address(controller.rateLimits()) == controllerInst.rateLimits, "ForeignControllerInit/incorrect-rateLimits"); - require(address(controller.psm()) == addresses.psm, "ForeignControllerInit/incorrect-psm"); - require(address(controller.usdc()) == addresses.usdc, "ForeignControllerInit/incorrect-usdc"); - require(address(controller.cctp()) == addresses.cctpMessenger, "ForeignControllerInit/incorrect-cctp"); - - require(controller.active(), "ForeignControllerInit/controller-not-active"); - - require(addresses.oldController != address(controller), "ForeignControllerInit/old-controller-is-new-controller"); - - IPSM3Like psm = IPSM3Like(addresses.psm); - - require(psm.totalAssets() >= 1e18, "ForeignControllerInit/psm-totalAssets-not-seeded"); - require(psm.totalShares() >= 1e18, "ForeignControllerInit/psm-totalShares-not-seeded"); - - require(psm.usdc() == addresses.usdc, "ForeignControllerInit/psm-incorrect-usdc"); - require(psm.usds() == addresses.usds, "ForeignControllerInit/psm-incorrect-usds"); - require(psm.susds() == addresses.susds, "ForeignControllerInit/psm-incorrect-susds"); - - // Step 1: Configure ACL permissions for controller and almProxy - - controller.grantRole(controller.FREEZER(), addresses.freezer); - controller.grantRole(controller.RELAYER(), addresses.relayer); - - almProxy.grantRole(almProxy.CONTROLLER(), address(controller)); - rateLimits.grantRole(rateLimits.CONTROLLER(), address(controller)); - - if (addresses.oldController != address(0)) { - require(almProxy.hasRole(almProxy.CONTROLLER(), addresses.oldController) == true, "ForeignControllerInit/old-controller-not-almProxy-controller"); - require(rateLimits.hasRole(rateLimits.CONTROLLER(), addresses.oldController) == true, "ForeignControllerInit/old-controller-not-rateLimits-controller"); - - almProxy.revokeRole(almProxy.CONTROLLER(), addresses.oldController); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.oldController); - } - - // Step 2: Configure all rate limits for controller - - bytes32 depositKey = controller.LIMIT_PSM_DEPOSIT(); - bytes32 withdrawKey = controller.LIMIT_PSM_WITHDRAW(); - - bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( - controller.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM - ); - - setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.usdc), rateLimits, data.usdcDepositData, "usdcDepositData", 6); - setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.usdc), rateLimits, data.usdcWithdrawData, "usdcWithdrawData", 6); - setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.usds), rateLimits, data.usdsDepositData, "usdsDepositData", 18); - setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.usds), rateLimits, data.usdsWithdrawData, "usdsWithdrawData", 18); - setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, addresses.susds), rateLimits, data.susdsDepositData, "susdsDepositData", 18); - setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, addresses.susds), rateLimits, data.susdsWithdrawData, "susdsWithdrawData", 18); - - setRateLimitData(controller.LIMIT_USDC_TO_CCTP(), rateLimits, data.usdcToCctpData, "usdcToCctpData", 6); - setRateLimitData(domainKeyEthereum, rateLimits, data.cctpToEthereumDomainData, "cctpToEthereumDomainData", 6); - - // Step 3: Configure the mint recipients on other domains - - for (uint256 i = 0; i < mintRecipients.length; i++) { - controller.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient); - } - } - - function setRateLimitData( - bytes32 key, - IRateLimits rateLimits, - RateLimitData memory data, - string memory name, - uint256 decimals - ) - internal - { - // Handle setting an unlimited rate limit - if (data.maxAmount == type(uint256).max) { - require( - data.slope == 0, - string(abi.encodePacked("ForeignControllerInit/invalid-rate-limit-", name)) - ); - } - else { - require( - data.maxAmount <= 1e12 * (10 ** decimals), - string(abi.encodePacked("ForeignControllerInit/invalid-max-amount-precision-", name)) - ); - require( - data.slope <= 1e12 * (10 ** decimals) / 1 hours, - string(abi.encodePacked("ForeignControllerInit/invalid-slope-precision-", name)) - ); - } - rateLimits.setRateLimitData(key, data.maxAmount, data.slope); - } - -} diff --git a/deploy/ForeignControllerInit.sol b/deploy/ForeignControllerInit.sol new file mode 100644 index 0000000..bc69fa3 --- /dev/null +++ b/deploy/ForeignControllerInit.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { ForeignController } from "../src/ForeignController.sol"; + +import { IALMProxy } from "../src/interfaces/IALMProxy.sol"; +import { IRateLimits } from "../src/interfaces/IRateLimits.sol"; + +import { ControllerInstance } from "./ControllerInstance.sol"; + +interface IPSM3Like { + function susds() external view returns (address); + function totalAssets() external view returns (uint256); + function totalShares() external view returns (uint256); + function usdc() external view returns (address); + function usds() external view returns (address); +} + +library ForeignControllerInit { + + /**********************************************************************************************/ + /*** Structs and constants ***/ + /**********************************************************************************************/ + + struct CheckAddressParams { + address admin; + address psm; + address cctp; + address usdc; + address susds; + address usds; + } + + struct ConfigAddressParams { + address freezer; + address relayer; + address oldController; + } + + struct MintRecipient { + uint32 domain; + bytes32 mintRecipient; + } + + bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; + + /**********************************************************************************************/ + /*** Internal library functions ***/ + /**********************************************************************************************/ + + function initAlmSystem( + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + internal + { + // Step 1: Do sanity checks outside of the controller + + require(IALMProxy(controllerInst.almProxy).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-almProxy"); + require(IRateLimits(controllerInst.rateLimits).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-rateLimits"); + + // Step 2: Initialize the controller + + _initController(controllerInst, configAddresses, checkAddresses, mintRecipients); + } + + function upgradeController( + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + internal + { + _initController(controllerInst, configAddresses, checkAddresses, mintRecipients); + + IALMProxy almProxy = IALMProxy(controllerInst.almProxy); + IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); + + require(configAddresses.oldController != address(0), "ForeignControllerInit/old-controller-zero-address"); + + require(almProxy.hasRole(almProxy.CONTROLLER(), configAddresses.oldController), "ForeignControllerInit/old-controller-not-almProxy-controller"); + require(rateLimits.hasRole(rateLimits.CONTROLLER(), configAddresses.oldController), "ForeignControllerInit/old-controller-not-rateLimits-controller"); + + almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); + } + + /**********************************************************************************************/ + /*** Private helper functions ***/ + /**********************************************************************************************/ + + function _initController( + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + private + { + // Step 1: Perform controller sanity checks + + ForeignController newController = ForeignController(controllerInst.controller); + + require(newController.hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-controller"); + + require(address(newController.proxy()) == controllerInst.almProxy, "ForeignControllerInit/incorrect-almProxy"); + require(address(newController.rateLimits()) == controllerInst.rateLimits, "ForeignControllerInit/incorrect-rateLimits"); + + require(address(newController.psm()) == checkAddresses.psm, "ForeignControllerInit/incorrect-psm"); + require(address(newController.usdc()) == checkAddresses.usdc, "ForeignControllerInit/incorrect-usdc"); + require(address(newController.cctp()) == checkAddresses.cctp, "ForeignControllerInit/incorrect-cctp"); + + require(newController.active(), "ForeignControllerInit/controller-not-active"); + + require(configAddresses.oldController != address(newController), "ForeignControllerInit/old-controller-is-new-controller"); + + // Step 2: Perform PSM sanity checks + + IPSM3Like psm = IPSM3Like(checkAddresses.psm); + + require(psm.totalAssets() >= 1e18, "ForeignControllerInit/psm-totalAssets-not-seeded"); + require(psm.totalShares() >= 1e18, "ForeignControllerInit/psm-totalShares-not-seeded"); + + require(psm.usdc() == checkAddresses.usdc, "ForeignControllerInit/psm-incorrect-usdc"); + require(psm.usds() == checkAddresses.usds, "ForeignControllerInit/psm-incorrect-usds"); + require(psm.susds() == checkAddresses.susds, "ForeignControllerInit/psm-incorrect-susds"); + + // Step 3: Configure ACL permissions controller, almProxy, and rateLimits + + IALMProxy almProxy = IALMProxy(controllerInst.almProxy); + IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); + + newController.grantRole(newController.FREEZER(), configAddresses.freezer); + newController.grantRole(newController.RELAYER(), configAddresses.relayer); + + almProxy.grantRole(almProxy.CONTROLLER(), address(newController)); + rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController)); + + // Step 4: Configure the mint recipients on other domains + + for (uint256 i = 0; i < mintRecipients.length; i++) { + newController.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient); + } + } + +} diff --git a/deploy/MainnetControllerInit.sol b/deploy/MainnetControllerInit.sol new file mode 100644 index 0000000..e8e50df --- /dev/null +++ b/deploy/MainnetControllerInit.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { MainnetController } from "../src/MainnetController.sol"; + +import { IALMProxy } from "../src/interfaces/IALMProxy.sol"; +import { IRateLimits } from "../src/interfaces/IRateLimits.sol"; + +import { ControllerInstance } from "./ControllerInstance.sol"; + +interface IBufferLike { + function approve(address, address, uint256) external; +} + +interface IPSMLike { + function kiss(address) external; +} + +interface IVaultLike { + function buffer() external view returns (address); + function rely(address) external; +} + +library MainnetControllerInit { + + /**********************************************************************************************/ + /*** Structs and constants ***/ + /**********************************************************************************************/ + + struct CheckAddressParams { + address admin; + address proxy; + address rateLimits; + address vault; + address psm; + address daiUsds; + address cctp; + } + + struct ConfigAddressParams { + address freezer; + address relayer; + address oldController; + } + + struct MintRecipient { + uint32 domain; + bytes32 mintRecipient; + } + + bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; + + /**********************************************************************************************/ + /*** Internal library functions ***/ + /**********************************************************************************************/ + + function initAlmSystem( + address vault, + address usds, + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + internal + { + // Step 1: Do sanity checks outside of the controller + + require(IALMProxy(controllerInst.almProxy).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-almProxy"); + require(IRateLimits(controllerInst.rateLimits).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-rateLimits"); + + // Step 2: Initialize the controller + + _initController(controllerInst, configAddresses, checkAddresses, mintRecipients); + + // Step 3: Configure almProxy within the allocation system + + require(vault == checkAddresses.vault, "MainnetControllerInit/incorrect-vault"); + + IVaultLike(vault).rely(controllerInst.almProxy); + IBufferLike(IVaultLike(vault).buffer()).approve(usds, controllerInst.almProxy, type(uint256).max); + } + + function upgradeController( + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + internal + { + _initController(controllerInst, configAddresses, checkAddresses, mintRecipients); + + IALMProxy almProxy = IALMProxy(controllerInst.almProxy); + IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); + + require(configAddresses.oldController != address(0), "MainnetControllerInit/old-controller-zero-address"); + + require(almProxy.hasRole(almProxy.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-almProxy-controller"); + require(rateLimits.hasRole(rateLimits.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-rateLimits-controller"); + + almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); + } + + function pauseProxyInitAlmSystem(address psm, address almProxy) internal { + IPSMLike(psm).kiss(almProxy); // To allow using no fee functionality + } + + /**********************************************************************************************/ + /*** Private helper functions ***/ + /**********************************************************************************************/ + + function _initController( + ControllerInstance memory controllerInst, + ConfigAddressParams memory configAddresses, + CheckAddressParams memory checkAddresses, + MintRecipient[] memory mintRecipients + ) + private + { + // Step 1: Perform controller sanity checks + + MainnetController newController = MainnetController(controllerInst.controller); + + require(newController.hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-controller"); + + require(address(newController.proxy()) == controllerInst.almProxy, "MainnetControllerInit/incorrect-almProxy"); + require(address(newController.rateLimits()) == controllerInst.rateLimits, "MainnetControllerInit/incorrect-rateLimits"); + + require(address(newController.vault()) == checkAddresses.vault, "MainnetControllerInit/incorrect-vault"); + require(address(newController.psm()) == checkAddresses.psm, "MainnetControllerInit/incorrect-psm"); + require(address(newController.daiUsds()) == checkAddresses.daiUsds, "MainnetControllerInit/incorrect-daiUsds"); + require(address(newController.cctp()) == checkAddresses.cctp, "MainnetControllerInit/incorrect-cctp"); + + require(newController.psmTo18ConversionFactor() == 1e12, "MainnetControllerInit/incorrect-psmTo18ConversionFactor"); + require(newController.active(), "MainnetControllerInit/controller-not-active"); + + require(configAddresses.oldController != address(newController), "MainnetControllerInit/old-controller-is-new-controller"); + + // Step 2: Configure ACL permissions controller, almProxy, and rateLimits + + IALMProxy almProxy = IALMProxy(controllerInst.almProxy); + IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits); + + newController.grantRole(newController.FREEZER(), configAddresses.freezer); + newController.grantRole(newController.RELAYER(), configAddresses.relayer); + + almProxy.grantRole(almProxy.CONTROLLER(), address(newController)); + rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController)); + + // Step 3: Configure the mint recipients on other domains + + for (uint256 i = 0; i < mintRecipients.length; i++) { + newController.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient); + } + } + +} diff --git a/lib/spark-address-registry b/lib/spark-address-registry index bf584d8..0894d15 160000 --- a/lib/spark-address-registry +++ b/lib/spark-address-registry @@ -1 +1 @@ -Subproject commit bf584d8b5d0afdd6cdc581fbffe543d3e00e2c3c +Subproject commit 0894d151cab9cc50dcf49c4c32e6469b16b391a1 diff --git a/script/input/1/mainnet-staging.json b/script/input/1/mainnet-staging.json index 294d050..3291b67 100644 --- a/script/input/1/mainnet-staging.json +++ b/script/input/1/mainnet-staging.json @@ -5,7 +5,7 @@ "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", - "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8", + "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042", "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", diff --git a/script/output/1/base-staging-deps-release-20241227.json b/script/output/1/base-staging-deps-release-20241227.json new file mode 100644 index 0000000..d80ca76 --- /dev/null +++ b/script/output/1/base-staging-deps-release-20241227.json @@ -0,0 +1,5 @@ +{ + "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", + "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047" +} diff --git a/script/output/1/base-staging-release-20241227.json b/script/output/1/base-staging-release-20241227.json new file mode 100644 index 0000000..b171750 --- /dev/null +++ b/script/output/1/base-staging-release-20241227.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0xa72e01A942f5E8EF09dbaf824C2d7a7033e96f0D", + "controller": "0xf1202d64010a7b644AB258ca46Ad5fDf2148905a", + "rateLimits": "0x1d741314F73Aea8A133C5c71653F779150f9c229" +} diff --git a/script/output/1/mainnet-staging-deps-release-20241227.json b/script/output/1/mainnet-staging-deps-release-20241227.json new file mode 100644 index 0000000..cb2dbc8 --- /dev/null +++ b/script/output/1/mainnet-staging-deps-release-20241227.json @@ -0,0 +1,19 @@ +{ + "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd", + "allocatorBuffer": "0x21fd5b6f0FbF7300a23F9cC397630A27Ee013AE5", + "allocatorOracle": "0xDf9EE9Ad3cB7ec26F98103751F7Fb7149a284541", + "allocatorRegistry": "0x2143B2949bc3B9b405cFebb065aB6beF4DB85956", + "allocatorRoles": "0x041918Ef264214BD999776667a693b06B35c36Fa", + "allocatorVault": "0x85cb1558802D32D437B63d7C3eB4e6d6c88a383B", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "jug": "0x6C20DC38D8e978955B12217D82692d448239C005", + "psm": "0x66c044CeE6bdf8360C2F6eDffc712829900100fB", + "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4", + "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + "usdsJoin": "0x12CBa9C1e5CC66926d1364B63e62cF16830bF977", + "vat": "0x2157802ce1172b7bae5540b0d20d8B4337B535C2" +} diff --git a/script/output/1/mainnet-staging-release-20241227.json b/script/output/1/mainnet-staging-release-20241227.json new file mode 100644 index 0000000..9f56b33 --- /dev/null +++ b/script/output/1/mainnet-staging-release-20241227.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0xba8b8375Af6bB5B7e0424D8581b82fe39CfCff8A", + "controller": "0xe08828c1A20dA7874A52dA070480B9B1e4213B6C", + "rateLimits": "0x2A85Ce4869e8fCFbb84b39485eB082F96d8f1b5d" +} diff --git a/script/staging/FullStagingDeploy.s.sol b/script/staging/FullStagingDeploy.s.sol index 05b6cd6..57202d5 100644 --- a/script/staging/FullStagingDeploy.s.sol +++ b/script/staging/FullStagingDeploy.s.sol @@ -33,15 +33,12 @@ import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; -import { - ForeignControllerInit, - MainnetControllerInit, - MintRecipient, - RateLimitData -} from "../../deploy/ControllerInit.sol"; +import { ForeignControllerInit } from "../../deploy/ForeignControllerInit.sol"; +import { MainnetControllerInit } from "../../deploy/MainnetControllerInit.sol"; + +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; -import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; -import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; +import { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; import { MockJug } from "./mocks/MockJug.sol"; import { MockUsdsJoin } from "./mocks/MockUsdsJoin.sol"; @@ -61,6 +58,17 @@ contract FullStagingDeploy is Script { using stdJson for string; using ScriptTools for string; + /**********************************************************************************************/ + /*** Deployed contracts ***/ + /**********************************************************************************************/ + + address constant AUSDS = 0x32a6268f9Ba3642Dda7892aDd74f1D34469A4259; + address constant AUSDC = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c; + + address constant AUSDC_BASE = 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB; + address constant MORPHO_BASE = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant MORPHO_VAULT_USDC_BASE = 0x305E03Ed9ADaAB22F4A58c24515D79f2B1E2FD5D; + /**********************************************************************************************/ /*** Mainnet existing/mock deployments ***/ /**********************************************************************************************/ @@ -215,25 +223,75 @@ contract FullStagingDeploy is Script { ScriptTools.exportContract(mainnet.nameDeps, "allocatorVault", vault); } - function _setUpALMController() internal { + function _setUpMainnetController() internal { vm.selectFork(mainnet.forkId); vm.startBroadcast(); // Step 1: Deploy ALM controller - ControllerInstance memory instance = MainnetControllerDeploy.deployFull({ + ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull({ admin : mainnet.admin, vault : vault, - psm : psm, + psm : psm, // Wrapper daiUsds : daiUsds, cctp : mainnet.config.readAddress(".cctpTokenMessenger") }); - mainnetAlmProxy = instance.almProxy; - mainnetController = instance.controller; + mainnetAlmProxy = controllerInst.almProxy; + mainnetController = controllerInst.controller; + + // Step 2: Initialize ALM system - // Step 2: Initialize ALM controller, setting rate limits, mint recipients, and setting ACL + MainnetControllerInit.ConfigAddressParams memory configAddresses + = MainnetControllerInit.ConfigAddressParams({ + freezer : mainnet.config.readAddress(".freezer"), + relayer : mainnet.config.readAddress(".relayer"), + oldController : address(0) + }); + + MainnetControllerInit.CheckAddressParams memory checkAddresses + = MainnetControllerInit.CheckAddressParams({ + admin : mainnet.admin, + proxy : controllerInst.almProxy, + rateLimits : controllerInst.rateLimits, + vault : vault, + psm : psm, + daiUsds : mainnet.config.readAddress(".daiUsds"), + cctp : mainnet.config.readAddress(".cctpTokenMessenger") + }); + + MainnetControllerInit.MintRecipient[] memory mintRecipients = new MainnetControllerInit.MintRecipient[](0); + + MainnetControllerInit.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + // Step 3: Set all rate limits for the controller + _setMainnetControllerRateLimits(controllerInst.rateLimits); + + // Step 4: Transfer ownership of mock usdsJoin to the vault (able to mint usds) + + MockUsdsJoin(usdsJoin).transferOwnership(vault); + + vm.stopBroadcast(); + + // Step 5: Export all deployed addresses + + ScriptTools.exportContract(mainnet.nameDeps, "freezer", mainnet.config.readAddress(".freezer")); + ScriptTools.exportContract(mainnet.nameDeps, "relayer", mainnet.config.readAddress(".relayer")); + + ScriptTools.exportContract(mainnet.name, "almProxy", controllerInst.almProxy); + ScriptTools.exportContract(mainnet.name, "controller", controllerInst.controller); + ScriptTools.exportContract(mainnet.name, "rateLimits", controllerInst.rateLimits); + } + + function _setMainnetControllerRateLimits(address rateLimits) internal { // Still constrained by the USDC_UNIT_SIZE RateLimitData memory rateLimitData18 = RateLimitData({ maxAmount : USDC_UNIT_SIZE * 1e12 * 5, @@ -243,151 +301,153 @@ contract FullStagingDeploy is Script { maxAmount : USDC_UNIT_SIZE * 5, slope : USDC_UNIT_SIZE / 4 hours }); - RateLimitData memory unlimitedRateLimit = RateLimitData({ - maxAmount : type(uint256).max, - slope : 0 - }); - // Configure this after Base ALM Proxy is deployed - MintRecipient[] memory mintRecipients = new MintRecipient[](0); + RateLimitData memory unlimitedRateLimit = RateLimitHelpers.unlimitedRateLimit(); + + MainnetController mainnetController_ = MainnetController(mainnetController); + + bytes32 ausdcDepositKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_AAVE_DEPOSIT(), AUSDC); + bytes32 ausdcWithdrawKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_AAVE_WITHDRAW(), AUSDC); + bytes32 ausdsDepositKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_AAVE_DEPOSIT(), AUSDS); + bytes32 ausdsWithdrawKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_AAVE_WITHDRAW(), AUSDS); + bytes32 susdeDepositKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_4626_DEPOSIT(), address(mainnetController_.susde())); + bytes32 susdsDepositKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_4626_DEPOSIT(), susds); + bytes32 susdsWithdrawKey = RateLimitHelpers.makeAssetKey(mainnetController_.LIMIT_4626_WITHDRAW(), susds); + + bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey(mainnetController_.LIMIT_USDC_TO_DOMAIN(), CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + + // USDS mint/burn and cross-chain transfer rate limits + RateLimitHelpers.setRateLimitData(domainKeyBase, rateLimits, rateLimitData6, "cctpToBaseDomainData", 6); + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_USDC_TO_CCTP(), rateLimits, unlimitedRateLimit, "usdsToCctpData", 6); + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_USDS_MINT(), rateLimits, rateLimitData18, "usdsMintData", 18); + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_USDS_TO_USDC(), rateLimits, rateLimitData6, "usdsToUsdcData", 6); + + // Ethena-specific rate limits + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_SUSDE_COOLDOWN(), rateLimits, rateLimitData18, "susdeCooldownData", 18); + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_USDE_BURN(), rateLimits, rateLimitData18, "usdeBurnData", 18); + RateLimitHelpers.setRateLimitData(mainnetController_.LIMIT_USDE_MINT(), rateLimits, rateLimitData6, "usdeMintData", 6); + + // 4626 and AAVE deposit/withdraw rate limits + RateLimitHelpers.setRateLimitData(ausdcDepositKey, rateLimits, rateLimitData6, "ausdcDepositData", 6); + RateLimitHelpers.setRateLimitData(ausdcWithdrawKey, rateLimits, unlimitedRateLimit, "ausdcWithdrawData", 6); + RateLimitHelpers.setRateLimitData(ausdsDepositKey, rateLimits, rateLimitData18, "ausdsDepositData", 18); + RateLimitHelpers.setRateLimitData(ausdsWithdrawKey, rateLimits, unlimitedRateLimit, "ausdcWithdrawData", 18); + RateLimitHelpers.setRateLimitData(susdeDepositKey, rateLimits, rateLimitData18, "susdeDepositData", 18); + RateLimitHelpers.setRateLimitData(susdsDepositKey, rateLimits, unlimitedRateLimit, "susdsDepositData", 18); + RateLimitHelpers.setRateLimitData(susdsWithdrawKey, rateLimits, unlimitedRateLimit, "susdsWithdrawData", 18); + } - MainnetControllerInit.subDaoInitFull({ - addresses: MainnetControllerInit.AddressParams({ - admin : mainnet.admin, - freezer : mainnet.config.readAddress(".freezer"), - relayer : mainnet.config.readAddress(".relayer"), - oldController : address(0), - psm : psm, - vault : vault, - buffer : buffer, - cctpMessenger : mainnet.config.readAddress(".cctpTokenMessenger"), - dai : dai, - daiUsds : daiUsds, - usdc : usdc, - usds : usds, - susds : susds - }), - controllerInst: instance, - data: MainnetControllerInit.InitRateLimitData({ - usdsMintData : rateLimitData18, - usdsToUsdcData : rateLimitData6, - usdcToCctpData : unlimitedRateLimit, - cctpToBaseDomainData : rateLimitData6, - susdsDepositData : rateLimitData18 - }), - mintRecipients: mintRecipients + function _setBaseControllerRateLimits(address rateLimits) internal { + RateLimitData memory rateLimitData18 = RateLimitData({ + maxAmount : USDC_UNIT_SIZE * 1e12 * 5, + slope : USDC_UNIT_SIZE * 1e12 / 4 hours }); - - // Extra rate limit configuration - bytes32 mintKey = MainnetController(instance.controller).LIMIT_USDE_MINT(); - bytes32 burnKey = MainnetController(instance.controller).LIMIT_USDE_BURN(); - - bytes32 susdsDepositKey = RateLimitHelpers.makeAssetKey( - MainnetController(instance.controller).LIMIT_4626_DEPOSIT(), - address(MainnetController(instance.controller).susde()) - ); - - bytes32 susdsWithdrawKey = RateLimitHelpers.makeAssetKey( - MainnetController(instance.controller).LIMIT_4626_WITHDRAW(), - address(MainnetController(instance.controller).susde()) + RateLimitData memory rateLimitData6 = RateLimitData({ + maxAmount : USDC_UNIT_SIZE * 5, + slope : USDC_UNIT_SIZE / 4 hours + }); + RateLimitData memory unlimitedRateLimit = RateLimitHelpers.unlimitedRateLimit(); + + ForeignController foreignController = ForeignController(baseController); + + bytes32 aaveDepositKey = foreignController.LIMIT_AAVE_DEPOSIT(); + bytes32 aaveWithdrawKey = foreignController.LIMIT_AAVE_WITHDRAW(); + bytes32 psmDepositKey = foreignController.LIMIT_PSM_DEPOSIT(); + bytes32 psmWithdrawKey = foreignController.LIMIT_PSM_WITHDRAW(); + bytes32 vaultDepositKey = foreignController.LIMIT_4626_DEPOSIT(); + bytes32 vaultWithdrawKey = foreignController.LIMIT_4626_WITHDRAW(); + + bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( + foreignController.LIMIT_USDC_TO_DOMAIN(), + CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM ); - // Extra rate limit configuration - MainnetControllerInit.setRateLimitData(mintKey, IRateLimits(instance.rateLimits), rateLimitData6, "usdeMintData", 6); - MainnetControllerInit.setRateLimitData(burnKey, IRateLimits(instance.rateLimits), rateLimitData18, "usdeBurnData", 18); - MainnetControllerInit.setRateLimitData(susdsDepositKey, IRateLimits(instance.rateLimits), rateLimitData18, "susdsDepositData", 18); - MainnetControllerInit.setRateLimitData(susdsWithdrawKey, IRateLimits(instance.rateLimits), rateLimitData18, "susdsWithdrawData", 18); - - // Step 3: Transfer ownership of mock usdsJoin to the vault (able to mint usds) - - MockUsdsJoin(usdsJoin).transferOwnership(vault); - - vm.stopBroadcast(); - - // Step 4: Export all deployed addresses - - ScriptTools.exportContract(mainnet.nameDeps, "freezer", mainnet.config.readAddress(".freezer")); - ScriptTools.exportContract(mainnet.nameDeps, "relayer", mainnet.config.readAddress(".relayer")); - - ScriptTools.exportContract(mainnet.name, "almProxy", instance.almProxy); - ScriptTools.exportContract(mainnet.name, "controller", instance.controller); - ScriptTools.exportContract(mainnet.name, "rateLimits", instance.rateLimits); + usdc = base.config.readAddress(".usdc"); + usds = base.config.readAddress(".usds"); + susds = base.config.readAddress(".susds"); + + // PSM rate limits for all three assets + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmDepositKey, usdc), rateLimits, rateLimitData6, "usdcDepositDataPsm", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmWithdrawKey, usdc), rateLimits, rateLimitData6, "usdcWithdrawDataPsm", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmDepositKey, usds), rateLimits, rateLimitData18, "usdsDepositDataPsm", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmWithdrawKey, usds), rateLimits, unlimitedRateLimit, "usdsWithdrawDataPsm", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmDepositKey, susds), rateLimits, rateLimitData18, "susdsDepositDataPsm", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(psmWithdrawKey, susds), rateLimits, unlimitedRateLimit, "susdsWithdrawDataPsm", 18); + + // CCTP rate limits + RateLimitHelpers.setRateLimitData(domainKeyEthereum, rateLimits, rateLimitData6, "cctpToEthereumDomainData", 6); + RateLimitHelpers.setRateLimitData(foreignController.LIMIT_USDC_TO_CCTP(), rateLimits, unlimitedRateLimit, "usdsToCctpData", 6); + + // AAVE rate limits + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(aaveDepositKey, AUSDC_BASE), rateLimits, rateLimitData6, "usdcDepositDataAave", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(aaveWithdrawKey, AUSDC_BASE), rateLimits, unlimitedRateLimit, "usdcWithdrawDataAave", 6); + + // Morpho rate limits + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(vaultDepositKey, MORPHO_VAULT_USDC_BASE), rateLimits, rateLimitData6, "usdsDepositDataMorpho", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(vaultWithdrawKey, MORPHO_VAULT_USDC_BASE), rateLimits, unlimitedRateLimit, "usdsWithdrawDataMorpho", 6); } - function _setUpBaseALMController() public { + function _setUpBaseALMController() internal { vm.selectFork(base.forkId); vm.startBroadcast(); // Step 1: Deploy ALM controller - ControllerInstance memory instance = ForeignControllerDeploy.deployFull({ + ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({ admin : base.admin, psm : base.config.readAddress(".psm"), usdc : base.config.readAddress(".usdc"), cctp : base.config.readAddress(".cctpTokenMessenger") }); - baseAlmProxy = instance.almProxy; - baseController = instance.controller; + baseAlmProxy = controllerInst.almProxy; + baseController = controllerInst.controller; - // Step 2: Initialize ALM controller, setting rate limits, mint recipients, and setting ACL + // Step 2: Initialize ALM system - // Still constrained by the USDC_UNIT_SIZE - RateLimitData memory rateLimitData18 = RateLimitData({ - maxAmount : USDC_UNIT_SIZE * 1e12 * 5, - slope : USDC_UNIT_SIZE * 1e12 / 4 hours - }); - RateLimitData memory rateLimitData6 = RateLimitData({ - maxAmount : USDC_UNIT_SIZE * 5, - slope : USDC_UNIT_SIZE / 4 hours + ForeignControllerInit.ConfigAddressParams memory configAddresses = ForeignControllerInit.ConfigAddressParams({ + freezer : base.config.readAddress(".freezer"), + relayer : base.config.readAddress(".relayer"), + oldController : address(0) }); - RateLimitData memory unlimitedRateLimit = RateLimitData({ - maxAmount : type(uint256).max, - slope : 0 + + ForeignControllerInit.CheckAddressParams memory checkAddresses = ForeignControllerInit.CheckAddressParams({ + admin : base.admin, + psm : base.config.readAddress(".psm"), + cctp : base.config.readAddress(".cctpTokenMessenger"), + usdc : base.config.readAddress(".usdc"), + susds : base.config.readAddress(".susds"), + usds : base.config.readAddress(".usds") }); - MintRecipient[] memory mintRecipients = new MintRecipient[](1); - mintRecipients[0] = MintRecipient({ + ForeignControllerInit.MintRecipient[] memory mintRecipients = new ForeignControllerInit.MintRecipient[](1); + + mintRecipients[0] = ForeignControllerInit.MintRecipient({ domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, mintRecipient : bytes32(uint256(uint160(mainnetAlmProxy))) }); - ForeignControllerInit.init({ - addresses: ForeignControllerInit.AddressParams({ - admin : base.admin, - freezer : base.config.readAddress(".freezer"), - relayer : base.config.readAddress(".relayer"), - oldController : address(0), - psm : base.config.readAddress(".psm"), - cctpMessenger : base.config.readAddress(".cctpTokenMessenger"), - usdc : base.config.readAddress(".usdc"), - usds : base.config.readAddress(".usds"), - susds : base.config.readAddress(".susds") - }), - controllerInst: instance, - data: ForeignControllerInit.InitRateLimitData({ - usdcDepositData : rateLimitData6, - usdcWithdrawData : rateLimitData6, - usdsDepositData : rateLimitData18, - usdsWithdrawData : rateLimitData18, - susdsDepositData : rateLimitData18, - susdsWithdrawData : rateLimitData18, - usdcToCctpData : unlimitedRateLimit, - cctpToEthereumDomainData : rateLimitData6 - }), - mintRecipients: mintRecipients - }); + ForeignControllerInit.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + // Step 3: Set all rate limits for the controller + + _setBaseControllerRateLimits(controllerInst.rateLimits); vm.stopBroadcast(); - // Step 3: Export all deployed addresses + // Step 4: Export all deployed addresses - ScriptTools.exportContract(base.nameDeps, "freezer", base.config.readAddress(".freezer")); - ScriptTools.exportContract(base.nameDeps, "relayer", base.config.readAddress(".relayer")); + ScriptTools.exportContract(base.nameDeps, "freezer", base.config.readAddress(".freezer")); + ScriptTools.exportContract(base.nameDeps, "relayer", base.config.readAddress(".relayer")); - ScriptTools.exportContract(base.name, "almProxy", instance.almProxy); - ScriptTools.exportContract(base.name, "controller", instance.controller); - ScriptTools.exportContract(base.name, "rateLimits", instance.rateLimits); + ScriptTools.exportContract(base.name, "almProxy", controllerInst.almProxy); + ScriptTools.exportContract(base.name, "controller", controllerInst.controller); + ScriptTools.exportContract(base.name, "rateLimits", controllerInst.rateLimits); } function _setBaseMintRecipient() internal { @@ -432,7 +492,7 @@ contract FullStagingDeploy is Script { _setUpDependencies(); _setUpAllocationSystem(); - _setUpALMController(); + _setUpMainnetController(); _setUpBaseALMController(); _setBaseMintRecipient(); diff --git a/script/staging/test/StagingDeployment.t.sol b/script/staging/test/StagingDeployment.t.sol index a13fd70..973b678 100644 --- a/script/staging/test/StagingDeployment.t.sol +++ b/script/staging/test/StagingDeployment.t.sol @@ -28,7 +28,7 @@ import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBr import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { MainnetControllerDeploy } from "../../../deploy/ControllerDeploy.sol"; -import { MainnetControllerInit } from "../../../deploy/ControllerInit.sol"; +import { MainnetControllerInit } from "../../../deploy/MainnetControllerInit.sol"; import { IRateLimits } from "../../../src/interfaces/IRateLimits.sol"; @@ -36,6 +36,7 @@ import { ALMProxy } from "../../../src/ALMProxy.sol"; import { ForeignController } from "../../../src/ForeignController.sol"; import { MainnetController } from "../../../src/MainnetController.sol"; import { RateLimits } from "../../../src/RateLimits.sol"; + import { RateLimitHelpers } from "../../../src/RateLimitHelpers.sol"; interface IVatLike { @@ -55,7 +56,7 @@ contract StagingDeploymentTestBase is Test { bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - uint256 constant RELEASE_DATE = 20241210; + uint256 constant RELEASE_DATE = 20241227; // Common variables address admin; @@ -140,7 +141,7 @@ contract StagingDeploymentTestBase is Test { // ALM system almProxy = ALMProxy(payable(outputMainnet.readAddress(".almProxy"))); rateLimits = RateLimits(outputMainnet.readAddress(".rateLimits")); - mainnetController = _reconfigureMainnetController(); + mainnetController = MainnetController(outputMainnet.readAddress(".controller")); // Base roles relayerSafeBase = outputBaseDeps.readAddress(".relayer"); @@ -162,56 +163,6 @@ contract StagingDeploymentTestBase is Test { deal(address(usds), address(usdsJoin), 1000e18); // Ensure there is enough balance } - - // TODO: Remove this once a deployment has been done on mainnet - function _reconfigureMainnetController() internal returns (MainnetController newController) { - newController = MainnetController(MainnetControllerDeploy.deployController({ - admin : admin, - almProxy : address(almProxy), - rateLimits : address(rateLimits), - vault : address(vault), - psm : inputMainnet.readAddress(".psm"), - daiUsds : inputMainnet.readAddress(".daiUsds"), - cctp : inputMainnet.readAddress(".cctpTokenMessenger") - })); - - vm.startPrank(admin); - - newController.grantRole(newController.FREEZER(), inputMainnet.readAddress(".freezer")); - newController.grantRole(newController.RELAYER(), inputMainnet.readAddress(".relayer")); - - almProxy.grantRole(almProxy.CONTROLLER(), address(newController)); - rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController)); - - almProxy.revokeRole(almProxy.CONTROLLER(), outputMainnet.readAddress(".controller")); - rateLimits.revokeRole(rateLimits.CONTROLLER(), outputMainnet.readAddress(".controller")); - - newController.setMintRecipient( - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, - bytes32(uint256(uint160(address(outputBase.readAddress(".almProxy"))))) - ); - - // Set all rate limits - - bytes32[] memory rateLimitKeys = new bytes32[](10); - - rateLimitKeys[0] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_DEPOSIT(), AUSDS); - rateLimitKeys[1] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_DEPOSIT(), AUSDC); - rateLimitKeys[2] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDS); - rateLimitKeys[3] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDE); - rateLimitKeys[4] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_WITHDRAW(), AUSDS); - rateLimitKeys[5] = RateLimitHelpers.makeAssetKey(newController.LIMIT_AAVE_WITHDRAW(), AUSDC); - rateLimitKeys[6] = RateLimitHelpers.makeAssetKey(newController.LIMIT_4626_WITHDRAW(), Ethereum.SUSDS); - rateLimitKeys[7] = newController.LIMIT_USDE_MINT(); - rateLimitKeys[8] = newController.LIMIT_USDE_BURN(); - rateLimitKeys[9] = newController.LIMIT_SUSDE_COOLDOWN(); - - for (uint256 i; i < rateLimitKeys.length; i++) { - rateLimits.setUnlimitedRateLimitData(rateLimitKeys[i]); - } - - vm.stopPrank(); - } } contract MainnetStagingDeploymentTests is StagingDeploymentTestBase { @@ -307,15 +258,15 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase { vm.startPrank(relayerSafe); mainnetController.depositERC4626(Ethereum.SUSDE, 10e18); skip(1 days); - mainnetController.cooldownAssetsSUSDe(10e18); + mainnetController.cooldownAssetsSUSDe(10e18 - 1); // Rounding skip(7 days); mainnetController.unstakeSUSDe(); - mainnetController.prepareUSDeBurn(10e18); + mainnetController.prepareUSDeBurn(10e18 - 1); vm.stopPrank(); - _simulateUsdeBurn(10e18); + _simulateUsdeBurn(10e18 - 1); - assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Rounding not captured assertGe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); // Interest earned } @@ -342,7 +293,7 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase { _simulateUsdeBurn(usdeAmount); - assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned + assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Interest earned (rounding) assertEq(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); } @@ -389,21 +340,6 @@ contract BaseStagingDeploymentTests is StagingDeploymentTestBase { super.setUp(); base.selectFork(); - - bytes32[] memory rateLimitKeys = new bytes32[](4); - - rateLimitKeys[0] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_AAVE_DEPOSIT(), AUSDC_BASE); - rateLimitKeys[1] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_4626_DEPOSIT(), MORPHO_VAULT_USDC); - rateLimitKeys[2] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_AAVE_WITHDRAW(), AUSDC_BASE); - rateLimitKeys[3] = RateLimitHelpers.makeAssetKey(baseController.LIMIT_4626_WITHDRAW(), MORPHO_VAULT_USDC); - - vm.startPrank(admin); - - for (uint256 i; i < rateLimitKeys.length; i++) { - baseRateLimits.setUnlimitedRateLimitData(rateLimitKeys[i]); - } - - vm.stopPrank(); } function test_transferCCTP() public { diff --git a/src/RateLimitHelpers.sol b/src/RateLimitHelpers.sol index 873e54c..9e66d84 100644 --- a/src/RateLimitHelpers.sol +++ b/src/RateLimitHelpers.sol @@ -1,6 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; +import { IRateLimits } from "../src/interfaces/IRateLimits.sol"; + +struct RateLimitData { + uint256 maxAmount; + uint256 slope; +} + library RateLimitHelpers { function makeAssetKey(bytes32 key, address asset) internal pure returns (bytes32) { @@ -11,4 +18,40 @@ library RateLimitHelpers { return keccak256(abi.encode(key, domain)); } + function unlimitedRateLimit() internal pure returns (RateLimitData memory) { + return RateLimitData({ + maxAmount : type(uint256).max, + slope : 0 + }); + } + + function setRateLimitData( + bytes32 key, + address rateLimits, + RateLimitData memory data, + string memory name, + uint256 decimals + ) + internal + { + // Handle setting an unlimited rate limit + if (data.maxAmount == type(uint256).max) { + require( + data.slope == 0, + string(abi.encodePacked("RateLimitHelpers/invalid-rate-limit-", name)) + ); + } + else { + require( + data.maxAmount <= 1e12 * (10 ** decimals), + string(abi.encodePacked("RateLimitHelpers/invalid-max-amount-precision-", name)) + ); + require( + data.slope <= 1e12 * (10 ** decimals) / 1 hours, + string(abi.encodePacked("RateLimitHelpers/invalid-slope-precision-", name)) + ); + } + IRateLimits(rateLimits).setRateLimitData(key, data.maxAmount, data.slope); + } + } diff --git a/test/base-fork/Deploy.t.sol b/test/base-fork/Deploy.t.sol new file mode 100644 index 0000000..b12b879 --- /dev/null +++ b/test/base-fork/Deploy.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; +import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; + +import "./ForkTestBase.t.sol"; + +contract ForeignControllerDeploySuccessTests is ForkTestBase { + + function test_deployFull() external { + // Perform new deployments against existing fork environment + + ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({ + admin : Base.SPARK_EXECUTOR, + psm : Base.PSM3, + usdc : Base.USDC, + cctp : Base.CCTP_TOKEN_MESSENGER + }); + + ALMProxy newAlmProxy = ALMProxy(payable(controllerInst.almProxy)); + ForeignController newController = ForeignController(controllerInst.controller); + RateLimits newRateLimits = RateLimits(controllerInst.rateLimits); + + assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true); + assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin + + assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true); + assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin + + _assertControllerInitState(newController, address(newAlmProxy), address(newRateLimits)); + } + + function test_deployController() external { + // Perform new deployments against existing fork environment + + ForeignController newController = ForeignController(ForeignControllerDeploy.deployController({ + admin : Base.SPARK_EXECUTOR, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + psm : Base.PSM3, + usdc : Base.USDC, + cctp : Base.CCTP_TOKEN_MESSENGER + })); + + _assertControllerInitState(newController, address(almProxy), address(rateLimits)); + } + + function _assertControllerInitState(ForeignController controller, address almProxy, address rateLimits) internal view { + assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true); + assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin + + assertEq(address(controller.proxy()), almProxy); + assertEq(address(controller.rateLimits()), rateLimits); + assertEq(address(controller.psm()), Base.PSM3); + assertEq(address(controller.usdc()), Base.USDC); + assertEq(address(controller.cctp()), Base.CCTP_TOKEN_MESSENGER); + + assertEq(controller.active(), true); + } + +} diff --git a/test/base-fork/DeployAndInit.t.sol b/test/base-fork/DeployAndInit.t.sol index 776c768..8fded8e 100644 --- a/test/base-fork/DeployAndInit.t.sol +++ b/test/base-fork/DeployAndInit.t.sol @@ -1,113 +1,69 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; +import "../../test/base-fork/ForkTestBase.t.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; -import { ForeignControllerInit, RateLimitData, MintRecipient } from "../../deploy/ControllerInit.sol"; - -import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; - -import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; - -import "./ForkTestBase.t.sol"; +import { ForeignControllerInit as Init } from "../../deploy/ForeignControllerInit.sol"; // Necessary to get error message assertions to work contract LibraryWrapper { - function init( - ForeignControllerInit.AddressParams memory params, - ControllerInstance memory controllerInst, - ForeignControllerInit.InitRateLimitData memory data, - MintRecipient[] memory mintRecipients + function initAlmSystem( + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients + ) + external + { + Init.initAlmSystem(controllerInst, configAddresses, checkAddresses, mintRecipients); + } + + function upgradeController( + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients ) external { - ForeignControllerInit.init(params, controllerInst, data, mintRecipients); + Init.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); } } -contract ForeignControllerDeployAndInitTestBase is ForkTestBase { +contract ForeignControllerInitAndUpgradeTestBase is ForkTestBase { - // Default params used for all testing, can be overridden where needed. function _getDefaultParams() internal returns ( - ForeignControllerInit.AddressParams memory addresses, - ForeignControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients ) { - addresses = ForeignControllerInit.AddressParams({ - admin : SPARK_EXECUTOR, + configAddresses = Init.ConfigAddressParams({ freezer : freezer, relayer : relayer, - oldController : address(0), // Empty - psm : address(psmBase), - cctpMessenger : CCTP_MESSENGER_BASE, - usdc : USDC_BASE, - usds : address(usdsBase), - susds : address(susdsBase) + oldController : address(0) }); - RateLimitData memory usdcDepositData = RateLimitData({ - maxAmount : 1_000_000e6, - slope : uint256(1_000_000e6) / 4 hours + checkAddresses = Init.CheckAddressParams({ + admin : Base.SPARK_EXECUTOR, + psm : address(psmBase), + cctp : Base.CCTP_TOKEN_MESSENGER, + usdc : address(usdcBase), + susds : address(susdsBase), + usds : address(usdsBase) }); - RateLimitData memory usdcWithdrawData = RateLimitData({ - maxAmount : 2_000_000e6, - slope : uint256(2_000_000e6) / 4 hours - }); - - RateLimitData memory usdsDepositData = RateLimitData({ - maxAmount : 3_000_000e6, - slope : uint256(3_000_000e6) / 4 hours - }); - - RateLimitData memory usdsWithdrawData = RateLimitData({ - maxAmount : 4_000_000e6, - slope : uint256(4_000_000e6) / 4 hours - }); - - RateLimitData memory susdsDepositData = RateLimitData({ - maxAmount : 5_000_000e6, - slope : uint256(5_000_000e6) / 4 hours - }); - - RateLimitData memory susdsWithdrawData = RateLimitData({ - maxAmount : 6_000_000e6, - slope : uint256(6_000_000e6) / 4 hours - }); + mintRecipients = new Init.MintRecipient[](1); - RateLimitData memory usdcToCctpData = RateLimitData({ - maxAmount : 7_000_000e6, - slope : uint256(7_000_000e6) / 4 hours - }); - - RateLimitData memory cctpToEthereumDomainData = RateLimitData({ - maxAmount : 8_000_000e6, - slope : uint256(8_000_000e6) / 4 hours - }); - - rateLimitData = ForeignControllerInit.InitRateLimitData({ - usdcDepositData : usdcDepositData, - usdcWithdrawData : usdcWithdrawData, - usdsDepositData : usdsDepositData, - usdsWithdrawData : usdsWithdrawData, - susdsDepositData : susdsDepositData, - susdsWithdrawData : susdsWithdrawData, - usdcToCctpData : usdcToCctpData, - cctpToEthereumDomainData : cctpToEthereumDomainData - }); - - mintRecipients = new MintRecipient[](1); - - mintRecipients[0] = MintRecipient({ + mintRecipients[0] = Init.MintRecipient({ domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) }); @@ -115,160 +71,163 @@ contract ForeignControllerDeployAndInitTestBase is ForkTestBase { } -contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAndInitTestBase { +contract ForeignControllerInitAndUpgradeFailureTest is ForeignControllerInitAndUpgradeTestBase { + + // NOTE: `initAlmSystem` and `upgradeController` are tested in the same contract because + // they both use _initController and have similar specific setups, so it + // less complex/repetitive to test them together. LibraryWrapper wrapper; ControllerInstance public controllerInst; address public mismatchAddress = makeAddr("mismatchAddress"); + + address public oldController; - // Default parameters for success that are overridden for failure tests - - ForeignControllerInit.AddressParams addresses; - ForeignControllerInit.InitRateLimitData rateLimitData; - MintRecipient[] mintRecipients; + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; function setUp() public override { super.setUp(); - controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); + oldController = address(foreignController); // Cache for later testing + + // Deploy new controller against existing system + // NOTE: initAlmSystem will redundantly call rely and approve on already inited + // almProxy and rateLimits, this setup was chosen to easily test upgrade and init failures + foreignController = ForeignController(ForeignControllerDeploy.deployController({ + admin : Base.SPARK_EXECUTOR, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + psm : address(psmBase), + usdc : address(usdcBase), + cctp : Base.CCTP_TOKEN_MESSENGER + })); - MintRecipient[] memory mintRecipients_ = new MintRecipient[](1); + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); - ( addresses, rateLimitData, mintRecipients_ ) = _getDefaultParams(); + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); // NOTE: This would need to be refactored to a for loop if more than one recipient mintRecipients.push(mintRecipients_[0]); - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - foreignController = ForeignController(controllerInst.controller); - rateLimits = RateLimits(controllerInst.rateLimits); + controllerInst = ControllerInstance({ + almProxy : address(almProxy), + controller : address(foreignController), + rateLimits : address(rateLimits) + }); // Admin will be calling the library from its own address - vm.etch(SPARK_EXECUTOR, address(new LibraryWrapper()).code); + vm.etch(Base.SPARK_EXECUTOR, address(new LibraryWrapper()).code); + + wrapper = LibraryWrapper(Base.SPARK_EXECUTOR); + } - wrapper = LibraryWrapper(SPARK_EXECUTOR); + function _getBlock() internal pure override returns (uint256) { + return 23900000; // Dec 19, 2024 } /**********************************************************************************************/ - /*** ACL failure modes ***/ + /*** ACL tests ***/ /**********************************************************************************************/ - function test_init_incorrectAdminAlmProxy() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_EXECUTOR); - almProxy.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); - almProxy.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR); - vm.stopPrank(); + function test_initAlmSystem_upgradeController_incorrectAdminAlmProxy() external { + vm.prank(Base.SPARK_EXECUTOR); + almProxy.revokeRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR); vm.expectRevert("ForeignControllerInit/incorrect-admin-almProxy"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_incorrectAdminRateLimits() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_EXECUTOR); - rateLimits.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); - rateLimits.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR); - vm.stopPrank(); + function test_initAlmSystem_upgradeController_incorrectAdminRateLimits() external { + vm.prank(Base.SPARK_EXECUTOR); + rateLimits.revokeRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR); vm.expectRevert("ForeignControllerInit/incorrect-admin-rateLimits"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_incorrectAdminController() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_EXECUTOR); - foreignController.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); - foreignController.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR); - vm.stopPrank(); + function test_initAlmSystem_upgradeController_incorrectAdminController() external { + vm.prank(Base.SPARK_EXECUTOR); + foreignController.revokeRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR); - vm.expectRevert("ForeignControllerInit/incorrect-admin-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-admin-controller")); } /**********************************************************************************************/ - /*** Controller constructor failure modes ***/ + /*** Constructor tests ***/ /**********************************************************************************************/ - function test_init_incorrectAlmProxy() external { + function test_initAlmSystem_upgradeController_incorrectAlmProxy() external { // Deploy new address that will not EVM revert on OZ ACL check - controllerInst.almProxy = address(new ALMProxy(SPARK_EXECUTOR)); + controllerInst.almProxy = address(new ALMProxy(Base.SPARK_EXECUTOR)); - vm.expectRevert("ForeignControllerInit/incorrect-almProxy"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-almProxy")); } - function test_init_incorrectRateLimits() external { + function test_initAlmSystem_upgradeController_incorrectRateLimits() external { // Deploy new address that will not EVM revert on OZ ACL check - controllerInst.rateLimits = address(new RateLimits(SPARK_EXECUTOR)); + controllerInst.rateLimits = address(new RateLimits(Base.SPARK_EXECUTOR)); - vm.expectRevert("ForeignControllerInit/incorrect-rateLimits"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-rateLimits")); } - function test_init_incorrectPsm() external { - addresses.psm = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-psm"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + function test_initAlmSystem_upgradeController_incorrectPsm() external { + checkAddresses.psm = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-psm")); } - function test_init_incorrectUsdc() external { - addresses.usdc = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-usdc"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + function test_initAlmSystem_upgradeController_incorrectUsdc() external { + checkAddresses.usdc = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-usdc")); } - function test_init_incorrectCctp() external { - addresses.cctpMessenger = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-cctp"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + function test_initAlmSystem_upgradeController_incorrectCctp() external { + checkAddresses.cctp = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-cctp")); } - function test_init_controllerInactive() external { + function test_initAlmSystem_upgradeController_controllerInactive() external { // Cheating to set this outside of init scripts so that the controller can be frozen - vm.prank(SPARK_EXECUTOR); + vm.startPrank(Base.SPARK_EXECUTOR); foreignController.grantRole(FREEZER, freezer); vm.startPrank(freezer); foreignController.freeze(); vm.stopPrank(); - vm.expectRevert("ForeignControllerInit/controller-not-active"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/controller-not-active")); + } + + function test_initAlmSystem_upgradeController_oldControllerIsNewController() external { + configAddresses.oldController = controllerInst.controller; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/old-controller-is-new-controller")); } /**********************************************************************************************/ - /*** Sanity check failure modes ***/ + /*** PSM tests ***/ /**********************************************************************************************/ - function test_init_oldControllerIsNewController() external { - addresses.oldController = controllerInst.controller; - - vm.expectRevert("ForeignControllerInit/old-controller-is-new-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_totalAssetsNotSeededBoundary() external { + function test_initAlmSystem_upgradeController_totalAssetsNotSeededBoundary() external { // Remove one wei from PSM to make seeded condition not met vm.prank(address(0)); psmBase.withdraw(address(usdsBase), address(this), 1); // Withdraw one wei from PSM assertEq(psmBase.totalAssets(), 1e18 - 1); - vm.expectRevert("ForeignControllerInit/psm-totalAssets-not-seeded"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-totalAssets-not-seeded")); // Approve from address(this) cause it received the one wei // Redo the seeding @@ -277,10 +236,10 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn assertEq(psmBase.totalAssets(), 1e18); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeSucceed(); } - function test_init_totalSharesNotSeededBoundary() external { + function test_initAlmSystem_upgradeController_totalSharesNotSeededBoundary() external { // Remove one wei from PSM to make seeded condition not met vm.prank(address(0)); psmBase.withdraw(address(usdsBase), address(this), 1); // Withdraw one wei from PSM @@ -290,8 +249,7 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn assertEq(psmBase.totalAssets(), 1e18); assertEq(psmBase.totalShares(), 1e18 - 1); - vm.expectRevert("ForeignControllerInit/psm-totalShares-not-seeded"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-totalShares-not-seeded")); // Do deposit to update shares, need to do 2 wei to get back to 1e18 because of rounding deal(address(usdsBase), address(this), 2); @@ -301,10 +259,10 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn assertEq(psmBase.totalAssets(), 1e18 + 2); assertEq(psmBase.totalShares(), 1e18); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeSucceed(); } - function test_init_incorrectPsmUsdc() external { + function test_initAlmSystem_upgradeController_incorrectPsmUsdc() external { ERC20Mock wrongUsdc = new ERC20Mock(); deal(address(usdsBase), address(this), 1e18); // For seeding PSM during deployment @@ -322,18 +280,17 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn CCTP_MESSENGER_BASE ); - addresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - vm.expectRevert("ForeignControllerInit/psm-incorrect-usdc"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-usdc")); } - function test_init_incorrectPsmUsds() external { + function test_initAlmSystem_upgradeController_incorrectPsmUsds() external { ERC20Mock wrongUsds = new ERC20Mock(); deal(address(wrongUsds), address(this), 1e18); // For seeding PSM during deployment - // Deploy a new PSM with the wrong USDC + // Deploy a new PSM with the wrong USDS psmBase = IPSM3(PSM3Deploy.deploy( SPARK_EXECUTOR, USDC_BASE, address(wrongUsds), address(susdsBase), SSR_ORACLE )); @@ -346,18 +303,17 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn CCTP_MESSENGER_BASE ); - addresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - vm.expectRevert("ForeignControllerInit/psm-incorrect-usds"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-usds")); } - function test_init_incorrectPsmSUsds() external { + function test_initAlmSystem_upgradeController_incorrectPsmSUsds() external { ERC20Mock wrongSUsds = new ERC20Mock(); deal(address(usdsBase), address(this), 1e18); // For seeding PSM during deployment - // Deploy a new PSM with the wrong USDC + // Deploy a new PSM with the wrong SUSDS psmBase = IPSM3(PSM3Deploy.deploy( SPARK_EXECUTOR, USDC_BASE, address(usdsBase), address(wrongSUsds), SSR_ORACLE )); @@ -370,428 +326,174 @@ contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAn CCTP_MESSENGER_BASE ); - addresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - - vm.expectRevert("ForeignControllerInit/psm-incorrect-susds"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - /**********************************************************************************************/ - /*** Rate limit unlimited boundary failure modes ***/ - /**********************************************************************************************/ - - function test_init_incorrectUsdcDepositData_unlimitedBoundary() external { - rateLimitData.usdcDepositData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-usdcDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcDepositData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcWithdrawData_unlimitedBoundary() external { - rateLimitData.usdcWithdrawData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-usdcWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcWithdrawData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsDepositData_unlimitedBoundary() external { - rateLimitData.usdsDepositData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-usdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsDepositData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsWithdrawData_unlimitedBoundary() external { - rateLimitData.usdsWithdrawData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-usdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsWithdrawData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsDepositData_unlimitedBoundary() external { - rateLimitData.susdsDepositData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-susdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsDepositData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsWithdrawData_unlimitedBoundary() external { - rateLimitData.susdsWithdrawData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-susdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsWithdrawData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcToCctpData_unlimitedBoundary() external { - rateLimitData.usdcToCctpData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-usdcToCctpData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcToCctpData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectCctpToEthereumDomainData_unlimitedBoundary() external { - rateLimitData.cctpToEthereumDomainData.maxAmount = type(uint256).max; - - vm.expectRevert("ForeignControllerInit/invalid-rate-limit-cctpToEthereumDomainData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.cctpToEthereumDomainData.slope = 0; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - /**********************************************************************************************/ - /*** Rate limit max amount precision boundary failure modes ***/ - /**********************************************************************************************/ - - function test_init_incorrectUsdcDepositData_maxAmountPrecisionBoundary() external { - rateLimitData.usdcDepositData.maxAmount = 1e18 + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-usdcDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcDepositData.maxAmount = 1e18; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcWithdrawData_maxAmountPrecisionBoundary() external { - rateLimitData.usdcWithdrawData.maxAmount = 1e18 + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-usdcWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcWithdrawData.maxAmount = 1e18; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsDepositData_maxAmountPrecisionBoundary() external { - rateLimitData.usdsDepositData.maxAmount = 1e30 + 1; - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-usdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsDepositData.maxAmount = 1e30; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsWithdrawData_maxAmountPrecisionBoundary() external { - rateLimitData.usdsWithdrawData.maxAmount = 1e30 + 1; - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-usdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsWithdrawData.maxAmount = 1e30; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsDepositData_maxAmountPrecisionBoundary() external { - rateLimitData.susdsDepositData.maxAmount = 1e30 + 1; - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-susdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsDepositData.maxAmount = 1e30; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsWithdrawData_maxAmountPrecisionBoundary() external { - rateLimitData.susdsWithdrawData.maxAmount = 1e30 + 1; - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-susdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsWithdrawData.maxAmount = 1e30; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcToCctpData_maxAmountPrecisionBoundary() external { - rateLimitData.usdcToCctpData.maxAmount = 1e18 + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-usdcToCctpData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcToCctpData.maxAmount = 1e18; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectCctpToEthereumDomainData_maxAmountPrecisionBoundary() external { - rateLimitData.cctpToEthereumDomainData.maxAmount = 1e18 + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-max-amount-precision-cctpToEthereumDomainData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.cctpToEthereumDomainData.maxAmount = 1e18; + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-susds")); } /**********************************************************************************************/ - /*** Rate limit slope precision boundary failure modes ***/ + /*** Upgrade tests ***/ /**********************************************************************************************/ - function test_init_incorrectUsdcDepositData_slopePrecisionBoundary() external { - rateLimitData.usdcDepositData.slope = uint256(1e18) / 1 hours + 1; // 1 USDS, but 1 trillion USDC + function test_upgradeController_oldControllerZeroAddress() external { + configAddresses.oldController = address(0); - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-usdcDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcDepositData.slope = uint256(1e18) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcWithdrawData_slopePrecisionBoundary() external { - rateLimitData.usdcWithdrawData.slope = uint256(1e18) / 1 hours + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-usdcWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcWithdrawData.slope = uint256(1e18) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsDepositData_slopePrecisionBoundary() external { - rateLimitData.usdsDepositData.slope = uint256(1e30) / 1 hours + 1; - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-usdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsDepositData.slope = uint256(1e30) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdsWithdrawData_slopePrecisionBoundary() external { - rateLimitData.usdsWithdrawData.slope = uint256(1e30) / 1 hours + 1; - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-usdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdsWithdrawData.slope = uint256(1e30) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsDepositData_slopePrecisionBoundary() external { - rateLimitData.susdsDepositData.slope = uint256(1e30) / 1 hours + 1; - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-susdsDepositData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsDepositData.slope = uint256(1e30) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectSUsdsWithdrawData_slopePrecisionBoundary() external { - rateLimitData.susdsWithdrawData.slope = uint256(1e30) / 1 hours + 1; - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-susdsWithdrawData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.susdsWithdrawData.slope = uint256(1e30) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdcToCctpData_slopePrecisionBoundary() external { - rateLimitData.usdcToCctpData.slope = uint256(1e18) / 1 hours + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-usdcToCctpData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.usdcToCctpData.slope = uint256(1e18) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectCctpToEthereumDomainData_slopePrecisionBoundary() external { - rateLimitData.cctpToEthereumDomainData.slope = uint256(1e18) / 1 hours + 1; // 1 USDS, but 1 trillion USDC - - vm.expectRevert("ForeignControllerInit/invalid-slope-precision-cctpToEthereumDomainData"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - rateLimitData.cctpToEthereumDomainData.slope = uint256(1e18) / 1 hours; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + vm.expectRevert("ForeignControllerInit/old-controller-zero-address"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - /**********************************************************************************************/ - /*** Old controller role check tests ***/ - /**********************************************************************************************/ - - function test_init_oldControllerDoesNotHaveRoleInAlmProxy() external { - _deployNewControllerAfterExistingControllerInit(); + function test_upgradeController_oldControllerDoesNotHaveRoleInAlmProxy() external { + configAddresses.oldController = oldController; // Revoke the old controller address in ALM proxy + vm.startPrank(Base.SPARK_EXECUTOR); + almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController); + vm.stopPrank(); - vm.startPrank(SPARK_EXECUTOR); - almProxy.revokeRole(almProxy.CONTROLLER(), addresses.oldController); - vm.stopPrank(); - - // Try to init with the old controller address that is doesn't have the CONTROLLER role - + // Try to upgrade with the old controller address that is doesn't have the CONTROLLER role vm.expectRevert("ForeignControllerInit/old-controller-not-almProxy-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_oldControllerDoesNotHaveRoleInRateLimits() external { - _deployNewControllerAfterExistingControllerInit(); + function test_upgradeController_oldControllerDoesNotHaveRoleInRateLimits() external { + configAddresses.oldController = oldController; - // Revoke the old controller address - - vm.startPrank(SPARK_EXECUTOR); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.oldController); + // Revoke the old controller address in rate limits + vm.startPrank(Base.SPARK_EXECUTOR); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); vm.stopPrank(); - // Try to init with the old controller address that is doesn't have the CONTROLLER role - + // Try to upgrade with the old controller address that is doesn't have the CONTROLLER role vm.expectRevert("ForeignControllerInit/old-controller-not-rateLimits-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } /**********************************************************************************************/ /*** Helper functions ***/ /**********************************************************************************************/ - function _deployNewControllerAfterExistingControllerInit() internal { - // Successfully init first controller + function _checkInitAndUpgradeFail(bytes memory expectedError) internal { + vm.expectRevert(expectedError); + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, + vm.expectRevert(expectedError); + wrapper.upgradeController( controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); - vm.stopPrank(); + } - // Deploy a new controller (controllerInst is used in init with new controller address) + function _checkInitAndUpgradeSucceed() internal { + uint256 id = vm.snapshot(); - controllerInst.controller = ForeignControllerDeploy.deployController( - SPARK_EXECUTOR, - controllerInst.almProxy, - controllerInst.rateLimits, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients ); - addresses.oldController = address(foreignController); + vm.revertTo(id); + + configAddresses.oldController = oldController; + + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } } -contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAndInitTestBase { +contract ForeignControllerInitAlmSystemSuccessTests is ForeignControllerInitAndUpgradeTestBase { - function test_deployAllAndInit() external { - // Perform new deployments against existing fork environment + LibraryWrapper wrapper; - ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, + ControllerInstance public controllerInst; + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + function setUp() public override { + super.setUp(); + + controllerInst = ForeignControllerDeploy.deployFull( + Base.SPARK_EXECUTOR, address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE + address(usdcBase), + Base.CCTP_TOKEN_MESSENGER ); - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); + // Overwrite storage for all previous deployments in setUp and assert brand new deployment foreignController = ForeignController(controllerInst.controller); + almProxy = ALMProxy(payable(controllerInst.almProxy)); rateLimits = RateLimits(controllerInst.rateLimits); - assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR), true); - assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR), true); - assertEq(foreignController.hasRole(DEFAULT_ADMIN_ROLE, SPARK_EXECUTOR), true); + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); + + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); - assertEq(address(foreignController.proxy()), controllerInst.almProxy); - assertEq(address(foreignController.rateLimits()), controllerInst.rateLimits); - assertEq(address(foreignController.psm()), address(psmBase)); - assertEq(address(foreignController.usdc()), USDC_BASE); - assertEq(address(foreignController.cctp()), CCTP_MESSENGER_BASE); + mintRecipients.push(mintRecipients_[0]); + + // Admin will be calling the library from its own address + vm.etch(Base.SPARK_EXECUTOR, address(new LibraryWrapper()).code); + + wrapper = LibraryWrapper(Base.SPARK_EXECUTOR); + } + + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } - assertEq(foreignController.active(), true); + function test_initAlmSystem() public { + assertEq(foreignController.hasRole(foreignController.FREEZER(), freezer), false); + assertEq(foreignController.hasRole(foreignController.RELAYER(), relayer), false); - // Perform SubDAO initialization (from governance relay during spell) - // Setting rate limits to different values from setUp to make assertions more robust + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), false); - ( - ForeignControllerInit.AddressParams memory addresses, - ForeignControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); + assertEq(foreignController.mintRecipients(mintRecipients[0].domain), bytes32(0)); + assertEq(foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), bytes32(0)); - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, + vm.startPrank(Base.SPARK_EXECUTOR); + wrapper.initAlmSystem( controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); - vm.stopPrank(); - - // Assert SubDAO initialization assertEq(foreignController.hasRole(foreignController.FREEZER(), freezer), true); assertEq(foreignController.hasRole(foreignController.RELAYER(), relayer), true); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), true); - + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), true); assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), true); - bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( - foreignController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM - ); - - _assertDepositRateLimitData(usdcBase, rateLimitData.usdcDepositData); - _assertDepositRateLimitData(usdsBase, rateLimitData.usdsDepositData); - _assertDepositRateLimitData(susdsBase, rateLimitData.susdsDepositData); - - _assertWithdrawRateLimitData(usdcBase, rateLimitData.usdcWithdrawData); - _assertWithdrawRateLimitData(usdsBase, rateLimitData.usdsWithdrawData); - _assertWithdrawRateLimitData(susdsBase, rateLimitData.susdsWithdrawData); - - _assertRateLimitData(foreignController.LIMIT_USDC_TO_CCTP(), rateLimitData.usdcToCctpData); - - _assertRateLimitData(domainKeyEthereum, rateLimitData.cctpToEthereumDomainData); - assertEq( foreignController.mintRecipients(mintRecipients[0].domain), mintRecipients[0].mintRecipient @@ -803,98 +505,95 @@ contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAn ); } - function test_init_transferAclToNewController() public { - ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); +} - ( - ForeignControllerInit.AddressParams memory addresses, - ForeignControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); +contract ForeignControllerUpgradeControllerSuccessTests is ForeignControllerInitAndUpgradeTestBase { - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); + LibraryWrapper wrapper; - // Example of how an upgrade would work - address newController = ForeignControllerDeploy.deployController( - SPARK_EXECUTOR, - controllerInst.almProxy, - controllerInst.rateLimits, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); + ControllerInstance public controllerInst; - // Overwrite storage of previous deployments in setUp + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; - almProxy = ALMProxy(payable(controllerInst.almProxy)); - rateLimits = RateLimits(controllerInst.rateLimits); + ForeignController newController; - address oldController = address(controllerInst.controller); + function setUp() public override { + super.setUp(); - controllerInst.controller = newController; // Overwrite struct for param + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); - // All other info is the same, just need to transfer ACL - addresses.oldController = oldController; + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), true); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), true); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), false); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), false); + mintRecipients.push(mintRecipients_[0]); - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); + newController = ForeignController(ForeignControllerDeploy.deployController({ + admin : Base.SPARK_EXECUTOR, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + psm : address(psmBase), + usdc : address(usdcBase), + cctp : Base.CCTP_TOKEN_MESSENGER + })); + + controllerInst = ControllerInstance({ + almProxy : address(almProxy), + controller : address(newController), + rateLimits : address(rateLimits) + }); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), false); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), false); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); - } + configAddresses.oldController = address(foreignController); // Revoke from old controller - function _assertDepositRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal view { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_DEPOSIT(), - address(asset) - ); + // Admin will be calling the library from its own address + vm.etch(Base.SPARK_EXECUTOR, address(new LibraryWrapper()).code); + + wrapper = LibraryWrapper(Base.SPARK_EXECUTOR); + } - _assertRateLimitData(assetKey, expectedData); + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 } - function _assertWithdrawRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal view { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_WITHDRAW(), - address(asset) + function test_upgradeController() public { + assertEq(newController.hasRole(newController.FREEZER(), freezer), false); + assertEq(newController.hasRole(newController.RELAYER(), relayer), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), true); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), false); + + assertEq(newController.mintRecipients(mintRecipients[0].domain), bytes32(0)); + assertEq(newController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), bytes32(0)); + + vm.startPrank(Base.SPARK_EXECUTOR); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients ); - _assertRateLimitData(assetKey, expectedData); - } + assertEq(newController.hasRole(newController.FREEZER(), freezer), true); + assertEq(newController.hasRole(newController.RELAYER(), relayer), true); - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal view { - IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), false); - assertEq(data.maxAmount, expectedData.maxAmount); - assertEq(data.slope, expectedData.slope); - assertEq(data.lastAmount, expectedData.maxAmount); // `lastAmount` should be `maxAmount` - assertEq(data.lastUpdated, block.timestamp); + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), true); - assertEq(rateLimits.getCurrentRateLimit(domainKey), expectedData.maxAmount); + assertEq( + newController.mintRecipients(mintRecipients[0].domain), + mintRecipients[0].mintRecipient + ); + + assertEq( + newController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), + bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) + ); } } diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 4ec6caf..6021b88 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -17,16 +17,14 @@ import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; -import { - ForeignControllerInit, - MintRecipient, - RateLimitData -} from "../../deploy/ControllerInit.sol"; +import { ForeignControllerInit as Init } from "../../deploy/ForeignControllerInit.sol"; import { ALMProxy } from "../../src/ALMProxy.sol"; import { ForeignController } from "../../src/ForeignController.sol"; import { RateLimits } from "../../src/RateLimits.sol"; +import { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; + contract ForkTestBase is Test { // TODO: Refactor to use live addresses @@ -41,9 +39,10 @@ contract ForkTestBase is Test { bytes32 FREEZER; bytes32 RELAYER; - address freezer = makeAddr("freezer"); - address pocket = makeAddr("pocket"); - address relayer = makeAddr("relayer"); + address freezer = Base.ALM_FREEZER; + address relayer = Base.ALM_RELAYER; + + address pocket = makeAddr("pocket"); /**********************************************************************************************/ /*** Base addresses ***/ @@ -99,7 +98,7 @@ contract ForkTestBase is Test { vm.prank(pocket); usdcBase.approve(address(psmBase), type(uint256).max); - /*** Step 3: Deploy and configure ALM system ***/ + /*** Step 3: Deploy ALM system ***/ ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({ admin : SPARK_EXECUTOR, @@ -116,54 +115,74 @@ contract ForkTestBase is Test { FREEZER = foreignController.FREEZER(); RELAYER = foreignController.RELAYER(); - ForeignControllerInit.AddressParams memory addresses = ForeignControllerInit.AddressParams({ - admin : SPARK_EXECUTOR, + /*** Step 3: Configure ALM system through Spark governance (Spark spell payload) ***/ + + + Init.ConfigAddressParams memory configAddresses = Init.ConfigAddressParams({ freezer : freezer, relayer : relayer, - oldController : address(0), // Empty - psm : address(psmBase), - cctpMessenger : CCTP_MESSENGER_BASE, - usdc : USDC_BASE, - usds : address(usdsBase), - susds : address(susdsBase) + oldController : address(0) + }); + + Init.CheckAddressParams memory checkAddresses = Init.CheckAddressParams({ + admin : Base.SPARK_EXECUTOR, + psm : address(psmBase), + cctp : Base.CCTP_TOKEN_MESSENGER, + usdc : address(usdcBase), + susds : address(susdsBase), + usds : address(usdsBase) + }); + + Init.MintRecipient[] memory mintRecipients = new Init.MintRecipient[](1); + + mintRecipients[0] = Init.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, + mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) }); - RateLimitData memory standardUsdcRateLimitData = RateLimitData({ + vm.startPrank(SPARK_EXECUTOR); + + Init.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + RateLimitData memory standardUsdcData = RateLimitData({ maxAmount : 5_000_000e6, slope : uint256(1_000_000e6) / 4 hours }); - RateLimitData memory standardUsdsRateLimitData = RateLimitData({ + RateLimitData memory standardUsdsData = RateLimitData({ maxAmount : 5_000_000e18, slope : uint256(1_000_000e18) / 4 hours }); - RateLimitData memory unlimitedRateLimitData = RateLimitData({ + RateLimitData memory unlimitedData = RateLimitData({ maxAmount : type(uint256).max, slope : 0 }); - ForeignControllerInit.InitRateLimitData memory rateLimitData - = ForeignControllerInit.InitRateLimitData({ - usdcDepositData : standardUsdcRateLimitData, - usdcWithdrawData : standardUsdcRateLimitData, - usdsDepositData : standardUsdsRateLimitData, - usdsWithdrawData : unlimitedRateLimitData, - susdsDepositData : standardUsdsRateLimitData, - susdsWithdrawData : unlimitedRateLimitData, - usdcToCctpData : standardUsdcRateLimitData, - cctpToEthereumDomainData : standardUsdcRateLimitData - }); - - MintRecipient[] memory mintRecipients = new MintRecipient[](1); - - mintRecipients[0] = MintRecipient({ - domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, - mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) - }); + bytes32 depositKey = foreignController.LIMIT_PSM_DEPOSIT(); + bytes32 withdrawKey = foreignController.LIMIT_PSM_WITHDRAW(); + + bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( + foreignController.LIMIT_USDC_TO_DOMAIN(), + CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM + ); + + // NOTE: Using minimal config for test base setup + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(usdcBase)), address(rateLimits), standardUsdcData, "usdcDepositData", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(usdcBase)), address(rateLimits), standardUsdcData, "usdcWithdrawData", 6); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(usdsBase)), address(rateLimits), standardUsdsData, "usdsDepositData", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(usdsBase)), address(rateLimits), unlimitedData, "usdsWithdrawData", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(susdsBase)), address(rateLimits), standardUsdsData, "susdsDepositData", 18); + RateLimitHelpers.setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(susdsBase)), address(rateLimits), unlimitedData, "susdsWithdrawData", 18); + + RateLimitHelpers.setRateLimitData(foreignController.LIMIT_USDC_TO_CCTP(), address(rateLimits), standardUsdcData, "usdcToCctpData", 6); + RateLimitHelpers.setRateLimitData(domainKeyEthereum, address(rateLimits), standardUsdcData, "cctpToEthereumDomainData", 6); - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init(addresses, controllerInst, rateLimitData, mintRecipients); vm.stopPrank(); } diff --git a/test/mainnet-fork/4626Calls.t.sol b/test/mainnet-fork/4626Calls.t.sol index dc55a57..d607f82 100644 --- a/test/mainnet-fork/4626Calls.t.sol +++ b/test/mainnet-fork/4626Calls.t.sol @@ -16,15 +16,12 @@ contract SUSDSTestBase is ForkTestBase { function setUp() override public { super.setUp(); + bytes32 depositKey = RateLimitHelpers.makeAssetKey(mainnetController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDS); + bytes32 withdrawKey = RateLimitHelpers.makeAssetKey(mainnetController.LIMIT_4626_WITHDRAW(), Ethereum.SUSDS); + vm.startPrank(Ethereum.SPARK_PROXY); - rateLimits.setRateLimitData( - RateLimitHelpers.makeAssetKey( - mainnetController.LIMIT_4626_WITHDRAW(), - Ethereum.SUSDS - ), - 5_000_000e18, - uint256(5_000_000e18) / 1 days - ); + rateLimits.setRateLimitData(depositKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours); + rateLimits.setRateLimitData(withdrawKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours); vm.stopPrank(); SUSDS_CONVERTED_ASSETS = susds.convertToAssets(1e18); diff --git a/test/mainnet-fork/CCTPCalls.t.sol b/test/mainnet-fork/CCTPCalls.t.sol index 41b9401..22f08f8 100644 --- a/test/mainnet-fork/CCTPCalls.t.sol +++ b/test/mainnet-fork/CCTPCalls.t.sol @@ -17,10 +17,7 @@ import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.s import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; -import { ForeignControllerInit, - MintRecipient, - RateLimitData -} from "../../deploy/ControllerInit.sol"; +import { ForeignControllerInit } from "../../deploy/ForeignControllerInit.sol"; import { ALMProxy } from "../../src/ALMProxy.sol"; import { ForeignController } from "../../src/ForeignController.sol"; @@ -216,54 +213,63 @@ contract BaseChainUSDCToCCTPTestBase is ForkTestBase { foreignRateLimits = RateLimits(controllerInst.rateLimits); foreignController = ForeignController(controllerInst.controller); - ForeignControllerInit.AddressParams memory addresses = ForeignControllerInit.AddressParams({ - admin : SPARK_EXECUTOR, + ForeignControllerInit.ConfigAddressParams memory configAddresses = ForeignControllerInit.ConfigAddressParams({ freezer : freezer, relayer : relayer, - oldController : address(0), // Empty - psm : address(psmBase), - cctpMessenger : CCTP_MESSENGER_BASE, - usdc : USDC_BASE, - usds : address(usdsBase), - susds : address(susdsBase) + oldController : address(0) }); - RateLimitData memory standardUsdcRateLimitData = RateLimitData({ + ForeignControllerInit.CheckAddressParams memory checkAddresses = ForeignControllerInit.CheckAddressParams({ + admin : Base.SPARK_EXECUTOR, + psm : address(psmBase), + cctp : Base.CCTP_TOKEN_MESSENGER, + usdc : address(usdcBase), + susds : address(susdsBase), + usds : address(usdsBase) + }); + + ForeignControllerInit.MintRecipient[] memory mintRecipients = new ForeignControllerInit.MintRecipient[](1); + + mintRecipients[0] = ForeignControllerInit.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, + mintRecipient : bytes32(uint256(uint160(address(almProxy)))) + }); + + vm.startPrank(SPARK_EXECUTOR); + + ForeignControllerInit.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + RateLimitData memory standardUsdcData = RateLimitData({ maxAmount : 5_000_000e6, slope : uint256(1_000_000e6) / 4 hours }); - RateLimitData memory standardUsdsRateLimitData = RateLimitData({ + RateLimitData memory standardUsdsData = RateLimitData({ maxAmount : 5_000_000e18, slope : uint256(1_000_000e18) / 4 hours }); - RateLimitData memory unlimitedRateLimitData = RateLimitData({ + RateLimitData memory unlimitedData = RateLimitData({ maxAmount : type(uint256).max, slope : 0 }); - ForeignControllerInit.InitRateLimitData memory rateLimitData - = ForeignControllerInit.InitRateLimitData({ - usdcDepositData : standardUsdcRateLimitData, - usdcWithdrawData : standardUsdcRateLimitData, - usdsDepositData : standardUsdsRateLimitData, - usdsWithdrawData : unlimitedRateLimitData, - susdsDepositData : standardUsdsRateLimitData, - susdsWithdrawData : unlimitedRateLimitData, - usdcToCctpData : standardUsdcRateLimitData, - cctpToEthereumDomainData : standardUsdcRateLimitData - }); - - MintRecipient[] memory mintRecipients = new MintRecipient[](1); - - mintRecipients[0] = MintRecipient({ - domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, - mintRecipient : bytes32(uint256(uint160(address(almProxy)))) - }); + bytes32 depositKey = foreignController.LIMIT_PSM_DEPOSIT(); + bytes32 withdrawKey = foreignController.LIMIT_PSM_WITHDRAW(); + + bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( + foreignController.LIMIT_USDC_TO_DOMAIN(), + CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM + ); + + RateLimitHelpers.setRateLimitData(foreignController.LIMIT_USDC_TO_CCTP(), address(foreignRateLimits), standardUsdcData, "usdcToCctpData", 6); + RateLimitHelpers.setRateLimitData(domainKeyEthereum, address(foreignRateLimits), standardUsdcData, "cctpToEthereumDomainData", 6); - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init(addresses, controllerInst, rateLimitData, mintRecipients); vm.stopPrank(); USDC_BASE_SUPPLY = usdcBase.totalSupply(); diff --git a/test/mainnet-fork/Deploy.t.sol b/test/mainnet-fork/Deploy.t.sol new file mode 100644 index 0000000..1c407cd --- /dev/null +++ b/test/mainnet-fork/Deploy.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; +import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; + +import "./ForkTestBase.t.sol"; + +contract MainnetControllerDeploySuccessTests is ForkTestBase { + + function test_deployFull() external { + // Perform new deployments against existing fork environment + + ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull({ + admin : SPARK_PROXY, + vault : vault, + psm : PSM, + daiUsds : DAI_USDS, + cctp : CCTP_MESSENGER + }); + + ALMProxy newAlmProxy = ALMProxy(payable(controllerInst.almProxy)); + MainnetController newController = MainnetController(controllerInst.controller); + RateLimits newRateLimits = RateLimits(controllerInst.rateLimits); + + assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); + assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin + + assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); + assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin + + _assertControllerInitState(newController, address(newAlmProxy), address(newRateLimits), vault, buffer); + } + + function test_deployController() external { + // Perform new deployments against existing fork environment + + MainnetController newController = MainnetController(MainnetControllerDeploy.deployController({ + admin : SPARK_PROXY, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + vault : vault, + psm : PSM, + daiUsds : DAI_USDS, + cctp : CCTP_MESSENGER + })); + + _assertControllerInitState(newController, address(almProxy), address(rateLimits), vault, buffer); + } + + function _assertControllerInitState(MainnetController controller, address almProxy, address rateLimits, address vault, address buffer) internal view { + assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); + assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); + + assertEq(address(controller.proxy()), almProxy); + assertEq(address(controller.rateLimits()), rateLimits); + assertEq(address(controller.vault()), vault); + assertEq(address(controller.buffer()), buffer); + assertEq(address(controller.psm()), Ethereum.PSM); + assertEq(address(controller.daiUsds()), Ethereum.DAI_USDS); + assertEq(address(controller.cctp()), Ethereum.CCTP_TOKEN_MESSENGER); + assertEq(address(controller.ethenaMinter()), Ethereum.ETHENA_MINTER); + assertEq(address(controller.susde()), Ethereum.SUSDE); + assertEq(address(controller.dai()), Ethereum.DAI); + assertEq(address(controller.usdc()), Ethereum.USDC); + assertEq(address(controller.usds()), Ethereum.USDS); + assertEq(address(controller.usde()), Ethereum.USDE); + + assertEq(controller.psmTo18ConversionFactor(), 1e12); + assertEq(controller.active(), true); + } + +} diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/DeployAndInit.t.sol index 28c4344..fa66b79 100644 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ b/test/mainnet-fork/DeployAndInit.t.sol @@ -1,112 +1,76 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; -import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; +import "test/mainnet-fork/ForkTestBase.t.sol"; -import { - MainnetControllerInit, - RateLimitData, - MintRecipient -} from "../../deploy/ControllerInit.sol"; +import { IRateLimits } from "src/interfaces/IRateLimits.sol"; -import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; +import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; -import "./ForkTestBase.t.sol"; +import { MainnetControllerInit as Init } from "../../deploy/MainnetControllerInit.sol"; // Necessary to get error message assertions to work contract LibraryWrapper { - function subDaoInitController( - MainnetControllerInit.AddressParams memory params, - ControllerInstance memory controllerInst, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients + function initAlmSystem( + address vault, + address usds, + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients ) external { - MainnetControllerInit.subDaoInitController( - params, - controllerInst, - rateLimitData, - mintRecipients - ); + Init.initAlmSystem(vault, usds, controllerInst, configAddresses, checkAddresses, mintRecipients); } - function subDaoInitFull( - MainnetControllerInit.AddressParams memory params, - ControllerInstance memory controllerInst, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients + function upgradeController( + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients ) external { - MainnetControllerInit.subDaoInitFull( - params, - controllerInst, - rateLimitData, - mintRecipients - ); + Init.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); + } + + function pauseProxyInitAlmSystem(address psm, address almProxy) external { + Init.pauseProxyInitAlmSystem(psm, almProxy); } } -contract MainnetControllerDeployInitTestBase is ForkTestBase { +contract MainnetControllerInitAndUpgradeTestBase is ForkTestBase { function _getDefaultParams() internal returns ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients ) { - addresses = MainnetControllerInit.AddressParams({ - admin : Ethereum.SPARK_PROXY, + configAddresses = Init.ConfigAddressParams({ freezer : freezer, relayer : relayer, - oldController : address(0), - psm : Ethereum.PSM, - vault : vault, - buffer : buffer, - cctpMessenger : Ethereum.CCTP_TOKEN_MESSENGER, - dai : Ethereum.DAI, - daiUsds : Ethereum.DAI_USDS, - usdc : Ethereum.USDC, - usds : Ethereum.USDS, - susds : Ethereum.SUSDS - }); - - RateLimitData memory standardUsdsData = RateLimitData({ - maxAmount : 1_000_000e18, - slope : uint256(1_000_000e18) / 4 hours - }); - - RateLimitData memory usdsToUsdcData = RateLimitData({ - maxAmount : 2_000_000e6, - slope : uint256(2_000_000e6) / 4 hours - }); - - RateLimitData memory usdcToCctpData = RateLimitData({ - maxAmount : 3_000_000e6, - slope : uint256(3_000_000e6) / 4 hours - }); - - RateLimitData memory cctpToBaseDomainData = RateLimitData({ - maxAmount : 4_000_000e6, - slope : uint256(4_000_000e6) / 4 hours + oldController : address(0) }); - rateLimitData = MainnetControllerInit.InitRateLimitData({ - usdsMintData : standardUsdsData, - usdsToUsdcData : usdsToUsdcData, - usdcToCctpData : usdcToCctpData, - cctpToBaseDomainData : cctpToBaseDomainData, - susdsDepositData : standardUsdsData + checkAddresses = Init.CheckAddressParams({ + admin : Ethereum.SPARK_PROXY, + proxy : address(almProxy), + rateLimits : address(rateLimits), + vault : address(vault), + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER }); - mintRecipients = new MintRecipient[](1); + mintRecipients = new Init.MintRecipient[](1); - mintRecipients[0] = MintRecipient({ + mintRecipients[0] = Init.MintRecipient({ domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, mintRecipient : bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) }); @@ -114,7 +78,11 @@ contract MainnetControllerDeployInitTestBase is ForkTestBase { } -contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployInitTestBase { +contract MainnetControllerInitAndUpgradeFailureTest is MainnetControllerInitAndUpgradeTestBase { + + // NOTE: `initAlmSystem` and `upgradeController` are tested in the same contract because + // they both use _initController and have similar specific setups, so it + // less complex/repetitive to test them together. LibraryWrapper wrapper; @@ -122,33 +90,44 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn address public mismatchAddress = makeAddr("mismatchAddress"); - MainnetControllerInit.AddressParams addresses; - MainnetControllerInit.InitRateLimitData rateLimitData; - MintRecipient[] mintRecipients; + address public oldController; + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; function setUp() public override { super.setUp(); - controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); + oldController = address(mainnetController); // Cache for later testing - MintRecipient[] memory mintRecipients_ = new MintRecipient[](1); + // NOTE: initAlmSystem will redundantly call rely and approve on already inited + // almProxy and rateLimits, this setup was chosen to easily test upgrade and init failures. + // It also should be noted that the almProxy and rateLimits that are being used in initAlmSystem + // are already deployed. This is technically possible to do and works in the same way, it was + // done also for make testing easier. + mainnetController = MainnetController(MainnetControllerDeploy.deployController({ + admin : Ethereum.SPARK_PROXY, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + vault : address(vault), + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER + })); - ( addresses, rateLimitData, mintRecipients_ ) = _getDefaultParams(); + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); + + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); // NOTE: This would need to be refactored to a for loop if more than one recipient mintRecipients.push(mintRecipients_[0]); - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - mainnetController = MainnetController(controllerInst.controller); - rateLimits = RateLimits(controllerInst.rateLimits); + controllerInst = ControllerInstance({ + almProxy : address(almProxy), + controller : address(mainnetController), + rateLimits : address(rateLimits) + }); // Admin will be calling the library from its own address vm.etch(SPARK_PROXY, address(new LibraryWrapper()).code); @@ -156,99 +135,90 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn wrapper = LibraryWrapper(SPARK_PROXY); } + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } + /**********************************************************************************************/ /*** ACL tests ***/ /**********************************************************************************************/ - function test_init_incorrectAdminAlmProxy() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_PROXY); - almProxy.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); + function test_initAlmSystem_incorrectAdminAlmProxy() external { + vm.prank(SPARK_PROXY); almProxy.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-almProxy")); + vm.expectRevert("MainnetControllerInit/incorrect-admin-almProxy"); + wrapper.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_incorrectAdminRateLimits() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_PROXY); - rateLimits.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); + function test_initAlmSystem_incorrectAdminRateLimits() external { + vm.prank(SPARK_PROXY); rateLimits.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-rateLimits")); + vm.expectRevert("MainnetControllerInit/incorrect-admin-rateLimits"); + wrapper.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_incorrectAdminController() external { - // Isolate different contracts instead of setting param so can get three different failures - vm.startPrank(SPARK_PROXY); - mainnetController.grantRole(DEFAULT_ADMIN_ROLE, mismatchAddress); + function test_initAlmSystem_upgradeController_incorrectAdminController() external { + vm.prank(SPARK_PROXY); mainnetController.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-controller")); + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-controller")); } /**********************************************************************************************/ /*** Constructor tests ***/ /**********************************************************************************************/ - function test_init_incorrectAlmProxy() external { + function test_initAlmSystem_upgradeController_incorrectAlmProxy() external { // Deploy new address that will not EVM revert on OZ ACL check controllerInst.almProxy = address(new ALMProxy(SPARK_PROXY)); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-almProxy")); + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-almProxy")); } - function test_init_incorrectRateLimits() external { + function test_initAlmSystem_upgradeController_incorrectRateLimits() external { // Deploy new address that will not EVM revert on OZ ACL check controllerInst.rateLimits = address(new RateLimits(SPARK_PROXY)); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-rateLimits")); - } - - function test_init_incorrectVault() external { - addresses.vault = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-vault")); - } - - function test_init_incorrectBuffer() external { - addresses.buffer = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-buffer")); - } - - function test_init_incorrectPsm() external { - addresses.psm = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-psm")); - } - - function test_init_incorrectDaiUsds() external { - addresses.daiUsds = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-daiUsds")); + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-rateLimits")); } - function test_init_incorrectCctp() external { - addresses.cctpMessenger = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-cctpMessenger")); + function test_initAlmSystem_upgradeController_incorrectVault() external { + checkAddresses.vault = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-vault")); } - function test_init_incorrectDai() external { - addresses.dai = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-dai")); + function test_initAlmSystem_upgradeController_incorrectPsm() external { + checkAddresses.psm = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-psm")); } - function test_init_incorrectUsdc() external { - addresses.usdc = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-usdc")); + function test_initAlmSystem_upgradeController_incorrectDaiUsds() external { + checkAddresses.daiUsds = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-daiUsds")); } - function test_init_incorrectUsds() external { - addresses.usds = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-usds")); + function test_initAlmSystem_upgradeController_incorrectCctp() external { + checkAddresses.cctp = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-cctp")); } - function test_init_controllerInactive() external { + function test_initAlmSystem_upgradeController_controllerInactive() external { // Cheating to set this outside of init scripts so that the controller can be frozen vm.startPrank(SPARK_PROXY); mainnetController.grantRole(FREEZER, freezer); @@ -257,298 +227,165 @@ contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployIn mainnetController.freeze(); vm.stopPrank(); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/controller-not-active")); - } - - function test_init_oldControllerIsNewController() external { - addresses.oldController = controllerInst.controller; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/old-controller-is-new-controller")); - } - - // TODO: Skipping conversion factor test, can add later if needed - - /**********************************************************************************************/ - /*** Unlimited `maxAmount` rate limit boundary tests ***/ - /**********************************************************************************************/ - - function test_init_incorrectUsdsMintData_unlimitedBoundary() external { - rateLimitData.usdsMintData.maxAmount = type(uint256).max; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-rate-limit-usdsMintData")); - - rateLimitData.usdsMintData.slope = 0; - _checkBothInitsSucceed(); - } - - function test_init_incorrectUsdcToUsdsData_unlimitedBoundary() external { - rateLimitData.usdsToUsdcData.maxAmount = type(uint256).max; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-rate-limit-usdsToUsdcData")); - - rateLimitData.usdsToUsdcData.slope = 0; - _checkBothInitsSucceed(); - } - - function test_init_incorrectUsdcToCctpData_unlimitedBoundary() external { - rateLimitData.usdcToCctpData.maxAmount = type(uint256).max; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-rate-limit-usdcToCctpData")); - - rateLimitData.usdcToCctpData.slope = 0; - _checkBothInitsSucceed(); - } - - function test_init_incorrectCctpToBaseDomain_unlimitedBoundary() external { - rateLimitData.cctpToBaseDomainData.maxAmount = type(uint256).max; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-rate-limit-cctpToBaseDomainData")); - - rateLimitData.cctpToBaseDomainData.slope = 0; - _checkBothInitsSucceed(); - } - - /**********************************************************************************************/ - /*** `maxAmount` rate limit precision boundary tests ***/ - /**********************************************************************************************/ - - function test_init_incorrectUsdsMintData_maxAmountPrecisionBoundary() external { - rateLimitData.usdsMintData.maxAmount = 1e12 * 1e18 + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-max-amount-precision-usdsMintData")); - - rateLimitData.usdsMintData.maxAmount = 1e12 * 1e18; - _checkBothInitsSucceed(); + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/controller-not-active")); } - function test_init_incorrectUsdcToUsdsData_maxAmountPrecisionBoundary() external { - rateLimitData.usdsToUsdcData.maxAmount = 1e12 * 1e6 + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-max-amount-precision-usdsToUsdcData")); - - rateLimitData.usdsToUsdcData.maxAmount = 1e12 * 1e6; - _checkBothInitsSucceed(); - } - - function test_init_incorrectUsdcToCctpData_maxAmountPrecisionBoundary() external { - rateLimitData.usdcToCctpData.maxAmount = 1e12 * 1e6 + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-max-amount-precision-usdcToCctpData")); - - rateLimitData.usdcToCctpData.maxAmount = 1e12 * 1e6; - _checkBothInitsSucceed(); - } - - function test_init_incorrectCctpToBaseDomain_maxAmountPrecisionBoundary() external { - rateLimitData.cctpToBaseDomainData.maxAmount = 1e12 * 1e6 + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-max-amount-precision-cctpToBaseDomainData")); - - rateLimitData.cctpToBaseDomainData.maxAmount = 1e12 * 1e6; - _checkBothInitsSucceed(); + function test_initAlmSystem_upgradeController_oldControllerIsNewController() external { + configAddresses.oldController = controllerInst.controller; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/old-controller-is-new-controller")); } /**********************************************************************************************/ - /*** `slope` rate limit precision boundary tests ***/ + /*** Upgrade tests ***/ /**********************************************************************************************/ - function test_init_incorrectUsdsMintData_slopePrecisionBoundary() external { - rateLimitData.usdsMintData.slope = uint256(1e12 * 1e18) / 1 hours + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-slope-precision-usdsMintData")); - - rateLimitData.usdsMintData.slope = uint256(1e12 * 1e18) / 1 hours; - _checkBothInitsSucceed(); - } - - function test_init_incorrectUsdcToUsdsData_slopePrecisionBoundary() external { - rateLimitData.usdsToUsdcData.slope = uint256(1e12 * 1e6) / 1 hours + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-slope-precision-usdsToUsdcData")); + function test_upgradeController_oldControllerZeroAddress() external { + configAddresses.oldController = address(0); - rateLimitData.usdsToUsdcData.slope = uint256(1e12 * 1e6) / 1 hours; - _checkBothInitsSucceed(); - } - - function test_init_incorrectUsdcToCctpData_slopePrecisionBoundary() external { - rateLimitData.usdcToCctpData.slope = uint256(1e12 * 1e6) / 1 hours + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-slope-precision-usdcToCctpData")); - - rateLimitData.usdcToCctpData.slope = uint256(1e12 * 1e6) / 1 hours; - _checkBothInitsSucceed(); - } - - function test_init_incorrectCctpToBaseDomain_slopePrecisionBoundary() external { - rateLimitData.cctpToBaseDomainData.slope = uint256(1e12 * 1e6) / 1 hours + 1; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/invalid-slope-precision-cctpToBaseDomainData")); - - rateLimitData.cctpToBaseDomainData.slope = uint256(1e12 * 1e6) / 1 hours; - _checkBothInitsSucceed(); + vm.expectRevert("MainnetControllerInit/old-controller-zero-address"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - /**********************************************************************************************/ - /*** Old controller role check tests ***/ - /**********************************************************************************************/ - - function test_init_oldControllerDoesNotHaveRoleInAlmProxy() external { - _deployNewControllerAfterExistingControllerInit(); + function test_upgradeController_oldControllerDoesNotHaveRoleInAlmProxy() external { + configAddresses.oldController = oldController; // Revoke the old controller address in ALM proxy - vm.startPrank(SPARK_PROXY); - almProxy.revokeRole(almProxy.CONTROLLER(), addresses.oldController); - vm.stopPrank(); - - // Try to init with the old controller address that is doesn't have the CONTROLLER role + almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController); + vm.stopPrank(); - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/old-controller-not-almProxy-controller")); + // Try to upgrade with the old controller address that is doesn't have the CONTROLLER role + vm.expectRevert("MainnetControllerInit/old-controller-not-almProxy-controller"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } - function test_init_oldControllerDoesNotHaveRoleInRateLimits() external { - _deployNewControllerAfterExistingControllerInit(); - - // Revoke the old controller address + function test_upgradeController_oldControllerDoesNotHaveRoleInRateLimits() external { + configAddresses.oldController = oldController; + // Revoke the old controller address in rate limits vm.startPrank(SPARK_PROXY); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.oldController); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); vm.stopPrank(); - // Try to init with the old controller address that is doesn't have the CONTROLLER role - - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/old-controller-not-rateLimits-controller")); + // Try to upgrade with the old controller address that is doesn't have the CONTROLLER role + vm.expectRevert("MainnetControllerInit/old-controller-not-rateLimits-controller"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); } /**********************************************************************************************/ /*** Helper functions ***/ /**********************************************************************************************/ - function _deployNewControllerAfterExistingControllerInit() internal { - // Successfully init first controller - - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitFull( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); - - // Deploy a new controller (controllerInst is used in init with new controller address) - - controllerInst.controller = MainnetControllerDeploy.deployController( - SPARK_PROXY, - address(almProxy), - address(rateLimits), - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); - - addresses.oldController = address(mainnetController); - } - - // Added this function to ensure that all the failure modes from `subDaoInitController` - // are also covered by `subDaoInitFull` calls - function _checkBothInitsFail(bytes memory expectedError) internal { + function _checkInitAndUpgradeFail(bytes memory expectedError) internal { vm.expectRevert(expectedError); - wrapper.subDaoInitController( - addresses, + wrapper.initAlmSystem( + vault, + address(usds), controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); vm.expectRevert(expectedError); - wrapper.subDaoInitFull( - addresses, + wrapper.upgradeController( controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); } - function _checkBothInitsSucceed() internal { - wrapper.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - - wrapper.subDaoInitFull( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - } } -contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployInitTestBase { +contract MainnetControllerInitAlmSystemSuccessTests is MainnetControllerInitAndUpgradeTestBase { - function test_deployAllAndInitFull() external { - // Perform new deployments against existing fork environment + LibraryWrapper wrapper; - ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); + ControllerInstance public controllerInst; + + address public mismatchAddress = makeAddr("mismatchAddress"); - // Overwrite storage for all previous deployments in setUp and assert deployment + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; - almProxy = ALMProxy(payable(controllerInst.almProxy)); + function setUp() public override { + super.setUp(); + + controllerInst = MainnetControllerDeploy.deployFull( + Ethereum.SPARK_PROXY, + address(vault), + Ethereum.PSM, + Ethereum.DAI_USDS, + Ethereum.CCTP_TOKEN_MESSENGER + ); + + // Overwrite storage for all previous deployments in setUp and assert brand new deployment mainnetController = MainnetController(controllerInst.controller); + almProxy = ALMProxy(payable(controllerInst.almProxy)); rateLimits = RateLimits(controllerInst.rateLimits); - assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); - assertEq(mainnetController.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); - assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true); - - assertEq(address(mainnetController.proxy()), controllerInst.almProxy); - assertEq(address(mainnetController.rateLimits()), controllerInst.rateLimits); - assertEq(address(mainnetController.vault()), vault); - assertEq(address(mainnetController.buffer()), buffer); - assertEq(address(mainnetController.psm()), PSM); - assertEq(address(mainnetController.daiUsds()), DAI_USDS); - assertEq(address(mainnetController.cctp()), CCTP_MESSENGER); - assertEq(address(mainnetController.dai()), address(dai)); - assertEq(address(mainnetController.usdc()), address(usdc)); - assertEq(address(mainnetController.usds()), address(usds)); - - assertEq(mainnetController.psmTo18ConversionFactor(), 1e12); - assertEq(mainnetController.active(), true); - - // Perform SubDAO initialization (from SPARK_PROXY during spell) - // Setting rate limits to different values from setUp to make assertions more robust - - ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); + + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); + + mintRecipients.push(mintRecipients_[0]); + + // Admin will be calling the library from its own address + vm.etch(SPARK_PROXY, address(new LibraryWrapper()).code); + + wrapper = LibraryWrapper(SPARK_PROXY); + } + + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } + + function test_initAlmSystem() public { + assertEq(mainnetController.hasRole(mainnetController.FREEZER(), freezer), false); + assertEq(mainnetController.hasRole(mainnetController.RELAYER(), relayer), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), false); + + assertEq(mainnetController.mintRecipients(mintRecipients[0].domain), bytes32(0)); + assertEq(mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), bytes32(0)); + + assertEq(IVaultLike(vault).wards(controllerInst.almProxy), 0); + assertEq(usds.allowance(buffer, controllerInst.almProxy), 0); vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitFull( - addresses, + wrapper.initAlmSystem( + address(vault), + address(usds), controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); - vm.stopPrank(); - - // Assert SubDAO initialization assertEq(mainnetController.hasRole(mainnetController.FREEZER(), freezer), true); assertEq(mainnetController.hasRole(mainnetController.RELAYER(), relayer), true); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); - + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); - bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( - mainnetController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE - ); - - _assertRateLimitData(mainnetController.LIMIT_USDS_MINT(), rateLimitData.usdsMintData); - _assertRateLimitData(mainnetController.LIMIT_USDS_TO_USDC(), rateLimitData.usdsToUsdcData); - _assertRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), rateLimitData.usdcToCctpData); - _assertRateLimitData(domainKeyBase, rateLimitData.cctpToBaseDomainData); - assertEq( mainnetController.mintRecipients(mintRecipients[0].domain), mintRecipients[0].mintRecipient @@ -560,163 +397,115 @@ contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployIn ); assertEq(IVaultLike(vault).wards(controllerInst.almProxy), 1); + assertEq(usds.allowance(buffer, controllerInst.almProxy), type(uint256).max); + } - assertEq(usds.allowance(buffer, controllerInst.almProxy), type(uint256).max); + function test_pauseProxyInitAlmSystem() public { + // Update to call the library from the pause proxy + vm.etch(PAUSE_PROXY, address(new LibraryWrapper()).code); + wrapper = LibraryWrapper(PAUSE_PROXY); - // Perform Maker initialization (from PAUSE_PROXY during spell) + assertEq(IPSMLike(Ethereum.PSM).bud(controllerInst.almProxy), 0); vm.startPrank(PAUSE_PROXY); - MainnetControllerInit.pauseProxyInit(PSM, controllerInst.almProxy); + wrapper.pauseProxyInitAlmSystem(Ethereum.PSM, controllerInst.almProxy); vm.stopPrank(); - // Assert Maker initialization - - assertEq(IPSMLike(PSM).bud(controllerInst.almProxy), 1); + assertEq(IPSMLike(Ethereum.PSM).bud(controllerInst.almProxy), 1); } - function test_deployAllAndInitController() external { - // Perform new deployments against existing fork environment - - ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); - - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - mainnetController = MainnetController(controllerInst.controller); - rateLimits = RateLimits(controllerInst.rateLimits); - - ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); - - // Perform ONLY controller initialization, setting rate limits and updating ACL - // Setting rate limits to different values from setUp to make assertions more robust - - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); +} - assertEq(mainnetController.hasRole(mainnetController.FREEZER(), freezer), true); - assertEq(mainnetController.hasRole(mainnetController.RELAYER(), relayer), true); +contract MainnetControllerUpgradeControllerSuccessTests is MainnetControllerInitAndUpgradeTestBase { - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); + LibraryWrapper wrapper; - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); + ControllerInstance public controllerInst; - bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( - mainnetController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE - ); + address public mismatchAddress = makeAddr("mismatchAddress"); - _assertRateLimitData(mainnetController.LIMIT_USDS_MINT(), rateLimitData.usdsMintData); - _assertRateLimitData(mainnetController.LIMIT_USDS_TO_USDC(), rateLimitData.usdsToUsdcData); - _assertRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), rateLimitData.usdcToCctpData); - _assertRateLimitData(domainKeyBase, rateLimitData.cctpToBaseDomainData); + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; - assertEq( - mainnetController.mintRecipients(mintRecipients[0].domain), - mintRecipients[0].mintRecipient - ); + MainnetController newController; - assertEq( - mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), - bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) - ); - } + function setUp() public override { + super.setUp(); - function test_init_transferAclToNewController() public { - // Deploy and init a controller + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); - ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); - ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); + mintRecipients.push(mintRecipients_[0]); - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); + newController = MainnetController(MainnetControllerDeploy.deployController({ + admin : Ethereum.SPARK_PROXY, + almProxy : address(almProxy), + rateLimits : address(rateLimits), + vault : address(vault), + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER + })); + + controllerInst = ControllerInstance({ + almProxy : address(almProxy), + controller : address(newController), + rateLimits : address(rateLimits) + }); - // Deploy a new controller (example of how an upgrade would work) + configAddresses.oldController = address(mainnetController); // Revoke from old controller - address newController = MainnetControllerDeploy.deployController( - SPARK_PROXY, - controllerInst.almProxy, - controllerInst.rateLimits, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER - ); + // Admin will be calling the library from its own address + vm.etch(SPARK_PROXY, address(new LibraryWrapper()).code); - // Overwrite storage for all previous deployments in setUp and assert deployment + wrapper = LibraryWrapper(SPARK_PROXY); + } - almProxy = ALMProxy(payable(controllerInst.almProxy)); - mainnetController = MainnetController(controllerInst.controller); - rateLimits = RateLimits(controllerInst.rateLimits); + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } - address oldController = address(controllerInst.controller); + function test_upgradeController() public { + assertEq(newController.hasRole(newController.FREEZER(), freezer), false); + assertEq(newController.hasRole(newController.RELAYER(), relayer), false); - controllerInst.controller = newController; // Overwrite struct for param + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); - // All other info is the same, just need to transfer ACL - addresses.oldController = oldController; + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), false); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), true); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), true); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), false); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), false); + assertEq(newController.mintRecipients(mintRecipients[0].domain), bytes32(0)); + assertEq(newController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), bytes32(0)); vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitController( - addresses, + wrapper.upgradeController( controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); - vm.stopPrank(); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), false); - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), oldController), false); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), newController), true); - } + assertEq(newController.hasRole(newController.FREEZER(), freezer), true); + assertEq(newController.hasRole(newController.RELAYER(), relayer), true); - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal view { - IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), false); - assertEq(data.maxAmount, expectedData.maxAmount); - assertEq(data.slope, expectedData.slope); - assertEq(data.lastAmount, expectedData.maxAmount); - assertEq(data.lastUpdated, block.timestamp); + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), true); - assertEq(rateLimits.getCurrentRateLimit(domainKey), expectedData.maxAmount); + assertEq( + newController.mintRecipients(mintRecipients[0].domain), + mintRecipients[0].mintRecipient + ); + + assertEq( + newController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), + bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) + ); } } diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 6caf971..2a2c97a 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -26,17 +26,14 @@ import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; -import { - MainnetControllerInit, - MintRecipient, - RateLimitData -} from "../../deploy/ControllerInit.sol"; +import { MainnetControllerInit as Init } from "../../deploy/MainnetControllerInit.sol"; import { ALMProxy } from "../../src/ALMProxy.sol"; import { RateLimits } from "../../src/RateLimits.sol"; -import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; import { MainnetController } from "../../src/MainnetController.sol"; +import { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; + interface IChainlogLike { function getAddress(bytes32) external view returns (address); } @@ -81,8 +78,8 @@ contract ForkTestBase is DssTest { uint256 constant SEVEN_PCT_APY = 1.000000002145441671308778766e27; // 7% APY (current DSR) uint256 constant EIGHT_PCT_APY = 1.000000002440418608258400030e27; // 8% APY (current DSR + 1%) - address freezer = makeAddr("freezer"); - address relayer = makeAddr("relayer"); + address freezer = Ethereum.ALM_FREEZER; + address relayer = Ethereum.ALM_RELAYER; bytes32 CONTROLLER; bytes32 FREEZER; @@ -201,14 +198,14 @@ contract ForkTestBase is DssTest { buffer = ilkInst.buffer; vault = ilkInst.vault; - /*** Step 3: Deploy and configure ALM system ***/ + /*** Step 3: Deploy ALM system ***/ ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull({ - admin : Ethereum.SPARK_PROXY, - vault : ilkInst.vault, - psm : Ethereum.PSM, - daiUsds: Ethereum.DAI_USDS, - cctp : Ethereum.CCTP_TOKEN_MESSENGER + admin : Ethereum.SPARK_PROXY, + vault : ilkInst.vault, + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER }); almProxy = ALMProxy(payable(controllerInst.almProxy)); @@ -219,61 +216,73 @@ contract ForkTestBase is DssTest { FREEZER = mainnetController.FREEZER(); RELAYER = mainnetController.RELAYER(); - MainnetControllerInit.AddressParams memory addresses = MainnetControllerInit.AddressParams({ - admin : Ethereum.SPARK_PROXY, - freezer : freezer, - relayer : relayer, - oldController : address(0), - psm : Ethereum.PSM, - vault : vault, - buffer : buffer, - cctpMessenger : Ethereum.CCTP_TOKEN_MESSENGER, - dai : Ethereum.DAI, - daiUsds : Ethereum.DAI_USDS, - usdc : Ethereum.USDC, - usds : Ethereum.USDS, - susds : Ethereum.SUSDS - }); - - RateLimitData memory standardUsdsData = RateLimitData({ - maxAmount : 5_000_000e18, - slope : uint256(1_000_000e18) / 4 hours - }); - - RateLimitData memory standardUsdcData = RateLimitData({ - maxAmount : 5_000_000e6, - slope : uint256(1_000_000e6) / 4 hours - }); + Init.ConfigAddressParams memory configAddresses + = Init.ConfigAddressParams({ + freezer : freezer, + relayer : relayer, + oldController : address(0) + }); - MainnetControllerInit.InitRateLimitData memory rateLimitData - = MainnetControllerInit.InitRateLimitData({ - usdsMintData : standardUsdsData, - usdsToUsdcData : standardUsdcData, - usdcToCctpData : standardUsdcData, - cctpToBaseDomainData : standardUsdcData, - susdsDepositData : standardUsdsData + Init.CheckAddressParams memory checkAddresses + = Init.CheckAddressParams({ + admin : Ethereum.SPARK_PROXY, + proxy : address(almProxy), + rateLimits : address(rateLimits), + vault : address(vault), + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER }); - MintRecipient[] memory mintRecipients = new MintRecipient[](1); + Init.MintRecipient[] memory mintRecipients = new Init.MintRecipient[](1); - mintRecipients[0] = MintRecipient({ + mintRecipients[0] = Init.MintRecipient({ domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, mintRecipient : bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) }); + // Step 4: Initialize through Sky governance (Sky spell payload) + vm.prank(Ethereum.PAUSE_PROXY); - MainnetControllerInit.pauseProxyInit(Ethereum.PSM, controllerInst.almProxy); + Init.pauseProxyInitAlmSystem(Ethereum.PSM, controllerInst.almProxy); + + // Step 5: Initialize through Spark governance (Spark spell payload) vm.startPrank(Ethereum.SPARK_PROXY); - MainnetControllerInit.subDaoInitFull( - addresses, + + Init.initAlmSystem( + vault, + address(usds), controllerInst, - rateLimitData, + configAddresses, + checkAddresses, mintRecipients ); + + RateLimitData memory standardUsdsData = RateLimitData({ + maxAmount : 5_000_000e18, + slope : uint256(1_000_000e18) / 4 hours + }); + + RateLimitData memory standardUsdcData = RateLimitData({ + maxAmount : 5_000_000e6, + slope : uint256(1_000_000e6) / 4 hours + }); + + bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( + mainnetController.LIMIT_USDC_TO_DOMAIN(), + CCTPForwarder.DOMAIN_ID_CIRCLE_BASE + ); + + // NOTE: Using minimal config for test base setup + RateLimitHelpers.setRateLimitData(mainnetController.LIMIT_USDS_MINT(), address(rateLimits), standardUsdsData, "usdsMintData", 18); + RateLimitHelpers.setRateLimitData(mainnetController.LIMIT_USDS_TO_USDC(), address(rateLimits), standardUsdcData, "usdsToUsdcData", 6); + RateLimitHelpers.setRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), address(rateLimits), standardUsdcData, "usdcToCctpData", 6); + RateLimitHelpers.setRateLimitData(domainKeyBase, address(rateLimits), standardUsdcData, "cctpToBaseDomainData", 6); + vm.stopPrank(); - /*** Step 4: Label addresses ***/ + /*** Step 6: Label addresses ***/ vm.label(buffer, "buffer"); vm.label(address(susds), "susds"); From 0d84f37782b2d522dd66f6129be6b31cf91d92ef Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Fri, 27 Dec 2024 23:40:24 -0500 Subject: [PATCH 20/22] fix: Remove manual remappings and update test file name (SC-897) (#77) * fix: rm manual remappings * fix: rename file --- foundry.toml | 9 --------- script/staging/FullStagingDeploy.s.sol | 2 +- script/staging/test/StagingDeployment.t.sol | 12 ++++++------ src/MainnetController.sol | 2 +- test/base-fork/ForkTestBase.t.sol | 4 ++-- .../{DeployAndInit.t.sol => InitAndUpgrade.t.sol} | 2 +- test/mainnet-fork/CCTPCalls.t.sol | 6 +++--- test/mainnet-fork/ForkTestBase.t.sol | 8 ++++---- .../{DeployAndInit.t.sol => InitAndUpgrade.t.sol} | 0 9 files changed, 18 insertions(+), 27 deletions(-) rename test/base-fork/{DeployAndInit.t.sol => InitAndUpgrade.t.sol} (99%) rename test/mainnet-fork/{DeployAndInit.t.sol => InitAndUpgrade.t.sol} (100%) diff --git a/foundry.toml b/foundry.toml index 7c47e3b..880f1bb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,15 +5,6 @@ libs = ["lib"] solc_version = '0.8.21' optimizer = true optimizer_runs = 200 -remappings = [ - "dss-allocator/=lib/dss-allocator/", - "dss-test/=lib/dss-test/src/", - "usds/=lib/usds/", - "sdai/=lib/sdai/", - "spark-psm/=lib/spark-psm/", - "spark-address-registry/=lib/spark-address-registry/", - "xchain-helpers/=lib/xchain-helpers/", -] fs_permissions = [ { access = "read", path = "./script/input/"}, { access = "read-write", path = "./script/output/"} diff --git a/script/staging/FullStagingDeploy.s.sol b/script/staging/FullStagingDeploy.s.sol index 57202d5..3ffefce 100644 --- a/script/staging/FullStagingDeploy.s.sol +++ b/script/staging/FullStagingDeploy.s.sol @@ -23,7 +23,7 @@ import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { Script } from "forge-std/Script.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; import { ControllerInstance, diff --git a/script/staging/test/StagingDeployment.t.sol b/script/staging/test/StagingDeployment.t.sol index 973b678..e4fc644 100644 --- a/script/staging/test/StagingDeployment.t.sol +++ b/script/staging/test/StagingDeployment.t.sol @@ -17,15 +17,15 @@ import { Usds } from "usds/src/Usds.sol"; import { SUsds } from "sdai/src/SUsds.sol"; -import { Base } from "spark-address-registry/src/Base.sol"; -import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; +import { Base } from "spark-address-registry/Base.sol"; +import { Ethereum } from "spark-address-registry/Ethereum.sol"; import { PSM3 } from "spark-psm/src/PSM3.sol"; -import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; -import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; -import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { Bridge } from "xchain-helpers/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "xchain-helpers/testing/Domain.sol"; +import { CCTPBridgeTesting } from "xchain-helpers/testing/bridges/CCTPBridgeTesting.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; import { MainnetControllerDeploy } from "../../../deploy/ControllerDeploy.sol"; import { MainnetControllerInit } from "../../../deploy/MainnetControllerInit.sol"; diff --git a/src/MainnetController.sol b/src/MainnetController.sol index 6414da8..4fe4844 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -9,7 +9,7 @@ import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol"; -import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; +import { Ethereum } from "spark-address-registry/Ethereum.sol"; import { IALMProxy } from "./interfaces/IALMProxy.sol"; import { ICCTPLike } from "./interfaces/CCTPInterfaces.sol"; diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 6021b88..3a6ea38 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -7,12 +7,12 @@ import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; -import { Base } from "spark-address-registry/src/Base.sol"; +import { Base } from "spark-address-registry/Base.sol"; import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol"; import { IPSM3 } from "spark-psm/src/PSM3.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; diff --git a/test/base-fork/DeployAndInit.t.sol b/test/base-fork/InitAndUpgrade.t.sol similarity index 99% rename from test/base-fork/DeployAndInit.t.sol rename to test/base-fork/InitAndUpgrade.t.sol index 8fded8e..a63bab4 100644 --- a/test/base-fork/DeployAndInit.t.sol +++ b/test/base-fork/InitAndUpgrade.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import "../../test/base-fork/ForkTestBase.t.sol"; -import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; diff --git a/test/mainnet-fork/CCTPCalls.t.sol b/test/mainnet-fork/CCTPCalls.t.sol index 22f08f8..bf8b2e9 100644 --- a/test/mainnet-fork/CCTPCalls.t.sol +++ b/test/mainnet-fork/CCTPCalls.t.sol @@ -5,14 +5,14 @@ import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; -import { Base } from "spark-address-registry/src/Base.sol"; +import { Base } from "spark-address-registry/Base.sol"; import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol"; import { IPSM3 } from "spark-psm/src/PSM3.sol"; import { MockRateProvider } from "spark-psm/test/mocks/MockRateProvider.sol"; -import { CCTPBridgeTesting } from "xchain-helpers/src/testing/bridges/CCTPBridgeTesting.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { CCTPBridgeTesting } from "xchain-helpers/testing/bridges/CCTPBridgeTesting.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; diff --git a/test/mainnet-fork/ForkTestBase.t.sol b/test/mainnet-fork/ForkTestBase.t.sol index 2a2c97a..40aaf08 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -17,11 +17,11 @@ import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { ISUsds } from "sdai/src/ISUsds.sol"; -import { Ethereum } from "spark-address-registry/src/Ethereum.sol"; +import { Ethereum } from "spark-address-registry/Ethereum.sol"; -import { Bridge } from "xchain-helpers/src/testing/Bridge.sol"; -import { CCTPForwarder } from "xchain-helpers/src/forwarders/CCTPForwarder.sol"; -import { Domain, DomainHelpers } from "xchain-helpers/src/testing/Domain.sol"; +import { Bridge } from "xchain-helpers/testing/Bridge.sol"; +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; +import { Domain, DomainHelpers } from "xchain-helpers/testing/Domain.sol"; import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol"; import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; diff --git a/test/mainnet-fork/DeployAndInit.t.sol b/test/mainnet-fork/InitAndUpgrade.t.sol similarity index 100% rename from test/mainnet-fork/DeployAndInit.t.sol rename to test/mainnet-fork/InitAndUpgrade.t.sol From 155f3a66403d1f384301df93abd9d8817e5f0092 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Sun, 29 Dec 2024 12:05:29 -0500 Subject: [PATCH 21/22] Add release and bump solc version (#79) * setting evm version and bumping solc * use shanghai * adding releases * newlines --- foundry.toml | 2 +- script/output/1/base-production-release-20241229.json | 3 +++ script/output/1/mainnet-production-release-20241229.json | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 script/output/1/base-production-release-20241229.json create mode 100644 script/output/1/mainnet-production-release-20241229.json diff --git a/foundry.toml b/foundry.toml index 880f1bb..d59ed99 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "src" out = "out" libs = ["lib"] -solc_version = '0.8.21' +solc_version = '0.8.25' optimizer = true optimizer_runs = 200 fs_permissions = [ diff --git a/script/output/1/base-production-release-20241229.json b/script/output/1/base-production-release-20241229.json new file mode 100644 index 0000000..f195457 --- /dev/null +++ b/script/output/1/base-production-release-20241229.json @@ -0,0 +1,3 @@ +{ + "controller": "0x5F032555353f3A1D16aA6A4ADE0B35b369da0440" +} diff --git a/script/output/1/mainnet-production-release-20241229.json b/script/output/1/mainnet-production-release-20241229.json new file mode 100644 index 0000000..e469028 --- /dev/null +++ b/script/output/1/mainnet-production-release-20241229.json @@ -0,0 +1,3 @@ +{ + "controller": "0x5cf73FDb7057E436A6eEaDFAd27E45E7ab6E431e" +} From eb8192199ee7713b583dd1b4920ec156c2333830 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Date: Mon, 30 Dec 2024 17:29:51 -0500 Subject: [PATCH 22/22] feat: Add upgrade scripts for simulations (SC-891) (#80) * feat: add first file change * feat: add working upgrade script * feat: update documentation * fix: use controller as a param * fix: update command * fix: update description --- README.md | 62 ++++++++++++ script/Upgrade.s.sol | 146 ++++++++++++++++++++++++++++ script/input/1/base-production.json | 4 +- 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 script/Upgrade.s.sol diff --git a/README.md b/README.md index 3fe8f6e..20df09a 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,68 @@ To run all tests, run the following command: forge test ``` +## Deployments +All commands to deploy: + - Either the full system or just the controller + - To mainnet or base + - For staging or production + +Can be found in the Makefile, with the nomenclature `make deploy---`. + +Deploy a full ALM system to base production: `make deploy-base-production-full` +Deploy a controller to mainnet production: `make deploy-mainnet-production-controller` + +To deploy a full staging environment from scratch, with a new allocation system and all necessary dependencies, run `make deploy-staging-full`. + +## Upgrade Simulations + +To perform upgrades against forks of mainnet and base for testing/simulation purposes, use the following instructions. + +1. Set up two anvil nodes forked against mainnet and base. +``` +anvil --fork-url $MAINNET_RPC_URL +``` +``` +anvil --fork-url $BASE_RPC_URL -p 8546 +``` + +2. Point to local RPCs. + +``` +export MAINNET_RPC_URL=http://127.0.0.1:8545 +export BASE_RPC_URL=http://127.0.0.1:8546 +``` + +3. Upgrade mainnet contracts impersonating as the `SPARK_PROXY`. + +``` +export SPARK_PROXY=0x3300f198988e4C9C63F75dF86De36421f06af8c4 + +cast rpc --rpc-url="$MAINNET_RPC_URL" anvil_setBalance $SPARK_PROXY `cast to-wei 1000 | cast to-hex` +cast rpc --rpc-url="$MAINNET_RPC_URL" anvil_impersonateAccount $SPARK_PROXY + +ENV=production \ +OLD_CONTROLLER=0xb960F71ca3f1f57799F6e14501607f64f9B36F11 \ +NEW_CONTROLLER=0x5cf73FDb7057E436A6eEaDFAd27E45E7ab6E431e \ +forge script script/Upgrade.s.sol:UpgradeMainnetController --broadcast --unlocked --sender $SPARK_PROXY +``` + +4. Upgrade base contracts impersonating as the `SPARK_EXEUCTOR`. + +``` +export SPARK_EXECUTOR=0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579 + +cast rpc --rpc-url="$BASE_RPC_URL" anvil_setBalance $SPARK_EXECUTOR `cast to-wei 1000 | cast to-hex` +cast rpc --rpc-url="$BASE_RPC_URL" anvil_impersonateAccount $SPARK_EXECUTOR + +CHAIN=base \ +ENV=production \ +OLD_CONTROLLER=0xc07f705D0C0e9F8C79C5fbb748aC1246BBCC37Ba \ +NEW_CONTROLLER=0x5F032555353f3A1D16aA6A4ADE0B35b369da0440 \ +forge script script/Upgrade.s.sol:UpgradeForeignController --broadcast --unlocked --sender $SPARK_EXECUTOR +``` + + *** *The IP in this repository was assigned to Mars SPC Limited in respect of the MarsOne SP* diff --git a/script/Upgrade.s.sol b/script/Upgrade.s.sol new file mode 100644 index 0000000..79c50c8 --- /dev/null +++ b/script/Upgrade.s.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; + +import "forge-std/Script.sol"; + +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; + +import { ControllerInstance } from "../deploy/ControllerInstance.sol"; +import { ForeignControllerInit as ForeignInit } from "../deploy/ForeignControllerInit.sol"; +import { MainnetControllerInit as MainnetInit } from "../deploy/MainnetControllerInit.sol"; + +contract UpgradeMainnetController is Script { + + using stdJson for string; + using ScriptTools for string; + + function run() external { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + vm.createSelectFork(getChain("mainnet").rpcUrl); + + console.log("Upgrading mainnet controller..."); + + string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV"))); + + address newController = vm.envAddress("NEW_CONTROLLER"); + address oldController = vm.envAddress("OLD_CONTROLLER"); + + vm.startBroadcast(); + + string memory inputConfig = ScriptTools.readInput(fileSlug); + + ControllerInstance memory controllerInst = ControllerInstance({ + almProxy : inputConfig.readAddress(".almProxy"), + controller : newController, + rateLimits : inputConfig.readAddress(".rateLimits") + }); + + MainnetInit.ConfigAddressParams memory configAddresses = MainnetInit.ConfigAddressParams({ + freezer : inputConfig.readAddress(".freezer"), + relayer : inputConfig.readAddress(".relayer"), + oldController : oldController + }); + + MainnetInit.CheckAddressParams memory checkAddresses = MainnetInit.CheckAddressParams({ + admin : inputConfig.readAddress(".admin"), + proxy : inputConfig.readAddress(".almProxy"), + rateLimits : inputConfig.readAddress(".rateLimits"), + vault : inputConfig.readAddress(".allocatorVault"), + psm : inputConfig.readAddress(".psm"), + daiUsds : inputConfig.readAddress(".daiUsds"), + cctp : inputConfig.readAddress(".cctpTokenMessenger") + }); + + MainnetInit.MintRecipient[] memory mintRecipients = new MainnetInit.MintRecipient[](1); + + string memory baseInputConfig = ScriptTools.readInput(string(abi.encodePacked("base-", vm.envString("ENV")))); + + address baseAlmProxy = baseInputConfig.readAddress(".almProxy"); + + mintRecipients[0] = MainnetInit.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, + mintRecipient : bytes32(uint256(uint160(baseAlmProxy))) + }); + + MainnetInit.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); + + vm.stopBroadcast(); + + console.log("ALMProxy updated at ", controllerInst.almProxy); + console.log("RateLimits upgraded at ", controllerInst.rateLimits); + console.log("Controller upgraded at ", newController); + console.log("Old Controller deprecated at", oldController); + } + +} + +contract UpgradeForeignController is Script { + + using stdJson for string; + using ScriptTools for string; + + function run() external { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + string memory chainName = vm.envString("CHAIN"); + string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV"))); + + address newController = vm.envAddress("NEW_CONTROLLER"); + address oldController = vm.envAddress("OLD_CONTROLLER"); + + vm.createSelectFork(getChain(chainName).rpcUrl); + + console.log(string(abi.encodePacked("Upgrading ", chainName, " controller..."))); + + vm.startBroadcast(); + + string memory inputConfig = ScriptTools.readInput(fileSlug); + + ControllerInstance memory controllerInst = ControllerInstance({ + almProxy : inputConfig.readAddress(".almProxy"), + controller : newController, + rateLimits : inputConfig.readAddress(".rateLimits") + }); + + ForeignInit.ConfigAddressParams memory configAddresses = ForeignInit.ConfigAddressParams({ + freezer : inputConfig.readAddress(".freezer"), + relayer : inputConfig.readAddress(".relayer"), + oldController : oldController + }); + + ForeignInit.CheckAddressParams memory checkAddresses = ForeignInit.CheckAddressParams({ + admin : inputConfig.readAddress(".admin"), + psm : inputConfig.readAddress(".psm"), + cctp : inputConfig.readAddress(".cctpTokenMessenger"), + usdc : inputConfig.readAddress(".usdc"), + susds : inputConfig.readAddress(".susds"), + usds : inputConfig.readAddress(".usds") + }); + + ForeignInit.MintRecipient[] memory mintRecipients = new ForeignInit.MintRecipient[](1); + + string memory mainnetInputConfig = ScriptTools.readInput(string(abi.encodePacked("mainnet-", vm.envString("ENV")))); + + address mainnetAlmProxy = mainnetInputConfig.readAddress(".almProxy"); + + mintRecipients[0] = ForeignInit.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, + mintRecipient : bytes32(uint256(uint160(mainnetAlmProxy))) + }); + + ForeignInit.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); + + vm.stopBroadcast(); + + console.log("ALMProxy updated at ", controllerInst.almProxy); + console.log("RateLimits upgraded at ", controllerInst.rateLimits); + console.log("Controller upgraded at ", newController); + console.log("Old controller deprecated at", oldController); + } + +} diff --git a/script/input/1/base-production.json b/script/input/1/base-production.json index fdbd491..34d8d39 100644 --- a/script/input/1/base-production.json +++ b/script/input/1/base-production.json @@ -6,5 +6,7 @@ "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74", "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB", "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431", - "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a" }