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/.gitmodules b/.gitmodules index c475ba2..daa52ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,9 @@ [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 +[submodule "lib/aave-v3-origin"] + path = lib/aave-v3-origin + url = https://github.com/aave-dao/aave-v3-origin diff --git a/Makefile b/Makefile index 74735db..9f1b5c4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,14 @@ +# Staging Full Deployment with Dependencies +deploy-staging-full :; forge script script/staging/FullStagingDeploy.s.sol:FullStagingDeploy --sender ${ETH_FROM} --broadcast --verify + # 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-controller :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetController --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/README.md b/README.md index d33e5ab..20df09a 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,24 @@ 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`. +- 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. +- 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: @@ -76,9 +99,71 @@ 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/deploy/ControllerDeploy.sol b/deploy/ControllerDeploy.sol index da4eaf2..b3489e6 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"; @@ -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 deleted file mode 100644 index 3e0bb72..0000000 --- a/deploy/ControllerInit.sol +++ /dev/null @@ -1,341 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { CCTPForwarder } from "lib/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; - } - - 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.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"); - - 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 - ); - - _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); - - // 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(_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); - - // 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 _makeKey(bytes32 actionKey, address asset) internal pure returns (bytes32) { - return RateLimitHelpers.makeAssetKey(actionKey, asset); - } - - 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/foundry.toml b/foundry.toml index 931751c..d59ed99 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,22 +2,14 @@ src = "src" out = "out" libs = ["lib"] -solc_version = '0.8.21' +solc_version = '0.8.25' optimizer = true optimizer_runs = 200 -assertions_revert = false -remappings = [ - "dss-allocator/=lib/dss-allocator/", - "dss-test/=lib/dss-test/src/", - "usds/=lib/usds/", - "sdai/=lib/sdai/", - "spark-psm/=lib/spark-psm/", - "xchain-helpers/=lib/xchain-helpers/", -] fs_permissions = [ { access = "read", path = "./script/input/"}, { access = "read-write", path = "./script/output/"} ] +evm_version = 'shanghai' [fuzz] runs = 1000 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/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..0894d15 160000 --- a/lib/spark-address-registry +++ b/lib/spark-address-registry @@ -1 +1 @@ -Subproject commit cf8be6199b40a3337916e3d6f7aba7991916f53d +Subproject commit 0894d151cab9cc50dcf49c4c32e6469b16b391a1 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6a9021b..8194b3d 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 "lib/spark-address-registry/src/Base.sol"; -import { Ethereum } from "lib/spark-address-registry/src/Ethereum.sol"; +import "forge-std/Script.sol"; import { ControllerInstance } from "../deploy/ControllerInstance.sol"; @@ -12,20 +11,29 @@ 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") }); vm.stopBroadcast(); @@ -33,48 +41,124 @@ 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") + }); + + 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/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 new file mode 100644 index 0000000..34d8d39 --- /dev/null +++ b/script/input/1/base-production.json @@ -0,0 +1,12 @@ +{ + "admin": "0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579", + "almProxy": "0x2917956eFF0B5eaF030abDB4EF4296DF775009cA", + "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", + "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74", + "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB", + "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431", + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a" +} diff --git a/script/input/1/base-staging.json b/script/input/1/base-staging.json new file mode 100644 index 0000000..904585b --- /dev/null +++ b/script/input/1/base-staging.json @@ -0,0 +1,9 @@ +{ + "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962", + "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", + "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E", + "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a" +} 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/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-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..3291b67 --- /dev/null +++ b/script/input/1/mainnet-staging.json @@ -0,0 +1,14 @@ +{ + "ilk": "ALLOCATOR-SPARK-A", + "usdcUnitSize": 10, + "usdsUnitSize": 10, + "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155", + "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A", + "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042", + "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/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/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-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/base-release-20241022.json b/script/output/1/base-staging-deps-release-20241022.json similarity index 64% rename from script/output/1/base-release-20241022.json rename to script/output/1/base-staging-deps-release-20241022.json index edb474c..ce097ad 100644 --- a/script/output/1/base-release-20241022.json +++ b/script/output/1/base-staging-deps-release-20241022.json @@ -1,9 +1,6 @@ { "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4", - "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE", - "controller": "0xD26112Ce8f7BE0834dBcfd018042bF76d68Ff42a", "psm": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7", - "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b", "susds": "0x4ae97016a03C132d2F600444E2493C62B01C9497", "safe": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047", "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 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-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-20241022.json b/script/output/1/base-staging-release-20241022.json new file mode 100644 index 0000000..98aa25a --- /dev/null +++ b/script/output/1/base-staging-release-20241022.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE", + "controller": "0xD26112Ce8f7BE0834dBcfd018042bF76d68Ff42a", + "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/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-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-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" +} diff --git a/script/output/1/mainnet-release-20241022.json b/script/output/1/mainnet-staging-deps-release-20241022.json similarity index 83% rename from script/output/1/mainnet-release-20241022.json rename to script/output/1/mainnet-staging-deps-release-20241022.json index 4656819..0dea83b 100644 --- a/script/output/1/mainnet-release-20241022.json +++ b/script/output/1/mainnet-staging-deps-release-20241022.json @@ -5,13 +5,10 @@ "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", 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-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-20241022.json b/script/output/1/mainnet-staging-release-20241022.json new file mode 100644 index 0000000..909bd36 --- /dev/null +++ b/script/output/1/mainnet-staging-release-20241022.json @@ -0,0 +1,5 @@ +{ + "almProxy": "0xC29D06ce81137E6B3C3DC090713636d81600a347", + "controller": "0xcc0c5ADF6649256d3cE6084eCf94AF5D01440b6C", + "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/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/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/DeployEthereum.s.sol b/script/staging/DeployEthereum.s.sol deleted file mode 100644 index a34bef2..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 "script/staging/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/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/FullStagingDeploy.s.sol b/script/staging/FullStagingDeploy.s.sol new file mode 100644 index 0000000..3ffefce --- /dev/null +++ b/script/staging/FullStagingDeploy.s.sol @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.21; + +import { + AllocatorDeploy, + AllocatorIlkInstance, + AllocatorSharedInstance +} from "dss-allocator/deploy/AllocatorDeploy.sol"; + +import { + BufferLike, + RegistryLike, + RolesLike, + VaultLike +} from "dss-allocator/deploy/AllocatorInit.sol"; + +import { AllocatorBuffer } from "dss-allocator/src/AllocatorBuffer.sol"; +import { AllocatorVault } from "dss-allocator/src/AllocatorVault.sol"; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; + +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/forwarders/CCTPForwarder.sol"; + +import { + ControllerInstance, + ForeignController, + ForeignControllerDeploy, + MainnetController, + MainnetControllerDeploy +} from "../../deploy/ControllerDeploy.sol"; + +import { ForeignControllerInit } from "../../deploy/ForeignControllerInit.sol"; +import { MainnetControllerInit } from "../../deploy/MainnetControllerInit.sol"; + +import { IRateLimits } from "../../src/interfaces/IRateLimits.sol"; + +import { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; + +import { MockJug } from "./mocks/MockJug.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 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 ***/ + /**********************************************************************************************/ + + address dai; + address daiUsds; + address livePsm; + address psm; + address susds; + address usds; + address usdc; + + // Mocked MCD contracts + address jug; + address usdsJoin; + address vat; + + /**********************************************************************************************/ + /*** Mainnet allocation system deployments ***/ + /**********************************************************************************************/ + + address oracle; + address roles; + address registry; + + address buffer; + address vault; + + /**********************************************************************************************/ + /*** ALM system deployments ***/ + /**********************************************************************************************/ + + address baseAlmProxy; + address baseController; + + address mainnetAlmProxy; + address mainnetController; + + /**********************************************************************************************/ + /*** Deployment-specific variables ***/ + /**********************************************************************************************/ + + address deployer; + bytes32 ilk; + + uint256 USDC_UNIT_SIZE; + uint256 USDS_UNIT_SIZE; + + Domain mainnet; + Domain base; + + /**********************************************************************************************/ + /*** Helper functions ***/ + /**********************************************************************************************/ + + function _setUpDependencies() internal { + vm.selectFork(mainnet.forkId); + vm.startBroadcast(); + + // Step 1: Use existing contracts for tokens, DaiUsds and PSM + + 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)); + + // 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 + // the DAI balance of the PSM to check if it should fill or not. Filling with DAI + // 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); + + // 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 + + 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.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 { + vm.selectFork(mainnet.forkId); + vm.startBroadcast(); + + // Step 1: Deploy allocation system + + AllocatorSharedInstance memory allocatorSharedInstance + = AllocatorDeploy.deployShared(deployer, mainnet.admin); + + AllocatorIlkInstance memory allocatorIlkInstance = AllocatorDeploy.deployIlk( + deployer, + mainnet.admin, + allocatorSharedInstance.roles, + ilk, + usdsJoin + ); + + oracle = allocatorSharedInstance.oracle; + registry = allocatorSharedInstance.registry; + roles = allocatorSharedInstance.roles; + + buffer = allocatorIlkInstance.buffer; + vault = allocatorIlkInstance.vault; + + // Step 2: Perform partial initialization (not using library because of mocked MCD) + + RegistryLike(registry).file(ilk, "buffer", buffer); + VaultLike(vault).file("jug", jug); + BufferLike(buffer).approve(usds, vault, type(uint256).max); + RolesLike(roles).setIlkAdmin(ilk, mainnet.admin); + + // Step 3: Move ownership of both the vault and buffer to the admin + + ScriptTools.switchOwner(vault, allocatorIlkInstance.owner, mainnet.admin); + ScriptTools.switchOwner(buffer, allocatorIlkInstance.owner, mainnet.admin); + + vm.stopBroadcast(); + + // Step 4: Export all deployed addresses + + ScriptTools.exportContract(mainnet.nameDeps, "allocatorOracle", oracle); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorRegistry", registry); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorRoles", roles); + + ScriptTools.exportContract(mainnet.nameDeps, "allocatorBuffer", buffer); + ScriptTools.exportContract(mainnet.nameDeps, "allocatorVault", vault); + } + + function _setUpMainnetController() internal { + vm.selectFork(mainnet.forkId); + vm.startBroadcast(); + + // Step 1: Deploy ALM controller + + ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull({ + admin : mainnet.admin, + vault : vault, + psm : psm, // Wrapper + daiUsds : daiUsds, + cctp : mainnet.config.readAddress(".cctpTokenMessenger") + }); + + mainnetAlmProxy = controllerInst.almProxy; + mainnetController = controllerInst.controller; + + // Step 2: Initialize ALM system + + 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, + slope : USDC_UNIT_SIZE * 1e12 / 4 hours + }); + RateLimitData memory rateLimitData6 = RateLimitData({ + maxAmount : USDC_UNIT_SIZE * 5, + slope : USDC_UNIT_SIZE / 4 hours + }); + + 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); + } + + function _setBaseControllerRateLimits(address rateLimits) internal { + 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 + }); + 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 + ); + + 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() internal { + vm.selectFork(base.forkId); + vm.startBroadcast(); + + // Step 1: Deploy ALM controller + + ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({ + admin : base.admin, + psm : base.config.readAddress(".psm"), + usdc : base.config.readAddress(".usdc"), + cctp : base.config.readAddress(".cctpTokenMessenger") + }); + + baseAlmProxy = controllerInst.almProxy; + baseController = controllerInst.controller; + + // Step 2: Initialize ALM system + + ForeignControllerInit.ConfigAddressParams memory configAddresses = ForeignControllerInit.ConfigAddressParams({ + freezer : base.config.readAddress(".freezer"), + relayer : base.config.readAddress(".relayer"), + oldController : address(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") + }); + + ForeignControllerInit.MintRecipient[] memory mintRecipients = new ForeignControllerInit.MintRecipient[](1); + + mintRecipients[0] = ForeignControllerInit.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, + mintRecipient : bytes32(uint256(uint160(mainnetAlmProxy))) + }); + + ForeignControllerInit.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + // Step 3: Set all rate limits for the controller + + _setBaseControllerRateLimits(controllerInst.rateLimits); + + vm.stopBroadcast(); + + // 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.name, "almProxy", controllerInst.almProxy); + ScriptTools.exportContract(base.name, "controller", controllerInst.controller); + ScriptTools.exportContract(base.name, "rateLimits", controllerInst.rateLimits); + } + + function _setBaseMintRecipient() internal { + vm.selectFork(mainnet.forkId); + vm.startBroadcast(); + + MainnetController(mainnetController).setMintRecipient( + CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, + bytes32(uint256(uint160(baseAlmProxy))) + ); + + vm.stopBroadcast(); + } + + function run() public { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true"); + + deployer = msg.sender; + + 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 = mainnet.config.readUint(".usdcUnitSize") * 1e6; + USDS_UNIT_SIZE = mainnet.config.readUint(".usdsUnitSize") * 1e18; + + // Run deployment scripts after setting storage variables + + _setUpDependencies(); + _setUpAllocationSystem(); + _setUpMainnetController(); + _setUpBaseALMController(); + _setBaseMintRecipient(); + + ScriptTools.exportContract(mainnet.nameDeps, "admin", deployer); + ScriptTools.exportContract(base.nameDeps, "admin", deployer); + } + +} diff --git a/script/staging/StagingDeploymentBase.sol b/script/staging/StagingDeploymentBase.sol deleted file mode 100644 index 582e4f7..0000000 --- a/script/staging/StagingDeploymentBase.sol +++ /dev/null @@ -1,509 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.21; - -import { - AllocatorDeploy, - AllocatorIlkInstance, - AllocatorSharedInstance -} from "dss-allocator/deploy/AllocatorDeploy.sol"; - -import { - BufferLike, - RegistryLike, - RolesLike, - VaultLike -} from "dss-allocator/deploy/AllocatorInit.sol"; - -import { AllocatorBuffer } from "dss-allocator/src/AllocatorBuffer.sol"; -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 { - ControllerInstance, - ForeignController, - ForeignControllerDeploy, - MainnetController, - MainnetControllerDeploy -} from "deploy/ControllerDeploy.sol"; - -import { - ForeignControllerInit, - MainnetControllerInit, - MintRecipient, - RateLimitData -} from "deploy/ControllerInit.sol"; - -import { MockDaiUsds } from "./mocks/MockDaiUsds.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 config; - uint256 forkId; - address admin; -} - -contract StagingDeploymentBase 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 ***/ - /**********************************************************************************************/ - - address dai; - address daiUsds; - address livePsm; - address psm; - address susds; - address usds; - - // Mocked MCD contracts - address jug; - address usdsJoin; - address vat; - - /**********************************************************************************************/ - /*** Mainnet allocation system deployments ***/ - /**********************************************************************************************/ - - address oracle; - address roles; - address registry; - - address buffer; - address vault; - - /**********************************************************************************************/ - /*** ALM system deployments ***/ - /**********************************************************************************************/ - - address baseAlmProxy; - address baseController; - - address mainnetAlmProxy; - address mainnetController; - - /**********************************************************************************************/ - /*** Base dependency deployments ***/ - /**********************************************************************************************/ - - address usdsBase; - address susdsBase; - - address psmBase; - - /**********************************************************************************************/ - /*** Deployment-specific variables ***/ - /**********************************************************************************************/ - - address deployer; - bytes32 ilk; - - uint256 USDC_UNIT_SIZE; - uint256 USDS_UNIT_SIZE; - - Domain mainnet; - Domain base; - - /**********************************************************************************************/ - /*** Helper functions ***/ - /**********************************************************************************************/ - - function _setUpDependencies(bool useLiveContracts) 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 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"); - 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)); - - // 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 - // the DAI balance of the PSM to check if it should fill or not. Filling with DAI - // 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"); - - dai = address(new MockERC20("DAI", "DAI", 18)); - usds = address(new MockERC20("USDS", "USDS", 18)); - susds = address(new MockSUsds(usds)); - - daiUsds = address(new MockDaiUsds(mainnet.admin, dai, usds)); - psm = address(new MockPSM(mainnet.admin, USDC, dai)); - - // Mint USDS into deployer so it can be transferred into usdsJoin - MockERC20(usds).mint(deployer, USDS_UNIT_SIZE); - - // Fill the psm with dai and usdc - IERC20(USDC).transfer(psm, USDC_UNIT_SIZE * 10); - MockERC20(dai).mint(psm, USDS_UNIT_SIZE); - - // Fill the DaiUsds contract with both tokens - MockERC20(dai).mint(daiUsds, USDS_UNIT_SIZE); - MockERC20(usds).mint(daiUsds, USDS_UNIT_SIZE); - } - - function _setUpAllocationSystem() internal { - vm.selectFork(mainnet.forkId); - vm.startBroadcast(); - - // Step 1: Deploy allocation system - - AllocatorSharedInstance memory allocatorSharedInstance - = AllocatorDeploy.deployShared(deployer, mainnet.admin); - - AllocatorIlkInstance memory allocatorIlkInstance = AllocatorDeploy.deployIlk( - deployer, - mainnet.admin, - allocatorSharedInstance.roles, - ilk, - usdsJoin - ); - - oracle = allocatorSharedInstance.oracle; - registry = allocatorSharedInstance.registry; - roles = allocatorSharedInstance.roles; - - buffer = allocatorIlkInstance.buffer; - vault = allocatorIlkInstance.vault; - - // Step 2: Perform partial initialization (not using library because of mocked MCD) - - RegistryLike(registry).file(ilk, "buffer", buffer); - VaultLike(vault).file("jug", jug); - BufferLike(buffer).approve(usds, vault, type(uint256).max); - RolesLike(roles).setIlkAdmin(ilk, mainnet.admin); - - // Step 3: Move ownership of both the vault and buffer to the admin - - ScriptTools.switchOwner(vault, allocatorIlkInstance.owner, mainnet.admin); - ScriptTools.switchOwner(buffer, allocatorIlkInstance.owner, mainnet.admin); - - vm.stopBroadcast(); - - // 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.name, "allocatorBuffer", buffer); - ScriptTools.exportContract(mainnet.name, "allocatorVault", vault); - } - - function _setUpALMController() internal { - vm.selectFork(mainnet.forkId); - vm.startBroadcast(); - - // Step 1: Deploy ALM controller - - ControllerInstance memory instance = MainnetControllerDeploy.deployFull({ - admin : mainnet.admin, - vault : vault, - psm : psm, - daiUsds : daiUsds, - cctp : CCTP_TOKEN_MESSENGER_MAINNET, - susds : susds - }); - - mainnetAlmProxy = instance.almProxy; - mainnetController = instance.controller; - - // Step 2: Initialize ALM controller, setting rate limits, mint recipients, and setting ACL - - // 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 - }); - RateLimitData memory unlimitedRateLimit = RateLimitData({ - maxAmount : type(uint256).max, - slope : 0 - }); - - // Configure this after Base ALM Proxy is deployed - MintRecipient[] memory mintRecipients = new MintRecipient[](0); - - MainnetControllerInit.subDaoInitFull({ - addresses: MainnetControllerInit.AddressParams({ - admin : mainnet.admin, - freezer : makeAddr("freezer"), - relayer : SAFE_MAINNET, - oldController : address(0), - psm : psm, - vault : vault, - buffer : buffer, - cctpMessenger : CCTP_TOKEN_MESSENGER_MAINNET, - dai : dai, - daiUsds : daiUsds, - usdc : USDC, - usds : usds, - susds : susds - }), - controllerInst: instance, - data: MainnetControllerInit.InitRateLimitData({ - usdsMintData : rateLimitData18, - usdsToUsdcData : rateLimitData6, - usdcToCctpData : unlimitedRateLimit, - cctpToBaseDomainData : rateLimitData6 - }), - mintRecipients: mintRecipients - }); - - // 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.name, "safe", SAFE_MAINNET); - 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(); - - // Step 1: Deploy ALM controller - - ControllerInstance memory instance = ForeignControllerDeploy.deployFull({ - admin : base.admin, - psm : address(psmBase), - usdc : USDC_BASE, - cctp : CCTP_TOKEN_MESSENGER_BASE - }); - - baseAlmProxy = instance.almProxy; - baseController = instance.controller; - - // Step 2: Initialize ALM controller, setting rate limits, mint recipients, and setting ACL - - // 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 - }); - RateLimitData memory unlimitedRateLimit = RateLimitData({ - maxAmount : type(uint256).max, - slope : 0 - }); - - MintRecipient[] memory mintRecipients = new MintRecipient[](1); - mintRecipients[0] = MintRecipient({ - domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, - mintRecipient : bytes32(uint256(uint160(mainnetAlmProxy))) - }); - - ForeignControllerInit.init({ - addresses: ForeignControllerInit.AddressParams({ - admin : base.admin, - freezer : makeAddr("freezer"), - relayer : SAFE_BASE, - oldController : address(0), - psm : psmBase, - cctpMessenger : CCTP_TOKEN_MESSENGER_BASE, - usdc : USDC_BASE, - usds : usdsBase, - susds : susdsBase - }), - controllerInst: instance, - data: ForeignControllerInit.InitRateLimitData({ - usdcDepositData : rateLimitData6, - usdcWithdrawData : rateLimitData6, - usdsDepositData : rateLimitData18, - usdsWithdrawData : rateLimitData18, - susdsDepositData : rateLimitData18, - susdsWithdrawData : rateLimitData18, - usdcToCctpData : unlimitedRateLimit, - cctpToEthereumDomainData : rateLimitData6 - }), - 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 - - 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); - } - - function _setBaseMintRecipient() internal { - vm.selectFork(mainnet.forkId); - vm.startBroadcast(); - - MainnetController(mainnetController).setMintRecipient( - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, - bytes32(uint256(uint160(baseAlmProxy))) - ); - - vm.stopBroadcast(); - } - - function _transferOwnershipOfMocks() internal { - vm.selectFork(mainnet.forkId); - vm.startBroadcast(); - - MockDaiUsds(daiUsds).transferOwnership(mainnetAlmProxy); - MockPSM(psm).transferOwnership(mainnetAlmProxy); - - vm.stopBroadcast(); - } - - function _runFullDeployment(bool useLiveContracts) internal { - // Step 1: Load general configuration - - string memory common = ScriptTools.loadConfig("common"); - - ilk = common.readString(".ilk").stringToBytes32(); - - // 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 - - CCTP_TOKEN_MESSENGER_MAINNET = mainnet.config.readAddress(".cctpTokenMessenger"); - CCTP_TOKEN_MESSENGER_BASE = base.config.readAddress(".cctpTokenMessenger"); - - 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); - _setUpAllocationSystem(); - _setUpALMController(); - _setUpBasePSM(); - _setUpBaseALMController(); - _setBaseMintRecipient(); - - if (!useLiveContracts) _transferOwnershipOfMocks(); - - ScriptTools.exportContract(mainnet.name, "admin", deployer); - ScriptTools.exportContract(base.name, "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/script/staging/test/DeployEthereum.t.sol b/script/staging/test/DeployEthereum.t.sol deleted file mode 100644 index f0cc791..0000000 --- a/script/staging/test/DeployEthereum.t.sol +++ /dev/null @@ -1,472 +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 { AllocatorRegistry } from "lib/dss-allocator/src/AllocatorRegistry.sol"; -import { AllocatorRoles } from "lib/dss-allocator/src/AllocatorRoles.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 { PSM3, IERC20 } from "lib/spark-psm/src/PSM3.sol"; -import { IRateProviderLike } from "lib/spark-psm/src/interfaces/IRateProviderLike.sol"; - -interface IVatLike { - function can(address, address) external view returns (uint256); -} - -contract DeployEthereumTest is Test { - - using stdJson for *; - using DomainHelpers for *; - using CCTPBridgeTesting for *; - using ScriptTools for *; - - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - - // Common variables - address admin; - - // Configuration data - string inputMainnet; - string outputMainnet; - string inputBase; - string outputBase; - - // Bridging - Domain mainnet; - Domain base; - Bridge cctpBridge; - - // Mainnet contracts - - Usds usds; - SUsds susds; - IERC20 usdc; - IERC20 dai; - - AllocatorVault vault; - AllocatorBuffer buffer; - AllocatorRegistry registry; - AllocatorRoles roles; - - address safeMainnet; - address usdsJoin; - - ALMProxy almProxy; - MainnetController mainnetController; - RateLimits rateLimits; - - // Base contracts - - address safeBase; - - PSM3 psmBase; - - IERC20 usdsBase; - IERC20 susdsBase; - IERC20 usdcBase; - - ALMProxy foreignAlmProxy; - ForeignController foreignController; - RateLimits foreignRateLimits; - - /**********************************************************************************************/ - /**** Setup ***/ - /**********************************************************************************************/ - - function setUp() public { - vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); - - // Domains and bridge - mainnet = getChain("mainnet").createSelectFork(); - base = getChain("base").createFork(); - 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); - - // Roles - admin = outputMainnet.readAddress(".admin"); - safeMainnet = outputMainnet.readAddress(".safe"); - - // 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"); - - // ALM system - almProxy = ALMProxy(payable(outputMainnet.readAddress(".almProxy"))); - mainnetController = MainnetController(outputMainnet.readAddress(".controller")); - rateLimits = RateLimits(outputMainnet.readAddress(".rateLimits")); - - // Base roles - safeBase = outputBase.readAddress(".safe"); - - // Base tokens - usdsBase = IERC20(outputBase.readAddress(".usds")); - susdsBase = IERC20(outputBase.readAddress(".susds")); - usdcBase = IERC20(outputBase.readAddress(".usdc")); - - // Base ALM system - foreignAlmProxy = ALMProxy(payable(outputBase.readAddress(".almProxy"))); - foreignController = ForeignController(outputBase.readAddress(".controller")); - foreignRateLimits = RateLimits(outputBase.readAddress(".rateLimits")); - - // Base PSM - psmBase = PSM3(outputBase.readAddress(".psm")); - - mainnet.selectFork(); - } - - /**********************************************************************************************/ - /**** Tests ***/ - /**********************************************************************************************/ - - function test_mainnetConfiguration() public { - mainnet.selectFork(); - - // 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.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")); - - assertEq(mainnetController.psmTo18ConversionFactor(), 1e12); - assertEq(mainnetController.active(), true); - - assertEq( - mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), - bytes32(uint256(uint160(outputBase.readAddress(".almProxy")))) - ); - - // ALM system roles - - assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(mainnetController.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - - assertEq(mainnetController.hasRole(mainnetController.FREEZER(), makeAddr("freezer")), true); - assertEq(mainnetController.hasRole(mainnetController.RELAYER(), safeMainnet), true); - - assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); - - assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); - - // Allocation system deployment and initialization - - bytes32 ilk = ScriptTools.readInput("common").readString(".ilk").stringToBytes32(); - - assertEq(registry.buffers(ilk), address(buffer)); - assertEq(address(vault.jug()), outputMainnet.readAddress(".jug")); - - assertEq(usds.allowance(address(buffer), address(almProxy)), type(uint256).max); - assertEq(usds.allowance(address(vault), usdsJoin), type(uint256).max); - - assertEq(roles.ilkAdmins(ilk), admin); - - 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); - - address vat = outputMainnet.readAddress(".vat"); - - 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); - - // NOTE: Not asserting vat.can because vat is mocked in this deployment and storage doesn't - // get updated on vat.hope in vault constructor - - // Starting token balances - - assertEq(usds.balanceOf(address(almProxy)), 0); - assertEq(usdc.balanceOf(address(almProxy)), 0); - assertEq(susds.balanceOf(address(almProxy)), 0); - - uint256 usdsUnitSize = ScriptTools.readInput("common").readUint(".usdsUnitSize"); - uint256 usdcUnitSize = ScriptTools.readInput("common").readUint(".usdcUnitSize"); - - // 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); - - // Rate limits - - uint256 max6 = usdcUnitSize * 1e6 * 5; - uint256 max18 = usdcUnitSize * 1e18 * 5; - uint256 slope6 = usdcUnitSize * 1e6 / 4 hours; - uint256 slope18 = usdcUnitSize * 1e18 / 4 hours; - - bytes32 domainKeyBase = RateLimitHelpers.makeDomainKey( - mainnetController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_BASE - ); - - _assertRateLimitData(mainnetController.LIMIT_USDS_MINT(), max18, slope18); - _assertRateLimitData(mainnetController.LIMIT_USDS_TO_USDC(), max6, slope6); - _assertRateLimitData(domainKeyBase, max6, slope6); - - _assertRateLimitData(mainnetController.LIMIT_USDC_TO_CCTP(), type(uint256).max, 0); - } - - function test_baseConfiguration() public { - base.selectFork(); - - // PSM configuration - - 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")); - - assertEq(psmBase.totalAssets(), 1e18); - assertEq(psmBase.totalShares(), 1e18); - - assertEq(IRateProviderLike(psmBase.rateProvider()).getConversionRate(), 1.2e27); - - // Foreign controller initialization - - 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")); - - assertEq(foreignController.active(), true); - - assertEq( - foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), - bytes32(uint256(uint160(outputMainnet.readAddress(".almProxy")))) - ); - - // ALM System roles - - assertEq(foreignAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(foreignRateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - assertEq(foreignController.hasRole(DEFAULT_ADMIN_ROLE, admin), true); - - assertEq(foreignController.hasRole(foreignController.FREEZER(), makeAddr("freezer")), true); - assertEq(foreignController.hasRole(foreignController.RELAYER(), safeBase), true); - - assertEq(foreignAlmProxy.hasRole(foreignAlmProxy.CONTROLLER(), address(foreignController)), true); - - assertEq(foreignRateLimits.hasRole(foreignRateLimits.CONTROLLER(), address(foreignController)), true); - - // Starting token balances - - uint256 usdsUnitSize = ScriptTools.readInput("common").readUint(".usdsUnitSize"); - uint256 usdcUnitSize = ScriptTools.readInput("common").readUint(".usdcUnitSize"); - - assertEq(usdsBase.balanceOf(address(foreignAlmProxy)), usdsUnitSize * 1e18); - assertEq(susdsBase.balanceOf(address(foreignAlmProxy)), usdsUnitSize * 1e18); - - // Rate limits - - uint256 max6 = usdcUnitSize * 1e6 * 5; - uint256 max18 = usdcUnitSize * 1e18 * 5; - uint256 slope6 = usdcUnitSize * 1e6 / 4 hours; - uint256 slope18 = usdcUnitSize * 1e18 / 4 hours; - - bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey( - foreignController.LIMIT_USDC_TO_DOMAIN(), - CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM - ); - - bytes32 cctpKey = foreignController.LIMIT_USDC_TO_CCTP(); - - _assertDepositRateLimitData(usdcBase, max6, slope6); - _assertDepositRateLimitData(usdsBase, max18, slope18); - _assertDepositRateLimitData(susdsBase, max18, slope18); - - _assertWithdrawRateLimitData(usdcBase, max6, slope6); - _assertWithdrawRateLimitData(usdsBase, max18, slope18); - _assertWithdrawRateLimitData(susdsBase, max18, slope18); - - _assertRateLimitData(address(foreignRateLimits), cctpKey, type(uint256).max, 0); - - _assertRateLimitData(address(foreignRateLimits), domainKeyEthereum, max6, slope6); - } - - function test_mintUSDS() public { - assertEq(usds.balanceOf(address(almProxy)), 0); - - vm.prank(safeMainnet); - mainnetController.mintUSDS(10e18); - - assertEq(usds.balanceOf(address(almProxy)), 10e18); - } - - function test_mintAndSwapToUSDC() public { - assertEq(usdc.balanceOf(address(almProxy)), 0); - - vm.startPrank(safeMainnet); - mainnetController.mintUSDS(10e18); - mainnetController.swapUSDSToUSDC(10e6); - vm.stopPrank(); - - assertEq(usdc.balanceOf(address(almProxy)), 10e6); - } - - function test_transferCCTP() public { - base.selectFork(); - - assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), 0); - - mainnet.selectFork(); - - vm.startPrank(safeMainnet); - mainnetController.mintUSDS(10e18); - mainnetController.swapUSDSToUSDC(10e6); - mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); - vm.stopPrank(); - - cctpBridge.relayMessagesToDestination(true); - - assertEq(usdcBase.balanceOf(address(foreignAlmProxy)), 10e6); - } - - function test_transferToPSM() public { - base.selectFork(); - - assertEq(usdcBase.balanceOf(address(psmBase)), 0); - - mainnet.selectFork(); - - vm.startPrank(safeMainnet); - 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), 10e6); - vm.stopPrank(); - - assertEq(usdcBase.balanceOf(address(psmBase)), 10e6); - - assertEq(psmBase.shares(address(foreignAlmProxy)), 10e18); - } - - function test_fullRoundTrip() public { - mainnet.selectFork(); - - vm.startPrank(safeMainnet); - mainnetController.mintUSDS(1e18); - mainnetController.swapUSDSToUSDC(1e6); - mainnetController.transferUSDCToCCTP(1e6, 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.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.stopPrank(); - } - - /**********************************************************************************************/ - /**** Helper functions ***/ - /**********************************************************************************************/ - - function _assertDepositRateLimitData(IERC20 asset, uint256 maxAmount, uint256 slope) - internal view - { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_DEPOSIT(), - address(asset) - ); - - _assertRateLimitData(address(foreignRateLimits), assetKey, maxAmount, slope); - } - - function _assertWithdrawRateLimitData(IERC20 asset, uint256 maxAmount, uint256 slope) - internal view - { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_WITHDRAW(), - address(asset) - ); - - _assertRateLimitData(address(foreignRateLimits), assetKey, maxAmount, slope); - } - - 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 _assertRateLimitData(address rateLimits_, bytes32 key, uint256 maxAmount, uint256 slope) - internal view - { - IRateLimits.RateLimitData memory data = IRateLimits(rateLimits_).getRateLimitData(key); - - assertEq(data.maxAmount, maxAmount); - assertEq(data.slope, slope); - assertEq(data.lastAmount, maxAmount); - - // Deployments are done in the past - assertLe(data.lastUpdated, block.timestamp); - - // Deployment is assumed to be untouched - assertEq(IRateLimits(rateLimits_).getCurrentRateLimit(key), maxAmount); - } - -} 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(); - } - -} diff --git a/script/staging/test/StagingDeployment.t.sol b/script/staging/test/StagingDeployment.t.sol new file mode 100644 index 0000000..e4fc644 --- /dev/null +++ b/script/staging/test/StagingDeployment.t.sol @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ScriptTools } from "dss-test/ScriptTools.sol"; + +import "forge-std/Test.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/Base.sol"; +import { Ethereum } from "spark-address-registry/Ethereum.sol"; + +import { PSM3 } from "spark-psm/src/PSM3.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"; + +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"; + +interface IVatLike { + function can(address, address) external view returns (uint256); +} + +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 = 20241227; + + // Common variables + address admin; + + // Configuration data + string inputBase; + string inputMainnet; + string outputBase; + string outputBaseDeps; + string outputMainnet; + string outputMainnetDeps; + + // Bridging + Domain mainnet; + Domain base; + Bridge cctpBridge; + + // Mainnet contracts + + Usds usds; + SUsds susds; + IERC20 usdc; + IERC20 dai; + + address vault; + address relayerSafe; + address usdsJoin; + + ALMProxy almProxy; + MainnetController mainnetController; + RateLimits rateLimits; + + // Base contracts + + address relayerSafeBase; + + PSM3 psmBase; + + IERC20 usdsBase; + IERC20 susdsBase; + IERC20 usdcBase; + + ALMProxy baseAlmProxy; + ForeignController baseController; + RateLimits baseRateLimits; + + /**********************************************************************************************/ + /**** Setup ***/ + /**********************************************************************************************/ + + function setUp() public virtual { + vm.setEnv("FOUNDRY_ROOT_CHAINID", "1"); + + // Domains and bridge + mainnet = getChain("mainnet").createSelectFork(); + base = getChain("base").createFork(); + cctpBridge = CCTPBridgeTesting.createCircleBridge(mainnet, base); + + // JSON data + 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 = outputMainnetDeps.readAddress(".admin"); + relayerSafe = outputMainnetDeps.readAddress(".relayer"); + + // Tokens + 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"))); + rateLimits = RateLimits(outputMainnet.readAddress(".rateLimits")); + mainnetController = MainnetController(outputMainnet.readAddress(".controller")); + + // Base roles + relayerSafeBase = outputBaseDeps.readAddress(".relayer"); + + // Base tokens + usdsBase = IERC20(inputBase.readAddress(".usds")); + susdsBase = IERC20(inputBase.readAddress(".susds")); + usdcBase = IERC20(inputBase.readAddress(".usdc")); + + // Base ALM system + baseAlmProxy = ALMProxy(payable(outputBase.readAddress(".almProxy"))); + baseController = ForeignController(outputBase.readAddress(".controller")); + baseRateLimits = RateLimits(outputBase.readAddress(".rateLimits")); + + // Base PSM + psmBase = PSM3(inputBase.readAddress(".psm")); + + mainnet.selectFork(); + + deal(address(usds), address(usdsJoin), 1000e18); // Ensure there is enough balance + } +} + +contract MainnetStagingDeploymentTests is StagingDeploymentTestBase { + + function test_mintUSDS() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); + + vm.prank(relayerSafe); + mainnetController.mintUSDS(10e18); + + assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); + } + + function test_mintAndSwapToUSDC() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + vm.stopPrank(); + + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); + } + + function test_depositAndWithdrawUsdsFromSUsds() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.depositERC4626(Ethereum.SUSDS, 10e18); + skip(1 days); + mainnetController.withdrawERC4626(Ethereum.SUSDS, 10e18); + vm.stopPrank(); + + assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18); + + assertGe(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0); // Interest earned + } + + function test_depositAndRedeemUsdsFromSUsds() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); + + 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(); + + assertGe(usds.balanceOf(address(almProxy)), startingBalance + 10e18); // Interest earned + + assertEq(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0); + } + + function test_depositAndWithdrawUsdsFromAave() public { + uint256 startingBalance = usds.balanceOf(address(almProxy)); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.depositAave(AUSDS, 10e6); + skip(1 days); + mainnetController.withdrawAave(AUSDS, type(uint256).max); + vm.stopPrank(); + + assertGe(usds.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned + } + + function test_depositAndWithdrawUsdcFromAave() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); + + 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(usdc.balanceOf(address(almProxy)), startingBalance + 10e6); // Interest earned + } + + function test_mintDepositCooldownAssetsBurnUsde() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.prepareUSDeMint(10e6); + vm.stopPrank(); + + _simulateUsdeMint(10e6); + + vm.startPrank(relayerSafe); + mainnetController.depositERC4626(Ethereum.SUSDE, 10e18); + skip(1 days); + mainnetController.cooldownAssetsSUSDe(10e18 - 1); // Rounding + skip(7 days); + mainnetController.unstakeSUSDe(); + mainnetController.prepareUSDeBurn(10e18 - 1); + vm.stopPrank(); + + _simulateUsdeBurn(10e18 - 1); + + assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Rounding not captured + + assertGe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); // Interest earned + } + + function test_mintDepositCooldownSharesBurnUsde() public { + uint256 startingBalance = usdc.balanceOf(address(almProxy)); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.prepareUSDeMint(10e6); + vm.stopPrank(); + + _simulateUsdeMint(10e6); + + 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(); + + _simulateUsdeBurn(usdeAmount); + + assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Interest earned (rounding) + + assertEq(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); + } + + /**********************************************************************************************/ + /**** Helper functions ***/ + /**********************************************************************************************/ + + // 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. + + // TODO: Try doing ethena minting with EIP-712 signatures (vm.sign) + + 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 + ); + } + + 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); + } + +} + +contract BaseStagingDeploymentTests is StagingDeploymentTestBase { + + using DomainHelpers for *; + using CCTPBridgeTesting for *; + + address constant AUSDC_BASE = 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB; + address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant MORPHO_VAULT_USDC = 0x305E03Ed9ADaAB22F4A58c24515D79f2B1E2FD5D; + + function setUp() public override { + super.setUp(); + + base.selectFork(); + } + + function test_transferCCTP() public { + base.selectFork(); + + uint256 startingBalance = usdcBase.balanceOf(address(baseAlmProxy)); + + mainnet.selectFork(); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.stopPrank(); + + cctpBridge.relayMessagesToDestination(true); + + assertEq(usdcBase.balanceOf(address(baseAlmProxy)), startingBalance + 10e6); + } + + function test_transferToPSM() public { + base.selectFork(); + + uint256 startingBalance = usdcBase.balanceOf(address(psmBase)); + + mainnet.selectFork(); + + vm.startPrank(relayerSafe); + mainnetController.mintUSDS(10e18); + mainnetController.swapUSDSToUSDC(10e6); + mainnetController.transferUSDCToCCTP(10e6, CCTPForwarder.DOMAIN_ID_CIRCLE_BASE); + vm.stopPrank(); + + cctpBridge.relayMessagesToDestination(true); + + uint256 startingShares = psmBase.shares(address(baseAlmProxy)); + + vm.startPrank(relayerSafeBase); + baseController.depositPSM(address(usdcBase), 10e6); + vm.stopPrank(); + + assertEq(usdcBase.balanceOf(address(psmBase)), startingBalance + 10e6); + + assertEq(psmBase.shares(address(baseAlmProxy)), startingShares + psmBase.convertToShares(10e18)); + } + + function test_addAndRemoveFundsFromBasePSM() public { + 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.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(); + + cctpBridge.relayMessagesToSource(true); + + vm.startPrank(relayerSafe); + mainnetController.swapUSDCToUSDS(10e6 - 1); + mainnetController.burnUSDS((10e6 - 1) * 1e12); + vm.stopPrank(); + } + + function test_addAndRemoveFundsFromBaseAAVE() public { + 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.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 test_depositWithdrawFundsFromBaseMorphoUsdc() 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.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 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(); + } + + // 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); + + IMetaMorpho(MORPHO_VAULT_USDC).acceptCap(usdcParams); + + Id[] memory supplyQueueUSDC = new Id[](1); + supplyQueueUSDC[0] = MarketParamsLib.id(usdcParams); + IMetaMorpho(MORPHO_VAULT_USDC).setSupplyQueue(supplyQueueUSDC); + + vm.stopPrank(); + } + +} 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 3b3c1c9..922bd3d 100644 --- a/src/ForeignController.sol +++ b/src/ForeignController.sol @@ -1,17 +1,25 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.21; -import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +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 { 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 "./RateLimitHelpers.sol"; -import { RateLimitHelpers } from "src/RateLimitHelpers.sol"; +interface IATokenWithPool is IAToken { + function POOL() external view returns(address); +} contract ForeignController is AccessControl { @@ -40,6 +48,10 @@ 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_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"); @@ -214,6 +226,134 @@ 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 + 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. + shares = abi.decode( + proxy.doCall( + token, + abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy))) + ), + (uint256) + ); + } + + // NOTE: !!! Rate limited at end of function !!! + 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) + ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + assets + ); + } + + /**********************************************************************************************/ + /*** 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)) + ); + } + + // NOTE: !!! Rate limited at end of function !!! + 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) + ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_WITHDRAW, aToken), + amountWithdrawn + ); + } + /**********************************************************************************************/ /*** Internal helper functions ***/ /**********************************************************************************************/ diff --git a/src/MainnetController.sol b/src/MainnetController.sol index b0d646c..4fe4844 100644 --- a/src/MainnetController.sol +++ b/src/MainnetController.sol @@ -1,16 +1,21 @@ // 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 { IALMProxy } from "src/interfaces/IALMProxy.sol"; -import { ICCTPLike } from "src/interfaces/CCTPInterfaces.sol"; -import { IRateLimits } from "src/interfaces/IRateLimits.sol"; +import { Ethereum } from "spark-address-registry/Ethereum.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); @@ -18,8 +23,15 @@ interface IDaiUsdsLike { function usdsToDai(address usr, uint256 wad) external; } -interface ISUSDSLike is IERC4626 { - function usds() external view returns(address); +interface IEthenaMinterLike { + function setDelegatedSigner(address delegateSigner) external; + function removeDelegatedSigner(address delegateSigner) external; +} + +interface ISUSDELike is IERC4626 { + function cooldownAssets(uint256 usdeAmount) external; + function cooldownShares(uint256 susdeAmount) external; + function unstake(address receiver) external; } interface IVaultLike { @@ -36,6 +48,10 @@ interface IPSMLike { function to18ConversionFactor() external view returns (uint256); } +interface IATokenWithPool is IAToken { + function POOL() external view returns(address); +} + contract MainnetController is AccessControl { /**********************************************************************************************/ @@ -63,24 +79,33 @@ 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_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"); + 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; - ISUSDSLike public immutable susds; + ISUSDELike public immutable susde; uint256 public immutable psmTo18ConversionFactor; @@ -99,8 +124,7 @@ contract MainnetController is AccessControl { address vault_, address psm_, address daiUsds_, - address cctp_, - address susds_ + address cctp_ ) { _grantRole(DEFAULT_ADMIN_ROLE, admin_); @@ -112,10 +136,13 @@ contract MainnetController is AccessControl { daiUsds = IDaiUsdsLike(daiUsds_); cctp = ICCTPLike(cctp_); - susds = ISUSDSLike(susds_ ); + ethenaMinter = IEthenaMinterLike(Ethereum.ETHENA_MINTER); + + susde = ISUSDELike(Ethereum.SUSDE); dai = IERC20(daiUsds.dai()); usdc = IERC20(psm.gem()); - usds = IERC20(susds.usds()); + usds = IERC20(Ethereum.USDS); + usde = IERC20(Ethereum.USDE); psmTo18ConversionFactor = psm.to18ConversionFactor(); @@ -203,54 +230,202 @@ 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) - external onlyRole(RELAYER) isActive returns (uint256 shares) + function withdrawERC4626(address token, uint256 amount) + external + onlyRole(RELAYER) + isActive + rateLimited( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + amount + ) + 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) + // NOTE: !!! Rate limited at end of function !!! + 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) + ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token), + assets + ); + } + + /**********************************************************************************************/ + /*** 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)) + ); + } + + // NOTE: !!! Rate limited at end of function !!! + 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) ); + + rateLimits.triggerRateLimitDecrease( + RateLimitHelpers.makeAssetKey(LIMIT_AAVE_WITHDRAW, aToken), + amountWithdrawn + ); + } + + /**********************************************************************************************/ + /*** Relayer 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 Ethena's mint/redeem per-block limits include 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)) + ); + } + + // NOTE: !!! Rate limited at end of function !!! + function cooldownSharesSUSDe(uint256 susdeAmount) + external + onlyRole(RELAYER) + isActive + returns (uint256 cooldownAmount) + { + 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 { + proxy.doCall( + address(susde), + abi.encodeCall(susde.unstake, (address(proxy))) + ); } /**********************************************************************************************/ 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/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/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..b8cfc9e --- /dev/null +++ b/test/base-fork/Aave.t.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol"; + +import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; + +import "./ForkTestBase.t.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 + ); + rateLimits.setRateLimitData( + RateLimitHelpers.makeAssetKey( + foreignController.LIMIT_AAVE_WITHDRAW(), + ATOKEN_USDC + ), + 1_000_000e6, + uint256(5_000_000e6) / 1 days + ); + + vm.stopPrank(); + + startingAUSDCBalance = usdcBase.balanceOf(address(ausdc)); + } + + function _getBlock() internal pure override returns (uint256) { + return 22841965; // November 24, 2024 + } + +} + +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_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); + + 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); + } + + 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 { + 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); + foreignController.depositAave(ATOKEN_USDC, 500_000e6); + + skip(1 days); + + uint256 fullBalance = ausdc.balanceOf(address(almProxy)); + + assertGe(fullBalance, 500_000e6); + + assertEq(ausdc.balanceOf(address(almProxy)), fullBalance); + 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); + + 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 + + assertEq(rateLimits.getCurrentRateLimit(key), 600_000e6); + + // Withdraw all + vm.prank(relayer); + assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6 - 1); + + assertEq(ausdc.balanceOf(address(almProxy)), 0); + 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 + 1); + + // 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/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 deleted file mode 100644 index b0c48d1..0000000 --- a/test/base-fork/DeployAndInit.t.sol +++ /dev/null @@ -1,900 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -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 "test/base-fork/ForkTestBase.t.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 { RateLimitHelpers } from "src/RateLimitHelpers.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 - ) - external - { - ForeignControllerInit.init(params, controllerInst, data, mintRecipients); - } - -} - -contract ForeignControllerDeployAndInitTestBase 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 - ) - { - addresses = ForeignControllerInit.AddressParams({ - admin : SPARK_EXECUTOR, - freezer : freezer, - relayer : relayer, - oldController : address(0), // Empty - psm : address(psmBase), - cctpMessenger : CCTP_MESSENGER_BASE, - usdc : USDC_BASE, - usds : address(usdsBase), - susds : address(susdsBase) - }); - - RateLimitData memory usdcDepositData = RateLimitData({ - maxAmount : 1_000_000e6, - slope : uint256(1_000_000e6) / 4 hours - }); - - 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 - }); - - 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({ - domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, - mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) - }); - } - -} - -contract ForeignControllerDeployAndInitFailureTests is ForeignControllerDeployAndInitTestBase { - - LibraryWrapper wrapper; - - ControllerInstance public controllerInst; - - address public mismatchAddress = makeAddr("mismatchAddress"); - - // Default parameters for success that are overridden for failure tests - - ForeignControllerInit.AddressParams addresses; - ForeignControllerInit.InitRateLimitData rateLimitData; - MintRecipient[] mintRecipients; - - function setUp() public override { - super.setUp(); - - controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - MintRecipient[] memory mintRecipients_ = new MintRecipient[](1); - - ( addresses, rateLimitData, 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); - - // Admin will be calling the library from its own address - vm.etch(SPARK_EXECUTOR, address(new LibraryWrapper()).code); - - wrapper = LibraryWrapper(SPARK_EXECUTOR); - } - - /**********************************************************************************************/ - /*** ACL failure modes ***/ - /**********************************************************************************************/ - - 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(); - - vm.expectRevert("ForeignControllerInit/incorrect-admin-almProxy"); - wrapper.init(addresses, controllerInst, rateLimitData, 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(); - - vm.expectRevert("ForeignControllerInit/incorrect-admin-rateLimits"); - wrapper.init(addresses, controllerInst, rateLimitData, 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(); - - vm.expectRevert("ForeignControllerInit/incorrect-admin-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - /**********************************************************************************************/ - /*** Controller constructor failure modes ***/ - /**********************************************************************************************/ - - function test_init_incorrectAlmProxy() external { - // Deploy new address that will not EVM revert on OZ ACL check - controllerInst.almProxy = address(new ALMProxy(SPARK_EXECUTOR)); - - vm.expectRevert("ForeignControllerInit/incorrect-almProxy"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectRateLimits() external { - // Deploy new address that will not EVM revert on OZ ACL check - controllerInst.rateLimits = address(new RateLimits(SPARK_EXECUTOR)); - - vm.expectRevert("ForeignControllerInit/incorrect-rateLimits"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectPsm() external { - addresses.psm = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-psm"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectUsdc() external { - addresses.usdc = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-usdc"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectCctp() external { - addresses.cctpMessenger = mismatchAddress; - - vm.expectRevert("ForeignControllerInit/incorrect-cctp"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_controllerInactive() external { - // Cheating to set this outside of init scripts so that the controller can be frozen - vm.prank(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); - } - - /**********************************************************************************************/ - /*** Sanity check failure modes ***/ - /**********************************************************************************************/ - - 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 { - // 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); - - // Approve from address(this) cause it received the one wei - // Redo the seeding - usdsBase.approve(address(psmBase), 1); - psmBase.deposit(address(usdsBase), address(0), 1); - - assertEq(psmBase.totalAssets(), 1e18); - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_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 - - usdsBase.transfer(address(psmBase), 1); // Transfer one wei to PSM to update totalAssets - - assertEq(psmBase.totalAssets(), 1e18); - assertEq(psmBase.totalShares(), 1e18 - 1); - - vm.expectRevert("ForeignControllerInit/psm-totalShares-not-seeded"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - - // Do deposit to update shares, need to do 2 wei to get back to 1e18 because of rounding - deal(address(usdsBase), address(this), 2); - usdsBase.approve(address(psmBase), 2); - psmBase.deposit(address(usdsBase), address(0), 2); - - assertEq(psmBase.totalAssets(), 1e18 + 2); - assertEq(psmBase.totalShares(), 1e18); - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_incorrectPsmUsdc() external { - ERC20Mock wrongUsdc = new ERC20Mock(); - - deal(address(usdsBase), address(this), 1e18); // For seeding PSM during deployment - - // Deploy a new PSM with the wrong USDC - psmBase = IPSM3(PSM3Deploy.deploy( - SPARK_EXECUTOR, address(wrongUsdc), address(usdsBase), address(susdsBase), SSR_ORACLE - )); - - // Deploy a new controller pointing to misconfigured PSM - controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - addresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - - vm.expectRevert("ForeignControllerInit/psm-incorrect-usdc"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_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 - psmBase = IPSM3(PSM3Deploy.deploy( - SPARK_EXECUTOR, USDC_BASE, address(wrongUsds), address(susdsBase), SSR_ORACLE - )); - - // Deploy a new controller pointing to misconfigured PSM - controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - addresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM - - vm.expectRevert("ForeignControllerInit/psm-incorrect-usds"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_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 - psmBase = IPSM3(PSM3Deploy.deploy( - SPARK_EXECUTOR, USDC_BASE, address(usdsBase), address(wrongSUsds), SSR_ORACLE - )); - - // Deploy a new controller pointing to misconfigured PSM - controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - 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; - - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - /**********************************************************************************************/ - /*** Rate limit slope precision boundary failure modes ***/ - /**********************************************************************************************/ - - function test_init_incorrectUsdcDepositData_slopePrecisionBoundary() external { - rateLimitData.usdcDepositData.slope = uint256(1e18) / 1 hours + 1; // 1 USDS, but 1 trillion USDC - - 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); - } - - /**********************************************************************************************/ - /*** Old controller role check tests ***/ - /**********************************************************************************************/ - - function test_init_oldControllerDoesNotHaveRoleInAlmProxy() external { - _deployNewControllerAfterExistingControllerInit(); - - // Revoke the old controller address in ALM proxy - - 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 - - vm.expectRevert("ForeignControllerInit/old-controller-not-almProxy-controller"); - wrapper.init(addresses, controllerInst, rateLimitData, mintRecipients); - } - - function test_init_oldControllerDoesNotHaveRoleInRateLimits() external { - _deployNewControllerAfterExistingControllerInit(); - - // Revoke the old controller address - - vm.startPrank(SPARK_EXECUTOR); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.oldController); - vm.stopPrank(); - - // Try to init 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); - } - - /**********************************************************************************************/ - /*** Helper functions ***/ - /**********************************************************************************************/ - - function _deployNewControllerAfterExistingControllerInit() internal { - // Successfully init first controller - - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); - - // Deploy a new controller (controllerInst is used in init with new controller address) - - controllerInst.controller = ForeignControllerDeploy.deployController( - SPARK_EXECUTOR, - controllerInst.almProxy, - controllerInst.rateLimits, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - addresses.oldController = address(foreignController); - } - -} - -contract ForeignControllerDeployAndInitSuccessTests is ForeignControllerDeployAndInitTestBase { - - function test_deployAllAndInit() external { - // Perform new deployments against existing fork environment - - ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull( - SPARK_EXECUTOR, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - foreignController = ForeignController(controllerInst.controller); - 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); - - 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); - - assertEq(foreignController.active(), true); - - // Perform SubDAO initialization (from governance relay during spell) - // Setting rate limits to different values from setUp to make assertions more robust - - ( - ForeignControllerInit.AddressParams memory addresses, - ForeignControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); - - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - 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(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 - ); - - assertEq( - foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), - bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) - ); - } - - 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(); - - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); - - // Example of how an upgrade would work - address newController = ForeignControllerDeploy.deployController( - SPARK_EXECUTOR, - controllerInst.almProxy, - controllerInst.rateLimits, - address(psmBase), - USDC_BASE, - CCTP_MESSENGER_BASE - ); - - // Overwrite storage of previous deployments in setUp - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - rateLimits = RateLimits(controllerInst.rateLimits); - - address oldController = address(controllerInst.controller); - - controllerInst.controller = newController; // Overwrite struct for param - - // All other info is the same, just need to transfer ACL - addresses.oldController = oldController; - - 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); - - vm.startPrank(SPARK_EXECUTOR); - ForeignControllerInit.init( - addresses, - controllerInst, - rateLimitData, - 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); - } - - function _assertDepositRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_DEPOSIT(), - address(asset) - ); - - _assertRateLimitData(assetKey, expectedData); - } - - function _assertWithdrawRateLimitData(IERC20 asset, RateLimitData memory expectedData) internal { - bytes32 assetKey = RateLimitHelpers.makeAssetKey( - foreignController.LIMIT_PSM_WITHDRAW(), - address(asset) - ); - - _assertRateLimitData(assetKey, expectedData); - } - - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal { - IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); - - 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(rateLimits.getCurrentRateLimit(domainKey), expectedData.maxAmount); - } - -} diff --git a/test/base-fork/ForkTestBase.t.sol b/test/base-fork/ForkTestBase.t.sol index 2cf797b..3a6ea38 100644 --- a/test/base-fork/ForkTestBase.t.sol +++ b/test/base-fork/ForkTestBase.t.sol @@ -3,7 +3,7 @@ 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"; @@ -12,29 +12,18 @@ 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 "lib/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"; +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 { CCTPForwarder } from "lib/xchain-helpers/src/forwarders/CCTPForwarder.sol"; +import { ALMProxy } from "../../src/ALMProxy.sol"; +import { ForeignController } from "../../src/ForeignController.sol"; +import { RateLimits } from "../../src/RateLimits.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 { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; contract ForkTestBase is Test { @@ -50,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 ***/ @@ -88,7 +78,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())); @@ -108,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, @@ -125,55 +115,80 @@ 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(); } + // 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/base-fork/InitAndUpgrade.t.sol b/test/base-fork/InitAndUpgrade.t.sol new file mode 100644 index 0000000..a63bab4 --- /dev/null +++ b/test/base-fork/InitAndUpgrade.t.sol @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "../../test/base-fork/ForkTestBase.t.sol"; + +import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol"; + +import { ControllerInstance } from "../../deploy/ControllerInstance.sol"; +import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol"; + +import { ForeignControllerInit as Init } from "../../deploy/ForeignControllerInit.sol"; + +// Necessary to get error message assertions to work +contract LibraryWrapper { + + 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 + { + Init.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); + } + +} + +contract ForeignControllerInitAndUpgradeTestBase is ForkTestBase { + + function _getDefaultParams() + internal returns ( + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients + ) + { + configAddresses = Init.ConfigAddressParams({ + freezer : freezer, + relayer : relayer, + oldController : address(0) + }); + + checkAddresses = Init.CheckAddressParams({ + admin : Base.SPARK_EXECUTOR, + psm : address(psmBase), + cctp : Base.CCTP_TOKEN_MESSENGER, + usdc : address(usdcBase), + susds : address(susdsBase), + usds : address(usdsBase) + }); + + mintRecipients = new Init.MintRecipient[](1); + + mintRecipients[0] = Init.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM, + mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) + }); + } + +} + +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; + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + function setUp() public override { + super.setUp(); + + 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 + })); + + 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]); + + controllerInst = ControllerInstance({ + almProxy : address(almProxy), + controller : address(foreignController), + rateLimits : address(rateLimits) + }); + + // 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 23900000; // Dec 19, 2024 + } + + /**********************************************************************************************/ + /*** ACL tests ***/ + /**********************************************************************************************/ + + 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.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + 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.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function test_initAlmSystem_upgradeController_incorrectAdminController() external { + vm.prank(Base.SPARK_EXECUTOR); + foreignController.revokeRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR); + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-admin-controller")); + } + + /**********************************************************************************************/ + /*** Constructor tests ***/ + /**********************************************************************************************/ + + function test_initAlmSystem_upgradeController_incorrectAlmProxy() external { + // Deploy new address that will not EVM revert on OZ ACL check + controllerInst.almProxy = address(new ALMProxy(Base.SPARK_EXECUTOR)); + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-almProxy")); + } + + function test_initAlmSystem_upgradeController_incorrectRateLimits() external { + // Deploy new address that will not EVM revert on OZ ACL check + controllerInst.rateLimits = address(new RateLimits(Base.SPARK_EXECUTOR)); + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-rateLimits")); + } + + function test_initAlmSystem_upgradeController_incorrectPsm() external { + checkAddresses.psm = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-psm")); + } + + function test_initAlmSystem_upgradeController_incorrectUsdc() external { + checkAddresses.usdc = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-usdc")); + } + + function test_initAlmSystem_upgradeController_incorrectCctp() external { + checkAddresses.cctp = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/incorrect-cctp")); + } + + function test_initAlmSystem_upgradeController_controllerInactive() external { + // Cheating to set this outside of init scripts so that the controller can be frozen + vm.startPrank(Base.SPARK_EXECUTOR); + foreignController.grantRole(FREEZER, freezer); + + vm.startPrank(freezer); + foreignController.freeze(); + vm.stopPrank(); + + _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")); + } + + /**********************************************************************************************/ + /*** PSM tests ***/ + /**********************************************************************************************/ + + 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); + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-totalAssets-not-seeded")); + + // Approve from address(this) cause it received the one wei + // Redo the seeding + usdsBase.approve(address(psmBase), 1); + psmBase.deposit(address(usdsBase), address(0), 1); + + assertEq(psmBase.totalAssets(), 1e18); + + _checkInitAndUpgradeSucceed(); + } + + 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 + + usdsBase.transfer(address(psmBase), 1); // Transfer one wei to PSM to update totalAssets + + assertEq(psmBase.totalAssets(), 1e18); + assertEq(psmBase.totalShares(), 1e18 - 1); + + _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); + usdsBase.approve(address(psmBase), 2); + psmBase.deposit(address(usdsBase), address(0), 2); + + assertEq(psmBase.totalAssets(), 1e18 + 2); + assertEq(psmBase.totalShares(), 1e18); + + _checkInitAndUpgradeSucceed(); + } + + function test_initAlmSystem_upgradeController_incorrectPsmUsdc() external { + ERC20Mock wrongUsdc = new ERC20Mock(); + + deal(address(usdsBase), address(this), 1e18); // For seeding PSM during deployment + + // Deploy a new PSM with the wrong USDC + psmBase = IPSM3(PSM3Deploy.deploy( + SPARK_EXECUTOR, address(wrongUsdc), address(usdsBase), address(susdsBase), SSR_ORACLE + )); + + // Deploy a new controller pointing to misconfigured PSM + controllerInst = ForeignControllerDeploy.deployFull( + SPARK_EXECUTOR, + address(psmBase), + USDC_BASE, + CCTP_MESSENGER_BASE + ); + + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-usdc")); + } + + 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 USDS + psmBase = IPSM3(PSM3Deploy.deploy( + SPARK_EXECUTOR, USDC_BASE, address(wrongUsds), address(susdsBase), SSR_ORACLE + )); + + // Deploy a new controller pointing to misconfigured PSM + controllerInst = ForeignControllerDeploy.deployFull( + SPARK_EXECUTOR, + address(psmBase), + USDC_BASE, + CCTP_MESSENGER_BASE + ); + + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-usds")); + } + + 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 SUSDS + psmBase = IPSM3(PSM3Deploy.deploy( + SPARK_EXECUTOR, USDC_BASE, address(usdsBase), address(wrongSUsds), SSR_ORACLE + )); + + // Deploy a new controller pointing to misconfigured PSM + controllerInst = ForeignControllerDeploy.deployFull( + SPARK_EXECUTOR, + address(psmBase), + USDC_BASE, + CCTP_MESSENGER_BASE + ); + + checkAddresses.psm = address(psmBase); // Overwrite to point to misconfigured PSM + + _checkInitAndUpgradeFail(abi.encodePacked("ForeignControllerInit/psm-incorrect-susds")); + } + + /**********************************************************************************************/ + /*** Upgrade tests ***/ + /**********************************************************************************************/ + + function test_upgradeController_oldControllerZeroAddress() external { + configAddresses.oldController = address(0); + + vm.expectRevert("ForeignControllerInit/old-controller-zero-address"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + 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(); + + // 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.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function test_upgradeController_oldControllerDoesNotHaveRoleInRateLimits() external { + configAddresses.oldController = oldController; + + // Revoke the old controller address in rate limits + vm.startPrank(Base.SPARK_EXECUTOR); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); + vm.stopPrank(); + + // 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.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + /**********************************************************************************************/ + /*** Helper functions ***/ + /**********************************************************************************************/ + + function _checkInitAndUpgradeFail(bytes memory expectedError) internal { + vm.expectRevert(expectedError); + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + vm.expectRevert(expectedError); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function _checkInitAndUpgradeSucceed() internal { + uint256 id = vm.snapshot(); + + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + vm.revertTo(id); + + configAddresses.oldController = oldController; + + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + +} + +contract ForeignControllerInitAlmSystemSuccessTests is ForeignControllerInitAndUpgradeTestBase { + + LibraryWrapper wrapper; + + 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), + address(usdcBase), + Base.CCTP_TOKEN_MESSENGER + ); + + // 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); + + 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(Base.SPARK_EXECUTOR, address(new LibraryWrapper()).code); + + wrapper = LibraryWrapper(Base.SPARK_EXECUTOR); + } + + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } + + function test_initAlmSystem() public { + assertEq(foreignController.hasRole(foreignController.FREEZER(), freezer), false); + assertEq(foreignController.hasRole(foreignController.RELAYER(), relayer), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), false); + + assertEq(foreignController.mintRecipients(mintRecipients[0].domain), bytes32(0)); + assertEq(foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), bytes32(0)); + + vm.startPrank(Base.SPARK_EXECUTOR); + wrapper.initAlmSystem( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + assertEq(foreignController.hasRole(foreignController.FREEZER(), freezer), true); + assertEq(foreignController.hasRole(foreignController.RELAYER(), relayer), true); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), true); + + assertEq( + foreignController.mintRecipients(mintRecipients[0].domain), + mintRecipients[0].mintRecipient + ); + + assertEq( + foreignController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM), + bytes32(uint256(uint160(makeAddr("ethereumAlmProxy")))) + ); + } + +} + +contract ForeignControllerUpgradeControllerSuccessTests is ForeignControllerInitAndUpgradeTestBase { + + LibraryWrapper wrapper; + + ControllerInstance public controllerInst; + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + ForeignController newController; + + function setUp() public override { + super.setUp(); + + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); + + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); + + mintRecipients.push(mintRecipients_[0]); + + 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) + }); + + configAddresses.oldController = address(foreignController); // Revoke from old controller + + // 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 + } + + 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 + ); + + assertEq(newController.hasRole(newController.FREEZER(), freezer), true); + assertEq(newController.hasRole(newController.RELAYER(), relayer), true); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(foreignController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(foreignController)), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), true); + + 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/Morpho.t.sol b/test/base-fork/Morpho.t.sol new file mode 100644 index 0000000..ddee575 --- /dev/null +++ b/test/base-fork/Morpho.t.sol @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +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 { RateLimitHelpers } from "../../src/RateLimitHelpers.sol"; + +import "./ForkTestBase.t.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 + ); + 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(); + } + + 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 +// TODO: Refactor tests here to be generic 4626, testing morpho as a subset, rename file and functions + +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_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); + + 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); + } + + 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 { + + 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); + } + + 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 { + + 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); + } + +} 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/SNstCalls.t.sol b/test/mainnet-fork/4626Calls.t.sol similarity index 54% rename from test/mainnet-fork/SNstCalls.t.sol rename to test/mainnet-fork/4626Calls.t.sol index bfa6f3c..d607f82 100644 --- a/test/mainnet-fork/SNstCalls.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 { @@ -16,6 +16,14 @@ 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(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); SUSDS_CONVERTED_SHARES = susds.convertToShares(1e18); @@ -35,31 +43,51 @@ 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); + } + + 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 MainnetControllerDepositToSUSDSTests is SUSDSTestBase { +contract MainnetControllerDepositERC4626Tests is SUSDSTestBase { - function test_depositToSUSDS() external { + function test_depositERC4626() external { vm.prank(relayer); mainnetController.mintUSDS(1e18); @@ -75,7 +103,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 +121,56 @@ 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); + } + + 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 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 +186,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 +204,79 @@ 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); } -} + 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 MainnetControllerRedeemFromSUSDSTests is SUSDSTestBase { +contract MainnetControllerRedeemERC4626Tests 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 +291,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 +308,3 @@ contract MainnetControllerRedeemFromSUSDSTests is SUSDSTestBase { } } - - diff --git a/test/mainnet-fork/Aave.t.sol b/test/mainnet-fork/Aave.t.sol new file mode 100644 index 0000000..e369d66 --- /dev/null +++ b/test/mainnet-fork/Aave.t.sol @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +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; + 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 + ); + 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(); + + startingAUSDCBalance = usdc.balanceOf(address(ausdc)); + 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 + +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_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); + + 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); + } + + 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 { + + 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); + + 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); + + assertEq(rateLimits.getCurrentRateLimit(key), 10_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 + + 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); + + // Interest accrued was withdrawn, reducing cash balance + 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); + + 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); + + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6); + + // Partial withdraw + vm.prank(relayer); + assertEq(mainnetController.withdrawAave(ATOKEN_USDC, 400_000e6), 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 + + assertEq(rateLimits.getCurrentRateLimit(key), 9_600_000e6); + + // Withdraw all + vm.prank(relayer); + 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 + 1); + assertEq(usdc.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance - 1); // Rounding + + assertEq(rateLimits.getCurrentRateLimit(key), 10_000_000e6 - fullBalance - 1); // Rounding + + // 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); + } + +} diff --git a/test/mainnet-fork/Attacks.t.sol b/test/mainnet-fork/Attacks.t.sol new file mode 100644 index 0000000..8ed2838 --- /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 "./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); + } + +} diff --git a/test/mainnet-fork/CCTPCalls.t.sol b/test/mainnet-fork/CCTPCalls.t.sol index b47576c..bf8b2e9 100644 --- a/test/mainnet-fork/CCTPCalls.t.sol +++ b/test/mainnet-fork/CCTPCalls.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later 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"; @@ -13,21 +11,20 @@ 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"; -import { ForeignControllerDeploy } from "deploy/ControllerDeploy.sol"; -import { ControllerInstance } from "deploy/ControllerInstance.sol"; +import { ForeignControllerInit } from "../../deploy/ForeignControllerInit.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 { 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 { @@ -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) + }); + + 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) }); - RateLimitData memory standardUsdcRateLimitData = RateLimitData({ + 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 deleted file mode 100644 index dd0bb85..0000000 --- a/test/mainnet-fork/DeployAndInit.t.sol +++ /dev/null @@ -1,733 +0,0 @@ -// 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"; - -import { - MainnetControllerInit, - RateLimitData, - MintRecipient -} from "../../deploy/ControllerInit.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 - ) - external - { - MainnetControllerInit.subDaoInitController( - params, - controllerInst, - rateLimitData, - mintRecipients - ); - } - - function subDaoInitFull( - MainnetControllerInit.AddressParams memory params, - ControllerInstance memory controllerInst, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) - external - { - MainnetControllerInit.subDaoInitFull( - params, - controllerInst, - rateLimitData, - mintRecipients - ); - } - -} - -contract MainnetControllerDeployInitTestBase is ForkTestBase { - - function _getDefaultParams() - internal returns ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) - { - 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 usdsMintData = 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 - }); - - rateLimitData = MainnetControllerInit.InitRateLimitData({ - usdsMintData : usdsMintData, - usdsToUsdcData : usdsToUsdcData, - usdcToCctpData : usdcToCctpData, - cctpToBaseDomainData : cctpToBaseDomainData - }); - - mintRecipients = new MintRecipient[](1); - - mintRecipients[0] = MintRecipient({ - domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, - mintRecipient : bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) - }); - } - -} - -contract MainnetControllerDeployAndInitFailureTests is MainnetControllerDeployInitTestBase { - - LibraryWrapper wrapper; - - ControllerInstance public controllerInst; - - address public mismatchAddress = makeAddr("mismatchAddress"); - - MainnetControllerInit.AddressParams addresses; - MainnetControllerInit.InitRateLimitData rateLimitData; - MintRecipient[] mintRecipients; - - function setUp() public override { - super.setUp(); - - controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER, - address(susds) - ); - - MintRecipient[] memory mintRecipients_ = new MintRecipient[](1); - - ( addresses, rateLimitData, 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); - - // Admin will be calling the library from its own address - vm.etch(SPARK_PROXY, address(new LibraryWrapper()).code); - - wrapper = LibraryWrapper(SPARK_PROXY); - } - - /**********************************************************************************************/ - /*** 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); - almProxy.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-almProxy")); - } - - 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); - rateLimits.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-rateLimits")); - } - - 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); - mainnetController.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); - vm.stopPrank(); - - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-controller")); - } - - /**********************************************************************************************/ - /*** Constructor tests ***/ - /**********************************************************************************************/ - - function test_init_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")); - } - - function test_init_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")); - } - - function test_init_incorrectCctp() external { - addresses.cctpMessenger = mismatchAddress; - _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")); - } - - function test_init_incorrectUsdc() external { - addresses.usdc = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-usdc")); - } - - function test_init_incorrectUsds() external { - addresses.usds = mismatchAddress; - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/incorrect-usds")); - } - - function test_init_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); - - vm.startPrank(freezer); - 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(); - } - - 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(); - } - - /**********************************************************************************************/ - /*** `slope` rate limit precision boundary 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")); - - 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(); - } - - /**********************************************************************************************/ - /*** Old controller role check tests ***/ - /**********************************************************************************************/ - - function test_init_oldControllerDoesNotHaveRoleInAlmProxy() external { - _deployNewControllerAfterExistingControllerInit(); - - // 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 - - _checkBothInitsFail(abi.encodePacked("MainnetControllerInit/old-controller-not-almProxy-controller")); - } - - function test_init_oldControllerDoesNotHaveRoleInRateLimits() external { - _deployNewControllerAfterExistingControllerInit(); - - // Revoke the old controller address - - vm.startPrank(SPARK_PROXY); - rateLimits.revokeRole(rateLimits.CONTROLLER(), addresses.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")); - } - - /**********************************************************************************************/ - /*** 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, - address(susds) - ); - - 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 { - vm.expectRevert(expectedError); - wrapper.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - - vm.expectRevert(expectedError); - wrapper.subDaoInitFull( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - } - - function _checkBothInitsSucceed() internal { - wrapper.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - - wrapper.subDaoInitFull( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - } -} - -contract MainnetControllerDeployAndInitSuccessTests is MainnetControllerDeployInitTestBase { - - function test_deployAllAndInitFull() external { - // Perform new deployments against existing fork environment - - ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER, - address(susds) - ); - - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - mainnetController = MainnetController(controllerInst.controller); - 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.susds()), address(susds)); - 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(); - - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitFull( - addresses, - controllerInst, - rateLimitData, - 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(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 - ); - - assertEq( - mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), - bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) - ); - - assertEq(IVaultLike(vault).wards(controllerInst.almProxy), 1); - - assertEq(usds.allowance(buffer, controllerInst.almProxy), type(uint256).max); - - // Perform Maker initialization (from PAUSE_PROXY during spell) - - vm.startPrank(PAUSE_PROXY); - MainnetControllerInit.pauseProxyInit(PSM, controllerInst.almProxy); - vm.stopPrank(); - - // Assert Maker initialization - - assertEq(IPSMLike(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, - address(susds) - ); - - // 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); - - 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 - ); - - assertEq( - mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), - bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) - ); - } - - function test_init_transferAclToNewController() public { - // Deploy and init a controller - - ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull( - SPARK_PROXY, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER, - address(susds) - ); - - ( - MainnetControllerInit.AddressParams memory addresses, - MainnetControllerInit.InitRateLimitData memory rateLimitData, - MintRecipient[] memory mintRecipients - ) = _getDefaultParams(); - - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - mintRecipients - ); - vm.stopPrank(); - - // Deploy a new controller (example of how an upgrade would work) - - address newController = MainnetControllerDeploy.deployController( - SPARK_PROXY, - controllerInst.almProxy, - controllerInst.rateLimits, - vault, - PSM, - DAI_USDS, - CCTP_MESSENGER, - address(susds) - ); - - // Overwrite storage for all previous deployments in setUp and assert deployment - - almProxy = ALMProxy(payable(controllerInst.almProxy)); - mainnetController = MainnetController(controllerInst.controller); - rateLimits = RateLimits(controllerInst.rateLimits); - - address oldController = address(controllerInst.controller); - - controllerInst.controller = newController; // Overwrite struct for param - - // All other info is the same, just need to transfer ACL - addresses.oldController = oldController; - - 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); - - vm.startPrank(SPARK_PROXY); - MainnetControllerInit.subDaoInitController( - addresses, - controllerInst, - rateLimitData, - 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); - } - - function _assertRateLimitData(bytes32 domainKey, RateLimitData memory expectedData) internal { - IRateLimits.RateLimitData memory data = rateLimits.getRateLimitData(domainKey); - - assertEq(data.maxAmount, expectedData.maxAmount); - assertEq(data.slope, expectedData.slope); - assertEq(data.lastAmount, expectedData.maxAmount); - assertEq(data.lastUpdated, block.timestamp); - - assertEq(rateLimits.getCurrentRateLimit(domainKey), expectedData.maxAmount); - } - -} diff --git a/test/mainnet-fork/Ethena.t.sol b/test/mainnet-fork/Ethena.t.sol new file mode 100644 index 0000000..b5fbedc --- /dev/null +++ b/test/mainnet-fork/Ethena.t.sol @@ -0,0 +1,895 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import "./ForkTestBase.t.sol"; + +interface IEthenaMinterLike { + function delegatedSigner(address signer, address owner) external view returns (uint8); +} + +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( + "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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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.previewRedeem(overBoundaryShares), 100e18 + 1); + assertEq(susde.previewRedeem(boundaryShares), 100e18); + + vm.prank(relayer); + vm.expectRevert("RateLimits/rate-limit-exceeded"); + mainnetController.cooldownSharesSUSDe(overBoundaryShares); + + vm.prank(relayer); + mainnetController.cooldownSharesSUSDe(boundaryShares); + } + +} + +contract MainnetControllerCooldownSharesSUSDeSuccessTests is EthenaTestBase { + + 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); + uint256 returnedAssets = mainnetController.cooldownSharesSUSDe(100e18); + + assertEq(returnedAssets, assets); + + 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); + 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); + + skip(4 hours); + + assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18 - assets1 + (1_000_000e18 - 6400)); // Rounding + + vm.prank(relayer); + 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); + } + +} + +contract MainnetControllerUnstakeSUSDeFailureTests is EthenaTestBase { + + 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 EthenaTestBase { + + 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 EthenaTestBase { + + 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 - 1); // 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 - 1); // Rounding + + assertEq(usde.balanceOf(silo), startingSiloBalance); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 5_000_000e18); + + vm.prank(relayer); + mainnetController.cooldownAssetsSUSDe(500_000e18 - 1); + + assertEq(rateLimits.getCurrentRateLimit(cooldownKey), 4_500_000e18 + 1); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + 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 - 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 - 1); + + // 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 - 1); + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 1); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 1); + + 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 - 1); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 1); + + 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 - 1); // 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 - 1); // 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 + 1); + + assertEq(susde.convertToAssets(susde.balanceOf(address(almProxy))), 0); + + 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 - 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 - 1); + + // 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 - 1); + + assertEq(rateLimits.getCurrentRateLimit(burnKey), 4_000_000e18 + 1); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 1_000_000e18 - 1); + + 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 - 1); + + assertEq(usde.allowance(address(almProxy), ETHENA_MINTER), 0); + + assertEq(usde.balanceOf(address(almProxy)), 0); + assertEq(usde.balanceOf(ETHENA_MINTER), startingMinterBalance + 1_000_000e18 - 1); + + 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..40aaf08 100644 --- a/test/mainnet-fork/ForkTestBase.t.sol +++ b/test/mainnet-fork/ForkTestBase.t.sol @@ -12,28 +12,27 @@ 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"; 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"; +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 { ALMProxy } from "../../src/ALMProxy.sol"; +import { RateLimits } from "../../src/RateLimits.sol"; +import { MainnetController } from "../../src/MainnetController.sol"; + +import { RateLimitHelpers, RateLimitData } from "../../src/RateLimitHelpers.sol"; interface IChainlogLike { function getAddress(bytes32) external view returns (address); @@ -43,6 +42,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); @@ -72,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; @@ -87,15 +93,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; @@ -142,7 +152,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); @@ -188,15 +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, - susds : Ethereum.SUSDS + admin : Ethereum.SPARK_PROXY, + vault : ilkInst.vault, + psm : Ethereum.PSM, + daiUsds : Ethereum.DAI_USDS, + cctp : Ethereum.CCTP_TOKEN_MESSENGER }); almProxy = ALMProxy(payable(controllerInst.almProxy)); @@ -207,60 +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 usdsMintData = 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 : usdsMintData, - usdsToUsdcData : standardUsdcData, - usdcToCctpData : standardUsdcData, - cctpToBaseDomainData : standardUsdcData + 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"); @@ -269,4 +291,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 + } + } diff --git a/test/mainnet-fork/InitAndUpgrade.t.sol b/test/mainnet-fork/InitAndUpgrade.t.sol new file mode 100644 index 0000000..fa66b79 --- /dev/null +++ b/test/mainnet-fork/InitAndUpgrade.t.sol @@ -0,0 +1,511 @@ +// 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"; + +import { MainnetControllerInit as Init } from "../../deploy/MainnetControllerInit.sol"; + +// Necessary to get error message assertions to work +contract LibraryWrapper { + + function initAlmSystem( + address vault, + address usds, + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients + ) + external + { + Init.initAlmSystem(vault, usds, controllerInst, configAddresses, checkAddresses, mintRecipients); + } + + function upgradeController( + ControllerInstance memory controllerInst, + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients + ) + external + { + Init.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients); + } + + function pauseProxyInitAlmSystem(address psm, address almProxy) external { + Init.pauseProxyInitAlmSystem(psm, almProxy); + } + +} + +contract MainnetControllerInitAndUpgradeTestBase is ForkTestBase { + + function _getDefaultParams() + internal returns ( + Init.ConfigAddressParams memory configAddresses, + Init.CheckAddressParams memory checkAddresses, + Init.MintRecipient[] memory mintRecipients + ) + { + configAddresses = Init.ConfigAddressParams({ + freezer : freezer, + relayer : relayer, + oldController : address(0) + }); + + 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 Init.MintRecipient[](1); + + mintRecipients[0] = Init.MintRecipient({ + domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE, + mintRecipient : bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) + }); + } + +} + +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; + + ControllerInstance public controllerInst; + + address public mismatchAddress = makeAddr("mismatchAddress"); + + address public oldController; + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + function setUp() public override { + super.setUp(); + + oldController = address(mainnetController); // Cache for later testing + + // 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 + })); + + 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]); + + 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); + + wrapper = LibraryWrapper(SPARK_PROXY); + } + + function _getBlock() internal pure override returns (uint256) { + return 21430000; // Dec 18, 2024 + } + + /**********************************************************************************************/ + /*** ACL tests ***/ + /**********************************************************************************************/ + + function test_initAlmSystem_incorrectAdminAlmProxy() external { + vm.prank(SPARK_PROXY); + almProxy.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); + + vm.expectRevert("MainnetControllerInit/incorrect-admin-almProxy"); + wrapper.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function test_initAlmSystem_incorrectAdminRateLimits() external { + vm.prank(SPARK_PROXY); + rateLimits.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); + + vm.expectRevert("MainnetControllerInit/incorrect-admin-rateLimits"); + wrapper.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function test_initAlmSystem_upgradeController_incorrectAdminController() external { + vm.prank(SPARK_PROXY); + mainnetController.revokeRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY); + + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-admin-controller")); + } + + /**********************************************************************************************/ + /*** Constructor tests ***/ + /**********************************************************************************************/ + + 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)); + + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-almProxy")); + } + + 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)); + + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-rateLimits")); + } + + function test_initAlmSystem_upgradeController_incorrectVault() external { + checkAddresses.vault = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-vault")); + } + + function test_initAlmSystem_upgradeController_incorrectPsm() external { + checkAddresses.psm = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-psm")); + } + + function test_initAlmSystem_upgradeController_incorrectDaiUsds() external { + checkAddresses.daiUsds = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-daiUsds")); + } + + function test_initAlmSystem_upgradeController_incorrectCctp() external { + checkAddresses.cctp = mismatchAddress; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/incorrect-cctp")); + } + + 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); + + vm.startPrank(freezer); + mainnetController.freeze(); + vm.stopPrank(); + + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/controller-not-active")); + } + + function test_initAlmSystem_upgradeController_oldControllerIsNewController() external { + configAddresses.oldController = controllerInst.controller; + _checkInitAndUpgradeFail(abi.encodePacked("MainnetControllerInit/old-controller-is-new-controller")); + } + + /**********************************************************************************************/ + /*** Upgrade tests ***/ + /**********************************************************************************************/ + + function test_upgradeController_oldControllerZeroAddress() external { + configAddresses.oldController = address(0); + + vm.expectRevert("MainnetControllerInit/old-controller-zero-address"); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + + function test_upgradeController_oldControllerDoesNotHaveRoleInAlmProxy() external { + configAddresses.oldController = oldController; + + // Revoke the old controller address in ALM proxy + vm.startPrank(SPARK_PROXY); + almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController); + vm.stopPrank(); + + // 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_upgradeController_oldControllerDoesNotHaveRoleInRateLimits() external { + configAddresses.oldController = oldController; + + // Revoke the old controller address in rate limits + vm.startPrank(SPARK_PROXY); + rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController); + vm.stopPrank(); + + // 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 _checkInitAndUpgradeFail(bytes memory expectedError) internal { + vm.expectRevert(expectedError); + wrapper.initAlmSystem( + vault, + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + vm.expectRevert(expectedError); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + } + +} + +contract MainnetControllerInitAlmSystemSuccessTests is MainnetControllerInitAndUpgradeTestBase { + + LibraryWrapper wrapper; + + ControllerInstance public controllerInst; + + address public mismatchAddress = makeAddr("mismatchAddress"); + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + 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); + + 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); + wrapper.initAlmSystem( + address(vault), + address(usds), + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + assertEq(mainnetController.hasRole(mainnetController.FREEZER(), freezer), true); + assertEq(mainnetController.hasRole(mainnetController.RELAYER(), relayer), true); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), true); + + assertEq( + mainnetController.mintRecipients(mintRecipients[0].domain), + mintRecipients[0].mintRecipient + ); + + assertEq( + mainnetController.mintRecipients(CCTPForwarder.DOMAIN_ID_CIRCLE_BASE), + bytes32(uint256(uint160(makeAddr("baseAlmProxy")))) + ); + + assertEq(IVaultLike(vault).wards(controllerInst.almProxy), 1); + 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); + + assertEq(IPSMLike(Ethereum.PSM).bud(controllerInst.almProxy), 0); + + vm.startPrank(PAUSE_PROXY); + wrapper.pauseProxyInitAlmSystem(Ethereum.PSM, controllerInst.almProxy); + vm.stopPrank(); + + assertEq(IPSMLike(Ethereum.PSM).bud(controllerInst.almProxy), 1); + } + +} + +contract MainnetControllerUpgradeControllerSuccessTests is MainnetControllerInitAndUpgradeTestBase { + + LibraryWrapper wrapper; + + ControllerInstance public controllerInst; + + address public mismatchAddress = makeAddr("mismatchAddress"); + + Init.ConfigAddressParams configAddresses; + Init.CheckAddressParams checkAddresses; + Init.MintRecipient[] mintRecipients; + + MainnetController newController; + + function setUp() public override { + super.setUp(); + + Init.MintRecipient[] memory mintRecipients_ = new Init.MintRecipient[](1); + + ( configAddresses, checkAddresses, mintRecipients_ ) = _getDefaultParams(); + + mintRecipients.push(mintRecipients_[0]); + + 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) + }); + + configAddresses.oldController = address(mainnetController); // Revoke from old controller + + // 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_upgradeController() public { + assertEq(newController.hasRole(newController.FREEZER(), freezer), false); + assertEq(newController.hasRole(newController.RELAYER(), relayer), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), 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_BASE), bytes32(0)); + + vm.startPrank(SPARK_PROXY); + wrapper.upgradeController( + controllerInst, + configAddresses, + checkAddresses, + mintRecipients + ); + + assertEq(newController.hasRole(newController.FREEZER(), freezer), true); + assertEq(newController.hasRole(newController.RELAYER(), relayer), true); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(mainnetController)), false); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(mainnetController)), false); + + assertEq(almProxy.hasRole(almProxy.CONTROLLER(), address(newController)), true); + assertEq(rateLimits.hasRole(rateLimits.CONTROLLER(), address(newController)), true); + + 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/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..90ee41b 100644 --- a/test/unit/controllers/Admin.t.sol +++ b/test/unit/controllers/Admin.t.sol @@ -1,15 +1,14 @@ // 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 { 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 { @@ -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 a82cd44..33f96f1 100644 --- a/test/unit/controllers/Constructor.t.sol +++ b/test/unit/controllers/Constructor.t.sol @@ -1,23 +1,21 @@ // 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 { 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 { 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 9ba8f9b..c1206c2 100644 --- a/test/unit/controllers/Freeze.t.sol +++ b/test/unit/controllers/Freeze.t.sol @@ -1,16 +1,15 @@ // 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 { 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); @@ -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 709f359..213bec8 100644 --- a/test/unit/deployments/Deploy.t.sol +++ b/test/unit/deployments/Deploy.t.sol @@ -1,14 +1,13 @@ // 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 { 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 { @@ -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; - } - -} 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 {