├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── Makefile
├── README.md
├── audits
├── v100-cantina-audit.pdf
├── v100-cantina-beta-audit.pdf
├── v100-chainsecurity-audit.pdf
├── v110-cantina-audit.pdf
├── v110-chainsecurity-audit.pdf
├── v130-cantina-audit.pdf
├── v130-chainsecurity-audit.pdf
├── v140-cantina-audit.pdf
└── v140-chainsecurity-audit.pdf
├── deploy
├── ControllerDeploy.sol
├── ControllerInstance.sol
├── ForeignControllerInit.sol
└── MainnetControllerInit.sol
├── foundry.toml
├── script
├── Deploy.s.sol
├── Upgrade.s.sol
├── input
│ └── 1
│ │ ├── arbitrum_one-staging.json
│ │ ├── base-production.json
│ │ ├── base-staging.json
│ │ ├── mainnet-production.json
│ │ ├── mainnet-staging.json
│ │ └── unichain-production.json
├── output
│ └── 1
│ │ ├── .gitkeep
│ │ ├── arbitrum_one-staging-release-20250226.json
│ │ ├── arbitrum_one-staging-release-20250402.json
│ │ ├── arbitrum_one-staging-release-20250409.json
│ │ ├── base-production-release-20241023.json
│ │ ├── base-production-release-20241229.json
│ │ ├── base-staging-deps-release-20241022.json
│ │ ├── base-staging-deps-release-20241210.json
│ │ ├── base-staging-deps-release-20241227.json
│ │ ├── base-staging-release-20241022.json
│ │ ├── base-staging-release-20241210.json
│ │ ├── base-staging-release-20241227.json
│ │ ├── base-staging-release-20250402.json
│ │ ├── base-staging-release-20250409.json
│ │ ├── mainnet-production-release-20241023.json
│ │ ├── mainnet-production-release-20241229.json
│ │ ├── mainnet-staging-deps-release-20241022.json
│ │ ├── mainnet-staging-deps-release-20241210.json
│ │ ├── mainnet-staging-deps-release-20241227.json
│ │ ├── mainnet-staging-release-20241022.json
│ │ ├── mainnet-staging-release-20241210.json
│ │ ├── mainnet-staging-release-20241227.json
│ │ ├── mainnet-staging-release-20250402.json
│ │ └── mainnet-staging-release-20250409.json
└── staging
│ ├── FullStagingDeploy.s.sol
│ ├── mocks
│ ├── MockJug.sol
│ ├── MockUsdsJoin.sol
│ ├── MockVat.sol
│ └── PSMWrapper.sol
│ └── test
│ └── StagingDeployment.t.sol
├── src
├── ALMProxy.sol
├── ForeignController.sol
├── MainnetController.sol
├── RateLimitHelpers.sol
├── RateLimits.sol
├── interfaces
│ ├── CCTPInterfaces.sol
│ ├── IALMProxy.sol
│ ├── ILayerZero.sol
│ └── IRateLimits.sol
└── libraries
│ ├── CCTPLib.sol
│ ├── CurveLib.sol
│ └── PSMLib.sol
└── test
├── base-fork
├── Aave.t.sol
├── Deploy.t.sol
├── ForkTestBase.t.sol
├── InitAndUpgrade.t.sol
├── Morpho.t.sol
├── MorphoAllocations.t.sol
└── PsmCalls.t.sol
├── mainnet-fork
├── 4626Calls.t.sol
├── Aave.t.sol
├── Approve.t.sol
├── Attacks.t.sol
├── Buidl.t.sol
├── CCTPCalls.t.sol
├── Centrifuge.t.sol
├── Curve.t.sol
├── DaiUsds.t.sol
├── Deploy.t.sol
├── Ethena.t.sol
├── ForkTestBase.t.sol
├── InitAndUpgrade.t.sol
├── LayerZero.t.sol
├── Maple.t.sol
├── PsmCalls.t.sol
├── Superstate.t.sol
└── VaultCalls.t.sol
└── unit
├── UnitTestBase.t.sol
├── controllers
├── Admin.t.sol
├── Constructor.t.sol
└── Freezer.t.sol
├── deployments
└── Deploy.t.sol
├── mocks
├── MockDaiUsds.sol
├── MockPSM.sol
├── MockPSM3.sol
├── MockTarget.sol
└── MockVault.sol
├── proxy
├── Constructor.t.sol
├── DoCall.t.sol
└── ReceiveEth.t.sol
└── rate-limits
├── RateLimitHelpers.t.sol
└── RateLimits.t.sol
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches:
8 | - master
9 |
10 | env:
11 | FOUNDRY_PROFILE: ci
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Install Foundry
20 | uses: foundry-rs/foundry-toolchain@v1
21 |
22 | - name: Build contracts
23 | run: |
24 | forge --version
25 | forge build --sizes
26 |
27 | test:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@v3
31 |
32 | - name: Install Foundry
33 | uses: foundry-rs/foundry-toolchain@v1
34 |
35 | - name: Run tests
36 | env:
37 | MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
38 | OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
39 | ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
40 | ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
41 | GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
42 | BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
43 | run: FOUNDRY_PROFILE=ci forge test
44 |
45 | coverage:
46 | runs-on: ubuntu-latest
47 | steps:
48 | - uses: actions/checkout@v3
49 |
50 | - name: Install Foundry
51 | uses: foundry-rs/foundry-toolchain@v1
52 |
53 | - name: Run coverage
54 | env:
55 | MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
56 | OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
57 | ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
58 | ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
59 | GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
60 | BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
61 | run: forge coverage --report summary --report lcov
62 |
63 | # To ignore coverage for certain directories modify the paths in this step as needed. The
64 | # below default ignores coverage results for the test and script directories. Alternatively,
65 | # to include coverage in all directories, comment out this step. Note that because this
66 | # filtering applies to the lcov file, the summary table generated in the previous step will
67 | # still include all files and directories.
68 | # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
69 | # defaults to removing branch info.
70 | - name: Filter directories
71 | run: |
72 | sudo apt update && sudo apt install -y lcov
73 | lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1
74 |
75 | # This step posts a detailed coverage report as a comment and deletes previous comments on
76 | # each push. The below step is used to fail coverage if the specified coverage threshold is
77 | # not met. The below step can post a comment (when it's `github-token` is specified) but it's
78 | # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
79 | # is why we use both in this way.
80 | - name: Post coverage report
81 | if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
82 | uses: romeovs/lcov-reporter-action@v0.3.1
83 | with:
84 | delete-old-comments: true
85 | lcov-file: ./lcov.info
86 | github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.
87 |
88 | - name: Verify minimum coverage
89 | uses: zgosalvez/github-actions-report-lcov@v2
90 | with:
91 | coverage-files: ./lcov.info
92 | minimum-coverage: 90 # Set coverage threshold.
93 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cache/
2 | out/
3 | /broadcast
4 | docs/
5 | .env
6 | lcov.info
7 | script/output/*/*.json
8 | !script/output/*/*-release-*.json
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | [submodule "lib/dss-allocator"]
5 | path = lib/dss-allocator
6 | url = https://github.com/makerdao/dss-allocator
7 | [submodule "lib/openzeppelin-contracts"]
8 | path = lib/openzeppelin-contracts
9 | url = https://github.com/openzeppelin/openzeppelin-contracts
10 | [submodule "lib/spark-psm"]
11 | path = lib/spark-psm
12 | url = https://github.com/marsfoundation/spark-psm
13 | [submodule "lib/xchain-helpers"]
14 | path = lib/xchain-helpers
15 | url = https://github.com/marsfoundation/xchain-helpers
16 | [submodule "lib/usds"]
17 | path = lib/usds
18 | url = https://github.com/makerdao/usds
19 | [submodule "lib/sdai"]
20 | path = lib/sdai
21 | url = https://github.com/makerdao/sdai
22 | [submodule "lib/erc20-helpers"]
23 | path = lib/erc20-helpers
24 | url = https://github.com/marsfoundation/erc20-helpers
25 | [submodule "lib/dss-test"]
26 | path = lib/dss-test
27 | url = https://github.com/makerdao/dss-test
28 | [submodule "lib/metamorpho"]
29 | path = lib/metamorpho
30 | url = https://github.com/morpho-org/metamorpho
31 | [submodule "lib/aave-v3-origin"]
32 | path = lib/aave-v3-origin
33 | url = https://github.com/aave-dao/aave-v3-origin
34 | [submodule "lib/spark-address-registry"]
35 | path = lib/spark-address-registry
36 | url = https://github.com/sparkdotfi/spark-address-registry
37 | [submodule "lib/devtools"]
38 | path = lib/devtools
39 | url = https://github.com/LayerZero-Labs/devtools
40 | [submodule "lib/layerzero-v2"]
41 | path = lib/layerzero-v2
42 | url = https://github.com/LayerZero-Labs/layerzero-v2
43 | [submodule "lib/solidity-bytes-utils"]
44 | path = lib/solidity-bytes-utils
45 | url = https://github.com/GNSPS/solidity-bytes-utils
46 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Staging Full Deployment with Dependencies
2 | deploy-staging-full :; forge script script/staging/FullStagingDeploy.s.sol:FullStagingDeploy --sender ${ETH_FROM} --broadcast --verify --multi
3 |
4 | # Staging Deployments
5 | deploy-mainnet-staging-controller :; ENV=staging forge script script/Deploy.s.sol:DeployMainnetController --sender ${ETH_FROM} --broadcast --verify
6 |
7 | deploy-base-staging-controller :; CHAIN=base ENV=staging forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify
8 |
9 | # Production Deployments
10 | deploy-mainnet-production-full :; ENV=production forge script script/Deploy.s.sol:DeployMainnetFull --sender ${ETH_FROM} --broadcast --verify
11 | deploy-mainnet-production-controller :; ENV=production forge script script/Deploy.s.sol:DeployMainnetController --sender ${ETH_FROM} --broadcast --verify
12 |
13 | deploy-base-production-full :; CHAIN=base ENV=production forge script script/Deploy.s.sol:DeployForeignFull --sender ${ETH_FROM} --broadcast --verify
14 | deploy-base-production-controller :; CHAIN=base ENV=production forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify
15 |
16 | deploy-unichain-production-full :; CHAIN=unichain ENV=production forge script script/Deploy.s.sol:DeployForeignFull --sender ${ETH_FROM} --broadcast --verify
17 | deploy-unichain-production-controller :; CHAIN=unichain ENV=production forge script script/Deploy.s.sol:DeployForeignController --sender ${ETH_FROM} --broadcast --verify
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spark ALM Controller
2 |
3 | 
4 | [![Foundry][foundry-badge]][foundry]
5 | [](https://github.com/marsfoundation/spark-alm-controller/blob/master/LICENSE)
6 |
7 | [foundry]: https://getfoundry.sh/
8 | [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg
9 |
10 | ## Overview
11 |
12 | This repo contains the onchain components of the Spark Liquidity Layer. The following contracts are contained in this repository:
13 |
14 | - `ALMProxy`: The proxy contract that holds custody of all funds. This contract routes calls to external contracts according to logic within a specified `controller` contract. This pattern was used to allow for future iterations in logic, as a new controller can be onboarded and can route calls through the proxy with new logic. This contract is stateless except for the ACL logic contained within the inherited OpenZeppelin `AccessControl` contract.
15 | - `ForeignController`: This controller contract is intended to be used on "foreign" domains. The term "foreign" is used to describe a domain that is not the Ethereum mainnet.
16 | - `MainnetController`: This controller contract is intended to be used on the Ethereum mainnet.
17 | - `RateLimits`: This contract is used to enforce and update rate limits on logic in the `ForeignController` and `MainnetController` contracts. This contract is stateful and is used to store the rate limit data.
18 |
19 | ## Architecture
20 |
21 | The general structure of calls is shown in the diagram below. The `controller` contract is the entry point for all calls. The `controller` contract checks the rate limits if necessary and executes the relevant logic. The `controller` can perform multiple calls to the `ALMProxy` contract atomically with specified calldata.
22 |
23 |
24 |
25 |
26 |
27 | The diagram below provides and example of calling to mint USDS using the Sky allocation system. Note that the funds are always held custody in the `ALMProxy` as a result of the calls made.
28 |
29 |
30 |
31 |
32 |
33 | ## Permissions
34 |
35 | All contracts in this repo inherit and implement the AccessControl contract from OpenZeppelin to manage permissions. The following roles are defined:
36 | - `DEFAULT_ADMIN_ROLE`: The admin role is the role that can grant and revoke roles. Also used for general admin functions in all contracts.
37 | - `RELAYER`: Used for the ALM Planner offchain system. This address can call functions on `controller` contracts to perform actions on behalf of the `ALMProxy` contract.
38 | - `FREEZER`: Allows an address with this role to remove a `RELAYER` that has been compromised. The intention of this is to have a backup `RELAYER` that the system can fall back to when the main one is removed.
39 | - `CONTROLLER`: Used for the `ALMProxy` contract. Only contracts with this role can call the `call` functions on the `ALMProxy` contract. Also used in the RateLimits contract, only this role can update rate limits.
40 |
41 | ## Controller Functionality
42 | The `MainnetController` contains all logic necessary to interact with the Sky allocation system to mint and burn USDS, swap USDS to USDC in the PSM, as well as interact with mainnet external protocols and CCTP for bridging USDC.
43 | The `ForeignController` contains all logic necessary to deposit, withdraw, and swap assets in L2 PSMs as well as interact with external protocols on L2s and CCTP for bridging USDC.
44 |
45 | ## Rate Limits
46 |
47 | The `RateLimits` contract is used to enforce rate limits on the `controller` contracts. The rate limits are defined using `keccak256` hashes to identify which function to apply the rate limit to. This was done to allow flexibility in future function signatures for the same desired high-level functionality. The rate limits are stored in a mapping with the `keccak256` hash as the key and a struct containing the rate limit data:
48 | - `maxAmount`: Maximum allowed amount at any time.
49 | - `slope`: The slope of the rate limit, used to calculate the new limit based on time passed. [tokens / second]
50 | - `lastAmount`: The amount left available at the last update.
51 | - `lastUpdated`: The timestamp when the rate limit was last updated.
52 |
53 | The rate limit is calculated as follows:
54 |
55 |
56 |
57 | `currentRateLimit = min(slope * (block.timestamp - lastUpdated) + lastAmount, maxAmount)`
58 |
59 |
60 |
61 | 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`).
62 |
63 | ## Trust Assumptions and Attack Mitigation
64 | Below are all stated trust assumptions for using this contract in production:
65 | - The `DEFAULT_ADMIN_ROLE` is fully trusted, to be run by governance.
66 | - 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.**
67 | - The logic in the smart contracts must prevent the movement of value anywhere outside of the ALM system of contracts. The exception for this is in asynchronous style integrations such as BUIDL, where `transferAsset` can be used to send funds to a whitelisted address. LP tokens are then asynchronously minted into the ALMProxy in a separate transaction.
68 | - Any action must be limited to "reasonable" slippage/losses/opportunity cost by rate limits.
69 | - The `FREEZER` must be able to stop the compromised `RELAYER` from performing more harmful actions within the max rate limits by using the `removeRelayer` function.
70 | - A compromised `RELAYER` can perform DOS attacks. These attacks along with their respective recovery procedures are outlined in the `Attacks.t.sol` test files.
71 | - Ethena USDe Mint/Burn is trusted to not honor requests with over 50bps slippage from a delegated signer.
72 |
73 | ## Operational Requirements
74 | - 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.
75 | - All ERC-20 tokens are to be non-rebasing with sufficiently high decimal precision.
76 | - 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.
77 | - Rate limits must take into account:
78 | - Risk tolerance for a given protocol
79 | - Griefing attacks (e.g., repetitive transactions with high slippage by malicious relayer).
80 |
81 | ## Testing
82 |
83 | To run all tests, run the following command:
84 |
85 | ```bash
86 | forge test
87 | ```
88 |
89 | ## Deployments
90 | All commands to deploy:
91 | - Either the full system or just the controller
92 | - To mainnet or base
93 | - For staging or production
94 |
95 | Can be found in the Makefile, with the nomenclature `make deploy---`.
96 |
97 | Deploy a full ALM system to base production: `make deploy-base-production-full`
98 | Deploy a controller to mainnet production: `make deploy-mainnet-production-controller`
99 |
100 | To deploy a full staging environment from scratch, with a new allocation system and all necessary dependencies, run `make deploy-staging-full`.
101 |
102 | ## Upgrade Simulations
103 |
104 | To perform upgrades against forks of mainnet and base for testing/simulation purposes, use the following instructions.
105 |
106 | 1. Set up two anvil nodes forked against mainnet and base.
107 | ```
108 | anvil --fork-url $MAINNET_RPC_URL
109 | ```
110 | ```
111 | anvil --fork-url $BASE_RPC_URL -p 8546
112 | ```
113 | ```
114 | anvil --fork-url $ARBITRUM_ONE_RPC_URL -p 8547
115 | ```
116 |
117 | 2. Point to local RPCs.
118 |
119 | ```
120 | export MAINNET_RPC_URL=http://127.0.0.1:8545
121 | export BASE_RPC_URL=http://127.0.0.1:8546
122 | export ARBITRUM_ONE_RPC_URL=http://127.0.0.1:8547
123 | ```
124 |
125 | 3. Upgrade mainnet contracts impersonating as the `SPARK_PROXY`.
126 |
127 | ```
128 | export SPARK_PROXY=0x3300f198988e4C9C63F75dF86De36421f06af8c4
129 |
130 | cast rpc --rpc-url="$MAINNET_RPC_URL" anvil_setBalance $SPARK_PROXY `cast to-wei 1000 | cast to-hex`
131 | cast rpc --rpc-url="$MAINNET_RPC_URL" anvil_impersonateAccount $SPARK_PROXY
132 |
133 | ENV=production \
134 | OLD_CONTROLLER=0xb960F71ca3f1f57799F6e14501607f64f9B36F11 \
135 | NEW_CONTROLLER=0x5cf73FDb7057E436A6eEaDFAd27E45E7ab6E431e \
136 | forge script script/Upgrade.s.sol:UpgradeMainnetController --broadcast --unlocked --sender $SPARK_PROXY
137 | ```
138 |
139 | 4. Upgrade base contracts impersonating as the `SPARK_EXEUCTOR`.
140 |
141 | ```
142 | export SPARK_EXECUTOR=0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579
143 |
144 | cast rpc --rpc-url="$BASE_RPC_URL" anvil_setBalance $SPARK_EXECUTOR `cast to-wei 1000 | cast to-hex`
145 | cast rpc --rpc-url="$BASE_RPC_URL" anvil_impersonateAccount $SPARK_EXECUTOR
146 |
147 | CHAIN=base \
148 | ENV=production \
149 | OLD_CONTROLLER=0xc07f705D0C0e9F8C79C5fbb748aC1246BBCC37Ba \
150 | NEW_CONTROLLER=0x5F032555353f3A1D16aA6A4ADE0B35b369da0440 \
151 | forge script script/Upgrade.s.sol:UpgradeForeignController --broadcast --unlocked --sender $SPARK_EXECUTOR
152 | ```
153 |
--------------------------------------------------------------------------------
/audits/v100-cantina-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v100-cantina-audit.pdf
--------------------------------------------------------------------------------
/audits/v100-cantina-beta-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v100-cantina-beta-audit.pdf
--------------------------------------------------------------------------------
/audits/v100-chainsecurity-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v100-chainsecurity-audit.pdf
--------------------------------------------------------------------------------
/audits/v110-cantina-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v110-cantina-audit.pdf
--------------------------------------------------------------------------------
/audits/v110-chainsecurity-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v110-chainsecurity-audit.pdf
--------------------------------------------------------------------------------
/audits/v130-cantina-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v130-cantina-audit.pdf
--------------------------------------------------------------------------------
/audits/v130-chainsecurity-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v130-chainsecurity-audit.pdf
--------------------------------------------------------------------------------
/audits/v140-cantina-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v140-cantina-audit.pdf
--------------------------------------------------------------------------------
/audits/v140-chainsecurity-audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/audits/v140-chainsecurity-audit.pdf
--------------------------------------------------------------------------------
/deploy/ControllerDeploy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { ALMProxy } from "../src/ALMProxy.sol";
5 | import { ForeignController } from "../src/ForeignController.sol";
6 | import { MainnetController } from "../src/MainnetController.sol";
7 | import { RateLimits } from "../src/RateLimits.sol";
8 |
9 | import { ControllerInstance } from "./ControllerInstance.sol";
10 |
11 | library ForeignControllerDeploy {
12 |
13 | function deployController(
14 | address admin,
15 | address almProxy,
16 | address rateLimits,
17 | address psm,
18 | address usdc,
19 | address cctp
20 | )
21 | internal returns (address controller)
22 | {
23 | controller = address(new ForeignController({
24 | admin_ : admin,
25 | proxy_ : almProxy,
26 | rateLimits_ : rateLimits,
27 | psm_ : psm,
28 | usdc_ : usdc,
29 | cctp_ : cctp
30 | }));
31 | }
32 |
33 | function deployFull(
34 | address admin,
35 | address psm,
36 | address usdc,
37 | address cctp
38 | )
39 | internal returns (ControllerInstance memory instance)
40 | {
41 | instance.almProxy = address(new ALMProxy(admin));
42 | instance.rateLimits = address(new RateLimits(admin));
43 |
44 | instance.controller = address(new ForeignController({
45 | admin_ : admin,
46 | proxy_ : instance.almProxy,
47 | rateLimits_ : instance.rateLimits,
48 | psm_ : psm,
49 | usdc_ : usdc,
50 | cctp_ : cctp
51 | }));
52 | }
53 |
54 | }
55 |
56 | library MainnetControllerDeploy {
57 |
58 | function deployController(
59 | address admin,
60 | address almProxy,
61 | address rateLimits,
62 | address vault,
63 | address psm,
64 | address daiUsds,
65 | address cctp
66 | )
67 | internal returns (address controller)
68 | {
69 | controller = address(new MainnetController({
70 | admin_ : admin,
71 | proxy_ : almProxy,
72 | rateLimits_ : rateLimits,
73 | vault_ : vault,
74 | psm_ : psm,
75 | daiUsds_ : daiUsds,
76 | cctp_ : cctp
77 | }));
78 | }
79 |
80 | function deployFull(
81 | address admin,
82 | address vault,
83 | address psm,
84 | address daiUsds,
85 | address cctp
86 | )
87 | internal returns (ControllerInstance memory instance)
88 | {
89 | instance.almProxy = address(new ALMProxy(admin));
90 | instance.rateLimits = address(new RateLimits(admin));
91 |
92 | instance.controller = address(new MainnetController({
93 | admin_ : admin,
94 | proxy_ : instance.almProxy,
95 | rateLimits_ : instance.rateLimits,
96 | vault_ : vault,
97 | psm_ : psm,
98 | daiUsds_ : daiUsds,
99 | cctp_ : cctp
100 | }));
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/deploy/ControllerInstance.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | struct ControllerInstance {
5 | address almProxy;
6 | address controller;
7 | address rateLimits;
8 | }
9 |
--------------------------------------------------------------------------------
/deploy/ForeignControllerInit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { ForeignController } from "../src/ForeignController.sol";
5 |
6 | import { IALMProxy } from "../src/interfaces/IALMProxy.sol";
7 | import { IRateLimits } from "../src/interfaces/IRateLimits.sol";
8 |
9 | import { ControllerInstance } from "./ControllerInstance.sol";
10 |
11 | interface IPSM3Like {
12 | function susds() external view returns (address);
13 | function totalAssets() external view returns (uint256);
14 | function totalShares() external view returns (uint256);
15 | function usdc() external view returns (address);
16 | function usds() external view returns (address);
17 | }
18 |
19 | library ForeignControllerInit {
20 |
21 | /**********************************************************************************************/
22 | /*** Structs and constants ***/
23 | /**********************************************************************************************/
24 |
25 | struct CheckAddressParams {
26 | address admin;
27 | address psm;
28 | address cctp;
29 | address usdc;
30 | address susds;
31 | address usds;
32 | }
33 |
34 | struct ConfigAddressParams {
35 | address freezer;
36 | address[] relayers;
37 | address oldController;
38 | }
39 |
40 | struct MintRecipient {
41 | uint32 domain;
42 | bytes32 mintRecipient;
43 | }
44 |
45 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
46 |
47 | /**********************************************************************************************/
48 | /*** Internal library functions ***/
49 | /**********************************************************************************************/
50 |
51 | function initAlmSystem(
52 | ControllerInstance memory controllerInst,
53 | ConfigAddressParams memory configAddresses,
54 | CheckAddressParams memory checkAddresses,
55 | MintRecipient[] memory mintRecipients
56 | )
57 | internal
58 | {
59 | // Step 1: Do sanity checks outside of the controller
60 |
61 | require(IALMProxy(controllerInst.almProxy).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-almProxy");
62 | require(IRateLimits(controllerInst.rateLimits).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-rateLimits");
63 |
64 | // Step 2: Initialize the controller
65 |
66 | _initController(controllerInst, configAddresses, checkAddresses, mintRecipients);
67 | }
68 |
69 | function upgradeController(
70 | ControllerInstance memory controllerInst,
71 | ConfigAddressParams memory configAddresses,
72 | CheckAddressParams memory checkAddresses,
73 | MintRecipient[] memory mintRecipients
74 | )
75 | internal
76 | {
77 | _initController(controllerInst, configAddresses, checkAddresses, mintRecipients);
78 |
79 | IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
80 | IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
81 |
82 | require(configAddresses.oldController != address(0), "ForeignControllerInit/old-controller-zero-address");
83 |
84 | require(almProxy.hasRole(almProxy.CONTROLLER(), configAddresses.oldController), "ForeignControllerInit/old-controller-not-almProxy-controller");
85 | require(rateLimits.hasRole(rateLimits.CONTROLLER(), configAddresses.oldController), "ForeignControllerInit/old-controller-not-rateLimits-controller");
86 |
87 | almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController);
88 | rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController);
89 | }
90 |
91 | /**********************************************************************************************/
92 | /*** Private helper functions ***/
93 | /**********************************************************************************************/
94 |
95 | function _initController(
96 | ControllerInstance memory controllerInst,
97 | ConfigAddressParams memory configAddresses,
98 | CheckAddressParams memory checkAddresses,
99 | MintRecipient[] memory mintRecipients
100 | )
101 | private
102 | {
103 | // Step 1: Perform controller sanity checks
104 |
105 | ForeignController newController = ForeignController(controllerInst.controller);
106 |
107 | require(newController.hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "ForeignControllerInit/incorrect-admin-controller");
108 |
109 | require(address(newController.proxy()) == controllerInst.almProxy, "ForeignControllerInit/incorrect-almProxy");
110 | require(address(newController.rateLimits()) == controllerInst.rateLimits, "ForeignControllerInit/incorrect-rateLimits");
111 |
112 | require(address(newController.psm()) == checkAddresses.psm, "ForeignControllerInit/incorrect-psm");
113 | require(address(newController.usdc()) == checkAddresses.usdc, "ForeignControllerInit/incorrect-usdc");
114 | require(address(newController.cctp()) == checkAddresses.cctp, "ForeignControllerInit/incorrect-cctp");
115 |
116 | require(configAddresses.oldController != address(newController), "ForeignControllerInit/old-controller-is-new-controller");
117 |
118 | // Step 2: Perform PSM sanity checks
119 |
120 | IPSM3Like psm = IPSM3Like(checkAddresses.psm);
121 |
122 | require(psm.totalAssets() >= 1e18, "ForeignControllerInit/psm-totalAssets-not-seeded");
123 | require(psm.totalShares() >= 1e18, "ForeignControllerInit/psm-totalShares-not-seeded");
124 |
125 | require(psm.usdc() == checkAddresses.usdc, "ForeignControllerInit/psm-incorrect-usdc");
126 | require(psm.usds() == checkAddresses.usds, "ForeignControllerInit/psm-incorrect-usds");
127 | require(psm.susds() == checkAddresses.susds, "ForeignControllerInit/psm-incorrect-susds");
128 |
129 | // Step 3: Configure ACL permissions controller, almProxy, and rateLimits
130 |
131 | IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
132 | IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
133 |
134 | almProxy.grantRole(almProxy.CONTROLLER(), address(newController));
135 | newController.grantRole(newController.FREEZER(), configAddresses.freezer);
136 | rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController));
137 |
138 | for (uint256 i; i < configAddresses.relayers.length; ++i) {
139 | newController.grantRole(newController.RELAYER(), configAddresses.relayers[i]);
140 | }
141 |
142 | // Step 4: Configure the mint recipients on other domains
143 |
144 | for (uint256 i; i < mintRecipients.length; ++i) {
145 | newController.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient);
146 | }
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/deploy/MainnetControllerInit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { MainnetController } from "../src/MainnetController.sol";
5 |
6 | import { IALMProxy } from "../src/interfaces/IALMProxy.sol";
7 | import { IRateLimits } from "../src/interfaces/IRateLimits.sol";
8 |
9 | import { ControllerInstance } from "./ControllerInstance.sol";
10 |
11 | interface IBufferLike {
12 | function approve(address, address, uint256) external;
13 | }
14 |
15 | interface IPSMLike {
16 | function kiss(address) external;
17 | }
18 |
19 | interface IVaultLike {
20 | function buffer() external view returns (address);
21 | function rely(address) external;
22 | }
23 |
24 | library MainnetControllerInit {
25 |
26 | /**********************************************************************************************/
27 | /*** Structs and constants ***/
28 | /**********************************************************************************************/
29 |
30 | struct CheckAddressParams {
31 | address admin;
32 | address proxy;
33 | address rateLimits;
34 | address vault;
35 | address psm;
36 | address daiUsds;
37 | address cctp;
38 | }
39 |
40 | struct ConfigAddressParams {
41 | address freezer;
42 | address[] relayers;
43 | address oldController;
44 | }
45 |
46 | struct MintRecipient {
47 | uint32 domain;
48 | bytes32 mintRecipient;
49 | }
50 |
51 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
52 |
53 | /**********************************************************************************************/
54 | /*** Internal library functions ***/
55 | /**********************************************************************************************/
56 |
57 | function initAlmSystem(
58 | address vault,
59 | address usds,
60 | ControllerInstance memory controllerInst,
61 | ConfigAddressParams memory configAddresses,
62 | CheckAddressParams memory checkAddresses,
63 | MintRecipient[] memory mintRecipients
64 | )
65 | internal
66 | {
67 | // Step 1: Do sanity checks outside of the controller
68 |
69 | require(IALMProxy(controllerInst.almProxy).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-almProxy");
70 | require(IRateLimits(controllerInst.rateLimits).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-rateLimits");
71 |
72 | // Step 2: Initialize the controller
73 |
74 | _initController(controllerInst, configAddresses, checkAddresses, mintRecipients);
75 |
76 | // Step 3: Configure almProxy within the allocation system
77 |
78 | require(vault == checkAddresses.vault, "MainnetControllerInit/incorrect-vault");
79 |
80 | IVaultLike(vault).rely(controllerInst.almProxy);
81 | IBufferLike(IVaultLike(vault).buffer()).approve(usds, controllerInst.almProxy, type(uint256).max);
82 | }
83 |
84 | function upgradeController(
85 | ControllerInstance memory controllerInst,
86 | ConfigAddressParams memory configAddresses,
87 | CheckAddressParams memory checkAddresses,
88 | MintRecipient[] memory mintRecipients
89 | )
90 | internal
91 | {
92 | _initController(controllerInst, configAddresses, checkAddresses, mintRecipients);
93 |
94 | IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
95 | IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
96 |
97 | require(configAddresses.oldController != address(0), "MainnetControllerInit/old-controller-zero-address");
98 |
99 | require(almProxy.hasRole(almProxy.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-almProxy-controller");
100 | require(rateLimits.hasRole(rateLimits.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-rateLimits-controller");
101 |
102 | almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController);
103 | rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController);
104 | }
105 |
106 | function pauseProxyInitAlmSystem(address psm, address almProxy) internal {
107 | IPSMLike(psm).kiss(almProxy); // To allow using no fee functionality
108 | }
109 |
110 | /**********************************************************************************************/
111 | /*** Private helper functions ***/
112 | /**********************************************************************************************/
113 |
114 | function _initController(
115 | ControllerInstance memory controllerInst,
116 | ConfigAddressParams memory configAddresses,
117 | CheckAddressParams memory checkAddresses,
118 | MintRecipient[] memory mintRecipients
119 | )
120 | private
121 | {
122 | // Step 1: Perform controller sanity checks
123 |
124 | MainnetController newController = MainnetController(controllerInst.controller);
125 |
126 | require(newController.hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-controller");
127 |
128 | require(address(newController.proxy()) == controllerInst.almProxy, "MainnetControllerInit/incorrect-almProxy");
129 | require(address(newController.rateLimits()) == controllerInst.rateLimits, "MainnetControllerInit/incorrect-rateLimits");
130 |
131 | require(address(newController.vault()) == checkAddresses.vault, "MainnetControllerInit/incorrect-vault");
132 | require(address(newController.psm()) == checkAddresses.psm, "MainnetControllerInit/incorrect-psm");
133 | require(address(newController.daiUsds()) == checkAddresses.daiUsds, "MainnetControllerInit/incorrect-daiUsds");
134 | require(address(newController.cctp()) == checkAddresses.cctp, "MainnetControllerInit/incorrect-cctp");
135 |
136 | require(newController.psmTo18ConversionFactor() == 1e12, "MainnetControllerInit/incorrect-psmTo18ConversionFactor");
137 |
138 | require(configAddresses.oldController != address(newController), "MainnetControllerInit/old-controller-is-new-controller");
139 |
140 | // Step 2: Configure ACL permissions controller, almProxy, and rateLimits
141 |
142 | IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
143 | IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
144 |
145 | almProxy.grantRole(almProxy.CONTROLLER(), address(newController));
146 | newController.grantRole(newController.FREEZER(), configAddresses.freezer);
147 | rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController));
148 |
149 | for (uint256 i; i < configAddresses.relayers.length; ++i) {
150 | newController.grantRole(newController.RELAYER(), configAddresses.relayers[i]);
151 | }
152 |
153 | // Step 3: Configure the mint recipients on other domains
154 |
155 | for (uint256 i; i < mintRecipients.length; ++i) {
156 | newController.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient);
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = "src"
3 | out = "out"
4 | libs = ["lib"]
5 | solc_version = '0.8.25'
6 | optimizer = true
7 | optimizer_runs = 1
8 | fs_permissions = [
9 | { access = "read", path = "./script/input/"},
10 | { access = "read-write", path = "./script/output/"}
11 | ]
12 | evm_version = 'cancun'
13 | remappings = [
14 | '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/',
15 | 'layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/',
16 | '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol',
17 | '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
18 | '@layerzerolabs/lz-evm-messagelib-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/messagelib/',
19 | 'solidity-bytes-utils/=lib/solidity-bytes-utils/',
20 | ]
21 |
22 | [fuzz]
23 | runs = 1000
24 |
25 | [invariant]
26 | runs = 1
27 | depth = 100
28 |
29 | [etherscan]
30 | mainnet = { key = "${ETHERSCAN_API_KEY}" }
31 | optimism = { key = "${OPTIMISMSCAN_API_KEY}" }
32 | base = { key = "${BASESCAN_API_KEY}" }
33 | gnosis_chain = { key = "${GNOSISSCAN_API_KEY}", url = "https://api.gnosisscan.io/api" }
34 | arbitrum_one = { key = "${ARBISCAN_API_KEY}" }
35 | world_chain = { key = "${WORLD_CHAIN_API_KEY}", chain = 480, url = "https://worldchain-mainnet-explorer.alchemy.com/api" }
36 | unichain = { key = "${UNICHAIN_API_KEY}", chain = 130, url = "https://unichain.blockscout.com/api" }
37 |
38 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config
39 |
--------------------------------------------------------------------------------
/script/Deploy.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import { ScriptTools } from "dss-test/ScriptTools.sol";
5 |
6 | import "forge-std/Script.sol";
7 |
8 | import { ControllerInstance } from "../deploy/ControllerInstance.sol";
9 |
10 | import { ForeignControllerDeploy, MainnetControllerDeploy } from "../deploy/ControllerDeploy.sol";
11 |
12 | contract DeployMainnetFull is Script {
13 |
14 | using stdJson for string;
15 | using ScriptTools for string;
16 |
17 | function run() external {
18 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
19 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
20 |
21 | vm.createSelectFork(getChain("mainnet").rpcUrl);
22 |
23 | console.log("Deploying Mainnet ALMProxy, Controller and RateLimits...");
24 |
25 | string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV")));
26 |
27 | vm.startBroadcast();
28 |
29 | string memory config = ScriptTools.loadConfig(fileSlug);
30 |
31 | ControllerInstance memory instance = MainnetControllerDeploy.deployFull({
32 | admin : config.readAddress(".admin"),
33 | vault : config.readAddress(".allocatorVault"),
34 | psm : config.readAddress(".psm"),
35 | daiUsds : config.readAddress(".daiUsds"),
36 | cctp : config.readAddress(".cctpTokenMessenger")
37 | });
38 |
39 | vm.stopBroadcast();
40 |
41 | console.log("ALMProxy deployed at", instance.almProxy);
42 | console.log("Controller deployed at", instance.controller);
43 | console.log("RateLimits deployed at", instance.rateLimits);
44 |
45 | ScriptTools.exportContract(fileSlug, "almProxy", instance.almProxy);
46 | ScriptTools.exportContract(fileSlug, "controller", instance.controller);
47 | ScriptTools.exportContract(fileSlug, "rateLimits", instance.rateLimits);
48 | }
49 |
50 | }
51 |
52 | contract DeployMainnetController is Script {
53 |
54 | using stdJson for string;
55 | using ScriptTools for string;
56 |
57 | function run() external {
58 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
59 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
60 |
61 | vm.createSelectFork(getChain("mainnet").rpcUrl);
62 |
63 | console.log("Deploying Mainnet Controller...");
64 |
65 | string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV")));
66 |
67 | vm.startBroadcast();
68 |
69 | string memory config = ScriptTools.loadConfig(fileSlug);
70 |
71 | address controller = MainnetControllerDeploy.deployController({
72 | admin : config.readAddress(".admin"),
73 | almProxy : config.readAddress(".almProxy"),
74 | rateLimits : config.readAddress(".rateLimits"),
75 | vault : config.readAddress(".allocatorVault"),
76 | psm : config.readAddress(".psm"),
77 | daiUsds : config.readAddress(".daiUsds"),
78 | cctp : config.readAddress(".cctpTokenMessenger")
79 | });
80 |
81 | vm.stopBroadcast();
82 |
83 | console.log("Controller deployed at", controller);
84 |
85 | ScriptTools.exportContract(fileSlug, "controller", controller);
86 | }
87 |
88 | }
89 |
90 | contract DeployForeignFull is Script {
91 |
92 | using stdJson for string;
93 | using ScriptTools for string;
94 |
95 | function run() external {
96 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
97 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
98 |
99 | string memory chainName = vm.envString("CHAIN");
100 | string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV")));
101 | string memory config = ScriptTools.loadConfig(fileSlug);
102 |
103 | vm.createSelectFork(getChain(chainName).rpcUrl);
104 |
105 | console.log(string(abi.encodePacked("Deploying ", chainName, " ALMProxy, Controller and RateLimits...")));
106 |
107 | vm.startBroadcast();
108 |
109 | ControllerInstance memory instance = ForeignControllerDeploy.deployFull({
110 | admin : config.readAddress(".admin"),
111 | psm : config.readAddress(".psm"),
112 | usdc : config.readAddress(".usdc"),
113 | cctp : config.readAddress(".cctpTokenMessenger")
114 | });
115 |
116 | vm.stopBroadcast();
117 |
118 | console.log("ALMProxy deployed at", instance.almProxy);
119 | console.log("Controller deployed at", instance.controller);
120 | console.log("RateLimits deployed at", instance.rateLimits);
121 |
122 | ScriptTools.exportContract(fileSlug, "almProxy", instance.almProxy);
123 | ScriptTools.exportContract(fileSlug, "controller", instance.controller);
124 | ScriptTools.exportContract(fileSlug, "rateLimits", instance.rateLimits);
125 | }
126 |
127 | }
128 |
129 | contract DeployForeignController is Script {
130 |
131 | using stdJson for string;
132 | using ScriptTools for string;
133 |
134 | function run() external {
135 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
136 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
137 |
138 | string memory chainName = vm.envString("CHAIN");
139 | string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV")));
140 | string memory config = ScriptTools.loadConfig(fileSlug);
141 |
142 | vm.createSelectFork(getChain(chainName).rpcUrl);
143 |
144 | console.log(string(abi.encodePacked("Deploying ", chainName, " Controller...")));
145 |
146 | vm.startBroadcast();
147 |
148 | address controller = ForeignControllerDeploy.deployController({
149 | admin : config.readAddress(".admin"),
150 | almProxy : config.readAddress(".almProxy"),
151 | rateLimits : config.readAddress(".rateLimits"),
152 | psm : config.readAddress(".psm"),
153 | usdc : config.readAddress(".usdc"),
154 | cctp : config.readAddress(".cctpTokenMessenger")
155 | });
156 |
157 | vm.stopBroadcast();
158 |
159 | console.log("Controller deployed at", controller);
160 |
161 | ScriptTools.exportContract(fileSlug, "controller", controller);
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/script/Upgrade.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import { ScriptTools } from "dss-test/ScriptTools.sol";
5 |
6 | import "forge-std/Script.sol";
7 |
8 | import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol";
9 |
10 | import { ControllerInstance } from "../deploy/ControllerInstance.sol";
11 | import { ForeignControllerInit as ForeignInit } from "../deploy/ForeignControllerInit.sol";
12 | import { MainnetControllerInit as MainnetInit } from "../deploy/MainnetControllerInit.sol";
13 |
14 | contract UpgradeMainnetController is Script {
15 |
16 | using stdJson for string;
17 | using ScriptTools for string;
18 |
19 | function run() external {
20 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
21 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
22 |
23 | vm.createSelectFork(getChain("mainnet").rpcUrl);
24 |
25 | console.log("Upgrading mainnet controller...");
26 |
27 | string memory fileSlug = string(abi.encodePacked("mainnet-", vm.envString("ENV")));
28 |
29 | address newController = vm.envAddress("NEW_CONTROLLER");
30 | address oldController = vm.envAddress("OLD_CONTROLLER");
31 |
32 | vm.startBroadcast();
33 |
34 | string memory inputConfig = ScriptTools.readInput(fileSlug);
35 |
36 | ControllerInstance memory controllerInst = ControllerInstance({
37 | almProxy : inputConfig.readAddress(".almProxy"),
38 | controller : newController,
39 | rateLimits : inputConfig.readAddress(".rateLimits")
40 | });
41 |
42 | address[] memory relayers = new address[](2);
43 | relayers[0] = inputConfig.readAddress(".relayer");
44 | relayers[1] = inputConfig.readAddress(".backstopRelayer");
45 |
46 | MainnetInit.ConfigAddressParams memory configAddresses = MainnetInit.ConfigAddressParams({
47 | freezer : inputConfig.readAddress(".freezer"),
48 | relayers : relayers,
49 | oldController : oldController
50 | });
51 |
52 | MainnetInit.CheckAddressParams memory checkAddresses = MainnetInit.CheckAddressParams({
53 | admin : inputConfig.readAddress(".admin"),
54 | proxy : inputConfig.readAddress(".almProxy"),
55 | rateLimits : inputConfig.readAddress(".rateLimits"),
56 | vault : inputConfig.readAddress(".allocatorVault"),
57 | psm : inputConfig.readAddress(".psm"),
58 | daiUsds : inputConfig.readAddress(".daiUsds"),
59 | cctp : inputConfig.readAddress(".cctpTokenMessenger")
60 | });
61 |
62 | MainnetInit.MintRecipient[] memory mintRecipients = new MainnetInit.MintRecipient[](1);
63 |
64 | string memory baseInputConfig = ScriptTools.readInput(string(abi.encodePacked("base-", vm.envString("ENV"))));
65 |
66 | address baseAlmProxy = baseInputConfig.readAddress(".almProxy");
67 |
68 | mintRecipients[0] = MainnetInit.MintRecipient({
69 | domain : CCTPForwarder.DOMAIN_ID_CIRCLE_BASE,
70 | mintRecipient : bytes32(uint256(uint160(baseAlmProxy)))
71 | });
72 |
73 | MainnetInit.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients);
74 |
75 | vm.stopBroadcast();
76 |
77 | console.log("ALMProxy updated at ", controllerInst.almProxy);
78 | console.log("RateLimits upgraded at ", controllerInst.rateLimits);
79 | console.log("Controller upgraded at ", newController);
80 | console.log("Old Controller deprecated at", oldController);
81 | }
82 |
83 | }
84 |
85 | contract UpgradeForeignController is Script {
86 |
87 | using stdJson for string;
88 | using ScriptTools for string;
89 |
90 | function run() external {
91 | vm.setEnv("FOUNDRY_ROOT_CHAINID", "1");
92 | vm.setEnv("FOUNDRY_EXPORTS_OVERWRITE_LATEST", "true");
93 |
94 | string memory chainName = vm.envString("CHAIN");
95 | string memory fileSlug = string(abi.encodePacked(chainName, "-", vm.envString("ENV")));
96 |
97 | address newController = vm.envAddress("NEW_CONTROLLER");
98 | address oldController = vm.envAddress("OLD_CONTROLLER");
99 |
100 | vm.createSelectFork(getChain(chainName).rpcUrl);
101 |
102 | console.log(string(abi.encodePacked("Upgrading ", chainName, " controller...")));
103 |
104 | vm.startBroadcast();
105 |
106 | string memory inputConfig = ScriptTools.readInput(fileSlug);
107 |
108 | ControllerInstance memory controllerInst = ControllerInstance({
109 | almProxy : inputConfig.readAddress(".almProxy"),
110 | controller : newController,
111 | rateLimits : inputConfig.readAddress(".rateLimits")
112 | });
113 |
114 | address[] memory relayers = new address[](2);
115 | relayers[0] = inputConfig.readAddress(".relayer");
116 | relayers[1] = inputConfig.readAddress(".backstopRelayer");
117 |
118 | ForeignInit.ConfigAddressParams memory configAddresses = ForeignInit.ConfigAddressParams({
119 | freezer : inputConfig.readAddress(".freezer"),
120 | relayers : relayers,
121 | oldController : oldController
122 | });
123 |
124 | ForeignInit.CheckAddressParams memory checkAddresses = ForeignInit.CheckAddressParams({
125 | admin : inputConfig.readAddress(".admin"),
126 | psm : inputConfig.readAddress(".psm"),
127 | cctp : inputConfig.readAddress(".cctpTokenMessenger"),
128 | usdc : inputConfig.readAddress(".usdc"),
129 | susds : inputConfig.readAddress(".susds"),
130 | usds : inputConfig.readAddress(".usds")
131 | });
132 |
133 | ForeignInit.MintRecipient[] memory mintRecipients = new ForeignInit.MintRecipient[](1);
134 |
135 | string memory mainnetInputConfig = ScriptTools.readInput(string(abi.encodePacked("mainnet-", vm.envString("ENV"))));
136 |
137 | address mainnetAlmProxy = mainnetInputConfig.readAddress(".almProxy");
138 |
139 | mintRecipients[0] = ForeignInit.MintRecipient({
140 | domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM,
141 | mintRecipient : bytes32(uint256(uint160(mainnetAlmProxy)))
142 | });
143 |
144 | ForeignInit.upgradeController(controllerInst, configAddresses, checkAddresses, mintRecipients);
145 |
146 | vm.stopBroadcast();
147 |
148 | console.log("ALMProxy updated at ", controllerInst.almProxy);
149 | console.log("RateLimits upgraded at ", controllerInst.rateLimits);
150 | console.log("Controller upgraded at ", newController);
151 | console.log("Old controller deprecated at", oldController);
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/script/input/1/arbitrum_one-staging.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0xAF6B11Ab939d96AFAa9e63c679C1594F9Dc03dB9",
4 | "cctpTokenMessenger": "0x19330d10D9Cc8751218eaf51E8885D058642E08A",
5 | "controller": "0x1b05EE24ac60bD60e151959c7DFbE053111F43bF",
6 | "freezer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F",
7 | "psm": "0x5c077cc797b2b0c2ea99c0583e096c243cea9db0",
8 | "rateLimits": "0x1CF8b4F9e106730F7984A5E34440E0624A638FFF",
9 | "relayer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F",
10 | "susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
11 | "usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
12 | "usds": "0x6491c05A82219b8D1479057361ff1654749b876b"
13 | }
14 |
--------------------------------------------------------------------------------
/script/input/1/base-production.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579",
3 | "almProxy": "0x2917956eFF0B5eaF030abDB4EF4296DF775009cA",
4 | "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962",
5 | "controller": "0x5F032555353f3A1D16aA6A4ADE0B35b369da0440",
6 | "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
7 | "psm": "0x1601843c5E9bC251A3272907010AFa41Fa18347E",
8 | "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74",
9 | "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB",
10 | "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a",
11 | "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
12 | "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc"
13 | }
14 |
--------------------------------------------------------------------------------
/script/input/1/base-staging.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0x370E141E3a568A314bF84decB8c07b82Bb7f1831",
4 | "cctpTokenMessenger": "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962",
5 | "controller": "0xAa1e0895596CbddED9fBf5eB408814Bb944C0a45",
6 | "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
7 | "psm": "0xaf6b11ab939d96afaa9e63c679c1594f9dc03db9",
8 | "rateLimits": "0x992e0a478687545b6b9a18528E835148b68A23E9",
9 | "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
10 | "susds": "0x5875eEE11Cf8398102FdAd704C9E96607675467a",
11 | "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
12 | "usds": "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc"
13 | }
14 |
--------------------------------------------------------------------------------
/script/input/1/mainnet-production.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x3300f198988e4C9C63F75dF86De36421f06af8c4",
3 | "allocatorVault": "0x691a6c29e9e96dd897718305427Ad5D534db16BA",
4 | "almProxy": "0x1601843c5E9bC251A3272907010AFa41Fa18347E",
5 | "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155",
6 | "controller": "0x5cf73FDb7057E436A6eEaDFAd27E45E7ab6E431e",
7 | "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
8 | "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A",
9 | "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
10 | "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042",
11 | "rateLimits": "0x7A5FD5cf045e010e62147F065cEAe59e5344b188",
12 | "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB",
13 | "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
14 | "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
15 | "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F"
16 | }
17 |
--------------------------------------------------------------------------------
/script/input/1/mainnet-staging.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "allocatorBuffer": "0xD310D2b300dA469fBc971CFB96b8168628CdA290",
4 | "allocatorOracle": "0x6B915170D924984270189aEC4b0D83dFcD45afAa",
5 | "allocatorRegistry": "0xE2A65Eb6cfD8BCc9f0682a5d1875E4d798a416Ee",
6 | "allocatorRoles": "0xf996D4D87d9Df257D9EFa4FE48706e22D504B898",
7 | "allocatorVault": "0x295d042F5804d7149fA75513Df6249815fc441F8",
8 | "almProxy": "0xe6A3179615cA28abd2d0a0d83bAAC21B24Ff7fFF",
9 | "cctpTokenMessenger": "0xBd3fa81B58Ba92a82136038B25aDec7066af3155",
10 | "controller": "0x37961cb56405cF6600EFE33c029A4c5Dcd0F4330",
11 | "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
12 | "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A",
13 | "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
14 | "ilk": "ALLOCATOR-SPARK-A",
15 | "jug": "0xD41Dbe8f9e7332a57b72BfCB77b671aAd8253379",
16 | "psm": "0xf6e72Db5454dd049d0788e411b06CfAF16853042",
17 | "psmWrapper": "0xDb6830e69Ec65408aE553e127EAa1b6F36e9F78A",
18 | "rateLimits": "0xdc140d03d97946092F2DAeD7d6177541b6805383",
19 | "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
20 | "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
21 | "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
22 | "usdcUnitSize": 10,
23 | "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F",
24 | "usdsJoin": "0x1C59Ce3403F88C1F952A693328771251e71976fc",
25 | "usdsUnitSize": 10,
26 | "vat": "0xAE2524825ed4F9d4f8e69379F264af5eE6Df3394"
27 | }
28 |
--------------------------------------------------------------------------------
/script/input/1/unichain-production.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xb037C43b433964A2017cd689f535BEb6B0531473",
3 | "cctpTokenMessenger": "0x4e744b28E787c3aD0e810eD65A24461D4ac5a762",
4 | "psm": "0x7b42Ed932f26509465F7cE3FAF76FfCe1275312f",
5 | "relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB",
6 | "freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
7 | "usdc": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
8 | "usds": "0x7E10036Acc4B56d4dFCa3b77810356CE52313F9C",
9 | "susds": "0xA06b10Db9F390990364A3984C04FaDf1c13691b5"
10 | }
11 |
--------------------------------------------------------------------------------
/script/output/1/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkdotfi/spark-alm-controller/7f0a473951e4c5528d52ee442461662976c4a947/script/output/1/.gitkeep
--------------------------------------------------------------------------------
/script/output/1/arbitrum_one-staging-release-20250226.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0xBC1B4aF7E41fcBD26472fa72490d777f95014E3F",
3 | "controller": "0xfd6B8A9CEbd4f2204245B6c403704BF929DCAFEF",
4 | "rateLimits": "0x52CC503225Bf7CE0D2267165f6860C7F701e71bE"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/arbitrum_one-staging-release-20250402.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0xe25DD602aA54B1346B8D004D10D51851C451fbF3",
4 | "controller": "0x149aCa3bC479EfB254aea74EB8c71625AEC55465",
5 | "freezer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F",
6 | "rateLimits": "0x7B843f95AA7a6A72836F1fFE2B10ca226236BB14",
7 | "relayer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F"
8 | }
--------------------------------------------------------------------------------
/script/output/1/arbitrum_one-staging-release-20250409.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0xAF6B11Ab939d96AFAa9e63c679C1594F9Dc03dB9",
4 | "controller": "0x1b05EE24ac60bD60e151959c7DFbE053111F43bF",
5 | "freezer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F",
6 | "rateLimits": "0x1CF8b4F9e106730F7984A5E34440E0624A638FFF",
7 | "relayer": "0x19C6728873c6d247582A8b39fF410B42C4AE5a3F"
8 | }
9 |
--------------------------------------------------------------------------------
/script/output/1/base-production-release-20241023.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0x2917956eFF0B5eaF030abDB4EF4296DF775009cA",
3 | "controller": "0xc07f705D0C0e9F8C79C5fbb748aC1246BBCC37Ba",
4 | "rateLimits": "0x983eC82E45C61a42FDDA7B3c43B8C767004c8A74"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-production-release-20241229.json:
--------------------------------------------------------------------------------
1 | {
2 | "controller": "0x5F032555353f3A1D16aA6A4ADE0B35b369da0440"
3 | }
4 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-deps-release-20241022.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4",
3 | "psm": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7",
4 | "susds": "0x4ae97016a03C132d2F600444E2493C62B01C9497",
5 | "safe": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
6 | "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
7 | "usds": "0x4e9BEe8F2b33d8893a5A219854AC52e9518ee328"
8 | }
9 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-deps-release-20241210.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd",
3 | "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
4 | "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-deps-release-20241227.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd",
3 | "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
4 | "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-release-20241022.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0x94eA1518cACD45786Dbe0fe646F93446F94d21FE",
3 | "controller": "0xD26112Ce8f7BE0834dBcfd018042bF76d68Ff42a",
4 | "rateLimits": "0x79F826786953fb42aed02796F792EF8f2701d18b"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-release-20241210.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0x2627d5D0AF0B88Ee58BD7346F20A429f67a73e00",
3 | "controller": "0xa3091Dfa6c02B6611250733852c95A59a127E00F",
4 | "rateLimits": "0xAe20F9093eB3301b2D83871A3505935eFc8498C6"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-release-20241227.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0xa72e01A942f5E8EF09dbaf824C2d7a7033e96f0D",
3 | "controller": "0xf1202d64010a7b644AB258ca46Ad5fDf2148905a",
4 | "rateLimits": "0x1d741314F73Aea8A133C5c71653F779150f9c229"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/base-staging-release-20250402.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0x5c077cC797B2b0C2ea99c0583e096c243cEa9Db0",
4 | "controller": "0x2eD26eF498B2e9A623Cdb6af9b48afF1a1cF920A",
5 | "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
6 | "rateLimits": "0xf13600B1fC2ec6BF98e1d8B924E5e478De85B123",
7 | "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047"
8 | }
--------------------------------------------------------------------------------
/script/output/1/base-staging-release-20250409.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "almProxy": "0x370E141E3a568A314bF84decB8c07b82Bb7f1831",
4 | "controller": "0xAa1e0895596CbddED9fBf5eB408814Bb944C0a45",
5 | "freezer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047",
6 | "rateLimits": "0x992e0a478687545b6b9a18528E835148b68A23E9",
7 | "relayer": "0xaB959A6F88b8D966c44a7cDC2049Ba9669EBf047"
8 | }
9 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-production-release-20241023.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0x1601843c5E9bC251A3272907010AFa41Fa18347E",
3 | "controller": "0xb960F71ca3f1f57799F6e14501607f64f9B36F11",
4 | "rateLimits": "0x7A5FD5cf045e010e62147F065cEAe59e5344b188"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-production-release-20241229.json:
--------------------------------------------------------------------------------
1 | {
2 | "controller": "0x5cf73FDb7057E436A6eEaDFAd27E45E7ab6E431e"
3 | }
4 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-deps-release-20241022.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x6F3066538A648b9CFad0679DF0a7e40882A23AA4",
3 | "allocatorBuffer": "0x6b728c4Fa4746a78e9af2cD75C712b5Bf2A90Ae7",
4 | "allocatorOracle": "0x4ae97016a03C132d2F600444E2493C62B01C9497",
5 | "allocatorRegistry": "0x6f75E221ccd8D7C496a48Ff4fd854C2319F2DaE2",
6 | "allocatorRoles": "0x87E8A7537875661d12a31912FAaF07f50043e3D7",
7 | "allocatorVault": "0x8E20650287635aE6e20ce38EcD3E795919D52354",
8 | "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
9 | "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A",
10 | "jug": "0x464c46b3bFCf261ABFe440F90f08a08A39a59DD4",
11 | "psm": "0x91AA02EDe82D3C2f49A2d5a7efBA7ba4403100C8",
12 | "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
13 | "safe": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
14 | "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
15 | "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F",
16 | "usdsJoin": "0xA1CCf21b7012874fB2CD81704e0eeeF083DDe1b8",
17 | "vat": "0xea53A79Fc3e024C887f37E844FA24B3DEbEC84B4"
18 | }
19 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-deps-release-20241210.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd",
3 | "allocatorBuffer": "0x36138584868028D1913bf01359D7c736E6773008",
4 | "allocatorOracle": "0xfC0E1fFBF9cCd82688c775b1587c45506cebDBdf",
5 | "allocatorRegistry": "0xfd0A671c07309f14b05ec72c741C86AEA02e873c",
6 | "allocatorRoles": "0xF954e125E979e104974882ca94063B4f088cf71D",
7 | "allocatorVault": "0xAB0d4019B1182021C4cAa2F3D078EFe55cD5B5A6",
8 | "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
9 | "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A",
10 | "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
11 | "jug": "0xf999576B81c53BFf473550354eeD98cD7b126184",
12 | "psm": "0x8ac160e388a3F975c9Db1D41DeB76C574702CFa9",
13 | "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
14 | "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
15 | "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
16 | "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F",
17 | "usdsJoin": "0xAd309294c38EB44C929a9bB2A9452B1C8D96965e",
18 | "vat": "0x373E0699D8bFDdd99d78248e5b993E6a02061B0e"
19 | }
20 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-deps-release-20241227.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0xd1236a6A111879d9862f8374BA15344b6B233Fbd",
3 | "allocatorBuffer": "0x21fd5b6f0FbF7300a23F9cC397630A27Ee013AE5",
4 | "allocatorOracle": "0xDf9EE9Ad3cB7ec26F98103751F7Fb7149a284541",
5 | "allocatorRegistry": "0x2143B2949bc3B9b405cFebb065aB6beF4DB85956",
6 | "allocatorRoles": "0x041918Ef264214BD999776667a693b06B35c36Fa",
7 | "allocatorVault": "0x85cb1558802D32D437B63d7C3eB4e6d6c88a383B",
8 | "dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
9 | "daiUsds": "0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A",
10 | "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
11 | "jug": "0x6C20DC38D8e978955B12217D82692d448239C005",
12 | "psm": "0x66c044CeE6bdf8360C2F6eDffc712829900100fB",
13 | "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
14 | "susds": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
15 | "usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
16 | "usds": "0xdC035D45d973E3EC169d2276DDab16f1e407384F",
17 | "usdsJoin": "0x12CBa9C1e5CC66926d1364B63e62cF16830bF977",
18 | "vat": "0x2157802ce1172b7bae5540b0d20d8B4337B535C2"
19 | }
20 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-release-20241022.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0xC29D06ce81137E6B3C3DC090713636d81600a347",
3 | "controller": "0xcc0c5ADF6649256d3cE6084eCf94AF5D01440b6C",
4 | "rateLimits": "0x9A140AC56CC28A00B2c036F454F202c2459ca84c"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-release-20241210.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0x675fc95BF2b42Fc61FF0f2E9969d9Ab19b65cda5",
3 | "controller": "0x08c830bc14b52A65E7e62aBc7365e1C53933D4Bf",
4 | "rateLimits": "0x449F100E37CF9CC4631c044efC4726609Be26766"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-release-20241227.json:
--------------------------------------------------------------------------------
1 | {
2 | "almProxy": "0xba8b8375Af6bB5B7e0424D8581b82fe39CfCff8A",
3 | "controller": "0xe08828c1A20dA7874A52dA070480B9B1e4213B6C",
4 | "rateLimits": "0x2A85Ce4869e8fCFbb84b39485eB082F96d8f1b5d"
5 | }
6 |
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-release-20250402.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "allocatorBuffer": "0x511ecc909b49d5F6Db5ff0DD8DcDB272c8Bc6569",
4 | "allocatorOracle": "0x011533ddfcd6566cF1ab1c03375a4Cdbdd289607",
5 | "allocatorRegistry": "0x94a01e71Abe2f441aa976F89f85Ba6438DCdC3e2",
6 | "allocatorRoles": "0x56E492C966E9704F865df436ceb6306FFf4A6fd9",
7 | "allocatorVault": "0x1fFd8A8382bCdB872ac62e36933c4e8Fc462728A",
8 | "almProxy": "0x07EF00D45C85a2046c714D0E944EB6dc28618C08",
9 | "controller": "0x3ba940F029a93Ba2FBBD10cE7507e9126f9dff6c",
10 | "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
11 | "jug": "0xa42473893FfEAf666e9632c1d4200E2d1046E1Ec",
12 | "psmWrapper": "0x96B0da3F44a5E1d545530F1C33b1485afa8Ee6E8",
13 | "rateLimits": "0xc0117e8600FE666AE0CCA1b4fC1890ABF8E1ec49",
14 | "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
15 | "usdsJoin": "0xc1Ca37dfDf716bf863e055820454E9a8581E03f4",
16 | "vat": "0x1a698C8E8f32a2ec193589c98Bb5a4CbBDD65dE4"
17 | }
--------------------------------------------------------------------------------
/script/output/1/mainnet-staging-release-20250409.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "0x62B5262D3639eA5A8ec0D8Aa442f1135ecF77205",
3 | "allocatorBuffer": "0xD310D2b300dA469fBc971CFB96b8168628CdA290",
4 | "allocatorOracle": "0x6B915170D924984270189aEC4b0D83dFcD45afAa",
5 | "allocatorRegistry": "0xE2A65Eb6cfD8BCc9f0682a5d1875E4d798a416Ee",
6 | "allocatorRoles": "0xf996D4D87d9Df257D9EFa4FE48706e22D504B898",
7 | "allocatorVault": "0x295d042F5804d7149fA75513Df6249815fc441F8",
8 | "almProxy": "0xe6A3179615cA28abd2d0a0d83bAAC21B24Ff7fFF",
9 | "controller": "0x37961cb56405cF6600EFE33c029A4c5Dcd0F4330",
10 | "freezer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
11 | "jug": "0xD41Dbe8f9e7332a57b72BfCB77b671aAd8253379",
12 | "psmWrapper": "0xDb6830e69Ec65408aE553e127EAa1b6F36e9F78A",
13 | "rateLimits": "0xdc140d03d97946092F2DAeD7d6177541b6805383",
14 | "relayer": "0x611C7c37F296240c2fF5a92f0B4a398B01B237c4",
15 | "usdsJoin": "0x1C59Ce3403F88C1F952A693328771251e71976fc",
16 | "vat": "0xAE2524825ed4F9d4f8e69379F264af5eE6Df3394"
17 | }
18 |
--------------------------------------------------------------------------------
/script/staging/mocks/MockJug.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | contract MockJug {
5 |
6 | function drip(bytes32) external pure returns (uint256) {
7 | return 1e27;
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/script/staging/mocks/MockUsdsJoin.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { IERC20 } from "forge-std/interfaces/IERC20.sol";
5 |
6 | import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol";
7 |
8 | contract MockUsdsJoin is Ownable {
9 |
10 | address public immutable vat;
11 | IERC20 public immutable usds;
12 |
13 | constructor(address owner_, address vat_, address usds_) Ownable(owner_) {
14 | vat = vat_;
15 | usds = IERC20(usds_);
16 | }
17 |
18 | function join(address, uint256 wad) external onlyOwner {
19 | usds.transferFrom(msg.sender, address(this), wad);
20 | }
21 |
22 | function exit(address usr, uint256 wad) external onlyOwner {
23 | usds.transfer(usr, wad);
24 | }
25 |
26 | // To fully cover daiJoin abi
27 | function dai() external view returns (address) {
28 | return address(usds);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/script/staging/mocks/MockVat.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol";
5 |
6 | contract MockVat is Ownable {
7 |
8 | uint256 public ilkLine;
9 |
10 | constructor(address _owner) Ownable(_owner) {
11 | ilkLine = 1e9 * 1e45; // Just make it some really large number by default so we can ignore
12 | }
13 |
14 | function ilks(bytes32) external view returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust) {
15 | return (0, 1e27, 0, ilkLine, 0);
16 | }
17 |
18 | function setIlkLine(uint256 _line) external onlyOwner {
19 | ilkLine = _line;
20 | }
21 |
22 | function hope(address usr) external {
23 | }
24 |
25 | function frob(bytes32 i, address u, address v, address w, int dink, int dart) external {
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/script/staging/mocks/PSMWrapper.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { IERC20 } from "forge-std/interfaces/IERC20.sol";
5 |
6 | interface IPSMLike {
7 | function buyGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
8 | function fill() external returns (uint256 wad);
9 | function gem() external view returns(address);
10 | function pocket() external view returns(address);
11 | function sellGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
12 | function to18ConversionFactor() external view returns (uint256);
13 | }
14 |
15 | interface ILivePSMLike is IPSMLike {
16 | function buyGem(address usr, uint256 usdcAmount) external returns (uint256 swappedAmount);
17 | function sellGem(address usr, uint256 usdcAmount) external returns (uint256 swappedAmount);
18 | }
19 |
20 | // TODO: Add admin that can withdraw
21 |
22 | contract PSMWrapper {
23 |
24 | IERC20 public immutable usdc;
25 | IERC20 public immutable dai;
26 |
27 | ILivePSMLike public immutable psm;
28 |
29 | constructor(address usdc_, address dai_, address psm_) {
30 | usdc = IERC20(usdc_);
31 | dai = IERC20(dai_);
32 | psm = ILivePSMLike(psm_);
33 | }
34 |
35 | /**********************************************************************************************/
36 | /*** Wrapped external functions ***/
37 | /**********************************************************************************************/
38 |
39 | function buyGemNoFee(address usr, uint256 usdcAmount)
40 | external returns (uint256 swappedAmount)
41 | {
42 | uint256 daiAmount = usdcAmount * 1e12;
43 |
44 | dai.transferFrom(msg.sender, address(this), daiAmount);
45 | dai.approve(address(psm), daiAmount);
46 | swappedAmount = psm.buyGem(usr, usdcAmount);
47 | }
48 |
49 | function sellGemNoFee(address usr, uint256 usdcAmount)
50 | external returns (uint256 swappedAmount)
51 | {
52 | usdc.transferFrom(msg.sender, address(this), usdcAmount);
53 | usdc.approve(address(psm), usdcAmount);
54 | swappedAmount = psm.sellGem(usr, usdcAmount);
55 | }
56 |
57 | function fill() external returns (uint256) {
58 | return psm.fill();
59 | }
60 |
61 | /**********************************************************************************************/
62 | /*** Wrapped view functions ***/
63 | /**********************************************************************************************/
64 |
65 | function gem() external view returns (address) {
66 | return psm.gem();
67 | }
68 |
69 | function pocket() external view returns (address) {
70 | return psm.pocket();
71 | }
72 |
73 | function to18ConversionFactor() external view returns (uint256) {
74 | return psm.to18ConversionFactor();
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/ALMProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol";
5 | import { Address } from "openzeppelin-contracts/contracts/utils/Address.sol";
6 |
7 | import { IALMProxy } from "./interfaces/IALMProxy.sol";
8 |
9 | contract ALMProxy is IALMProxy, AccessControl {
10 |
11 | using Address for address;
12 |
13 | /**********************************************************************************************/
14 | /*** State variables ***/
15 | /**********************************************************************************************/
16 |
17 | bytes32 public override constant CONTROLLER = keccak256("CONTROLLER");
18 |
19 | /**********************************************************************************************/
20 | /*** Initialization ***/
21 | /**********************************************************************************************/
22 |
23 | constructor(address admin) {
24 | _grantRole(DEFAULT_ADMIN_ROLE, admin);
25 | }
26 |
27 | /**********************************************************************************************/
28 | /*** Call functions ***/
29 | /**********************************************************************************************/
30 |
31 | function doCall(address target, bytes memory data)
32 | external override onlyRole(CONTROLLER) returns (bytes memory result)
33 | {
34 | result = target.functionCall(data);
35 | }
36 |
37 | function doCallWithValue(address target, bytes memory data, uint256 value)
38 | external payable override onlyRole(CONTROLLER) returns (bytes memory result)
39 | {
40 | result = target.functionCallWithValue(data, value);
41 | }
42 |
43 | function doDelegateCall(address target, bytes memory data)
44 | external override onlyRole(CONTROLLER) returns (bytes memory result)
45 | {
46 | result = target.functionDelegateCall(data);
47 | }
48 |
49 | /**********************************************************************************************/
50 | /*** Receive function ***/
51 | /**********************************************************************************************/
52 |
53 | receive() external payable { }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/RateLimitHelpers.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | library RateLimitHelpers {
5 |
6 | function makeAssetKey(bytes32 key, address asset) internal pure returns (bytes32) {
7 | return keccak256(abi.encode(key, asset));
8 | }
9 |
10 | function makeAssetDestinationKey(bytes32 key, address asset, address destination) internal pure returns (bytes32) {
11 | return keccak256(abi.encode(key, asset, destination));
12 | }
13 |
14 | function makeDomainKey(bytes32 key, uint32 domain) internal pure returns (bytes32) {
15 | return keccak256(abi.encode(key, domain));
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/RateLimits.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol";
5 |
6 | import { IRateLimits } from "./interfaces/IRateLimits.sol";
7 |
8 | contract RateLimits is IRateLimits, AccessControl {
9 |
10 | /**********************************************************************************************/
11 | /*** State variables ***/
12 | /**********************************************************************************************/
13 |
14 | bytes32 public override constant CONTROLLER = keccak256("CONTROLLER");
15 |
16 | mapping(bytes32 => RateLimitData) private _data;
17 |
18 | /**********************************************************************************************/
19 | /*** Initialization ***/
20 | /**********************************************************************************************/
21 |
22 | constructor(address admin_) {
23 | _grantRole(DEFAULT_ADMIN_ROLE, admin_);
24 | }
25 |
26 | /**********************************************************************************************/
27 | /*** Admin functions ***/
28 | /**********************************************************************************************/
29 |
30 | function setRateLimitData(
31 | bytes32 key,
32 | uint256 maxAmount,
33 | uint256 slope,
34 | uint256 lastAmount,
35 | uint256 lastUpdated
36 | )
37 | public override onlyRole(DEFAULT_ADMIN_ROLE)
38 | {
39 | require(lastAmount <= maxAmount, "RateLimits/invalid-lastAmount");
40 | require(lastUpdated <= block.timestamp, "RateLimits/invalid-lastUpdated");
41 |
42 | _data[key] = RateLimitData({
43 | maxAmount: maxAmount,
44 | slope: slope,
45 | lastAmount: lastAmount,
46 | lastUpdated: lastUpdated
47 | });
48 |
49 | emit RateLimitDataSet(key, maxAmount, slope, lastAmount, lastUpdated);
50 | }
51 |
52 | function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external override {
53 | setRateLimitData(key, maxAmount, slope, maxAmount, block.timestamp);
54 | }
55 |
56 | function setUnlimitedRateLimitData(bytes32 key) external override {
57 | setRateLimitData(key, type(uint256).max, 0, type(uint256).max, block.timestamp);
58 | }
59 |
60 | /**********************************************************************************************/
61 | /*** Getter Functions ***/
62 | /**********************************************************************************************/
63 |
64 | function getRateLimitData(bytes32 key) external override view returns (RateLimitData memory) {
65 | return _data[key];
66 | }
67 |
68 | function getCurrentRateLimit(bytes32 key) public override view returns (uint256) {
69 | RateLimitData memory d = _data[key];
70 |
71 | // Unlimited rate limit case
72 | if (d.maxAmount == type(uint256).max) {
73 | return type(uint256).max;
74 | }
75 |
76 | return _min(
77 | d.slope * (block.timestamp - d.lastUpdated) + d.lastAmount,
78 | d.maxAmount
79 | );
80 | }
81 |
82 | /**********************************************************************************************/
83 | /*** Controller functions ***/
84 | /**********************************************************************************************/
85 |
86 | function triggerRateLimitDecrease(bytes32 key, uint256 amountToDecrease)
87 | external
88 | override
89 | onlyRole(CONTROLLER)
90 | returns (uint256 newLimit)
91 | {
92 | RateLimitData storage d = _data[key];
93 | uint256 maxAmount = d.maxAmount;
94 |
95 | require(maxAmount > 0, "RateLimits/zero-maxAmount");
96 | if (maxAmount == type(uint256).max) return type(uint256).max; // Special case unlimited
97 |
98 | uint256 currentRateLimit = getCurrentRateLimit(key);
99 |
100 | require(amountToDecrease <= currentRateLimit, "RateLimits/rate-limit-exceeded");
101 |
102 | d.lastAmount = newLimit = currentRateLimit - amountToDecrease;
103 | d.lastUpdated = block.timestamp;
104 |
105 | emit RateLimitDecreaseTriggered(key, amountToDecrease, currentRateLimit, newLimit);
106 | }
107 |
108 | function triggerRateLimitIncrease(bytes32 key, uint256 amountToIncrease)
109 | external
110 | override
111 | onlyRole(CONTROLLER)
112 | returns (uint256 newLimit)
113 | {
114 | RateLimitData storage d = _data[key];
115 | uint256 maxAmount = d.maxAmount;
116 |
117 | require(maxAmount > 0, "RateLimits/zero-maxAmount");
118 | if (maxAmount == type(uint256).max) return type(uint256).max; // Special case unlimited
119 |
120 | uint256 currentRateLimit = getCurrentRateLimit(key);
121 |
122 | d.lastAmount = newLimit = _min(currentRateLimit + amountToIncrease, maxAmount);
123 | d.lastUpdated = block.timestamp;
124 |
125 | emit RateLimitIncreaseTriggered(key, amountToIncrease, currentRateLimit, newLimit);
126 | }
127 |
128 | /**********************************************************************************************/
129 | /*** Internal Utility Functions ***/
130 | /**********************************************************************************************/
131 |
132 | function _min(uint256 a, uint256 b) internal pure returns (uint256) {
133 | return a < b ? a : b;
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/interfaces/CCTPInterfaces.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | interface ICCTPLike {
5 |
6 | function depositForBurn(
7 | uint256 amount,
8 | uint32 destinationDomain,
9 | bytes32 mintRecipient,
10 | address burnToken
11 | ) external returns (uint64 nonce);
12 |
13 | function localMinter() external view returns (ICCTPTokenMinterLike);
14 |
15 | }
16 |
17 | interface ICCTPTokenMinterLike {
18 | function burnLimitsPerMessage(address) external view returns (uint256);
19 | }
20 |
--------------------------------------------------------------------------------
/src/interfaces/IALMProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol";
5 |
6 | interface IALMProxy is IAccessControl {
7 |
8 | /**
9 | * @dev This function retrieves a constant `bytes32` value that represents the controller.
10 | * @return The `bytes32` identifier of the controller.
11 | */
12 | function CONTROLLER() external view returns (bytes32);
13 |
14 | /**
15 | * @dev Performs a standard call to the specified `target` with the given `data`.
16 | * Reverts if the call fails.
17 | * @param target The address of the target contract to call.
18 | * @param data The calldata that will be sent to the target contract.
19 | * @return result The returned data from the call.
20 | */
21 | function doCall(address target, bytes calldata data)
22 | external returns (bytes memory result);
23 |
24 | /**
25 | * @dev This function allows for transferring `value` (ether) along with the call to the target contract.
26 | * Reverts if the call fails.
27 | * @param target The address of the target contract to call.
28 | * @param data The calldata that will be sent to the target contract.
29 | * @param value The amount of Ether (in wei) to send with the call.
30 | * @return result The returned data from the call.
31 | */
32 | function doCallWithValue(address target, bytes memory data, uint256 value)
33 | external payable returns (bytes memory result);
34 |
35 | /**
36 | * @dev This function performs a delegate call to the specified `target`
37 | * with the given `data`. Reverts if the call fails.
38 | * @param target The address of the target contract to delegate call.
39 | * @param data The calldata that will be sent to the target contract.
40 | * @return result The returned data from the delegate call.
41 | */
42 | function doDelegateCall(address target, bytes calldata data)
43 | external returns (bytes memory result);
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/interfaces/ILayerZero.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | struct MessagingFee {
5 | uint nativeFee; // gas amount in native gas token
6 | uint lzTokenFee; // gas amount in ZRO token
7 | }
8 |
9 | struct MessagingReceipt {
10 | bytes32 guid;
11 | uint64 nonce;
12 | MessagingFee fee;
13 | }
14 |
15 | /**
16 | * @dev Struct representing OFT fee details.
17 | * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
18 | */
19 | struct OFTFeeDetail {
20 | int256 feeAmountLD; // Amount of the fee in local decimals.
21 | string description; // Description of the fee.
22 | }
23 |
24 | /**
25 | * @dev Struct representing OFT limit information.
26 | * @dev These amounts can change dynamically and are up the the specific oft implementation.
27 | */
28 | struct OFTLimit {
29 | uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
30 | uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
31 | }
32 |
33 | struct OFTReceipt {
34 | uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
35 | // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
36 | uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
37 | }
38 |
39 | /**
40 | * @dev Struct representing token parameters for the OFT send() operation.
41 | */
42 | struct SendParam {
43 | uint32 dstEid; // Destination endpoint ID.
44 | bytes32 to; // Recipient address.
45 | uint256 amountLD; // Amount to send in local decimals.
46 | uint256 minAmountLD; // Minimum amount to send in local decimals.
47 | bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
48 | bytes composeMsg; // The composed message for the send() operation.
49 | bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
50 | }
51 |
52 | interface ILayerZero {
53 |
54 | function quoteOFT(
55 | SendParam calldata _sendParam
56 | ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);
57 |
58 | function quoteSend(
59 | SendParam calldata _sendParam,
60 | bool _payInLzToken
61 | ) external returns (MessagingFee memory msgFee);
62 |
63 | function send(
64 | SendParam calldata _sendParam,
65 | MessagingFee calldata _fee,
66 | address _refundAddress
67 | ) external payable returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt);
68 |
69 | function token() external view returns (address);
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/interfaces/IRateLimits.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol";
5 |
6 | interface IRateLimits is IAccessControl {
7 |
8 | /**********************************************************************************************/
9 | /*** Structs ***/
10 | /**********************************************************************************************/
11 |
12 | /**
13 | * @dev Struct representing a rate limit.
14 | * The current rate limit is calculated using the formula:
15 | * `currentRateLimit = min(slope * (block.timestamp - lastUpdated) + lastAmount, maxAmount)`.
16 | * @param maxAmount Maximum allowed amount at any time.
17 | * @param slope The slope of the rate limit, used to calculate the new
18 | * limit based on time passed. [tokens / second]
19 | * @param lastAmount The amount left available at the last update.
20 | * @param lastUpdated The timestamp when the rate limit was last updated.
21 | */
22 | struct RateLimitData {
23 | uint256 maxAmount;
24 | uint256 slope;
25 | uint256 lastAmount;
26 | uint256 lastUpdated;
27 | }
28 |
29 | /**********************************************************************************************/
30 | /*** Events ***/
31 | /**********************************************************************************************/
32 |
33 | /**
34 | * @dev Emitted when the rate limit data is set.
35 | * @param key The identifier for the rate limit.
36 | * @param maxAmount The maximum allowed amount for the rate limit.
37 | * @param slope The slope value used in the rate limit calculation.
38 | * @param lastAmount The amount left available at the last update.
39 | * @param lastUpdated The timestamp when the rate limit was last updated.
40 | */
41 | event RateLimitDataSet(
42 | bytes32 indexed key,
43 | uint256 maxAmount,
44 | uint256 slope,
45 | uint256 lastAmount,
46 | uint256 lastUpdated
47 | );
48 |
49 | /**
50 | * @dev Emitted when a rate limit decrease is triggered.
51 | * @param key The identifier for the rate limit.
52 | * @param amountToDecrease The amount to decrease from the current rate limit.
53 | * @param oldRateLimit The previous rate limit value before triggering.
54 | * @param newRateLimit The new rate limit value after triggering.
55 | */
56 | event RateLimitDecreaseTriggered(
57 | bytes32 indexed key,
58 | uint256 amountToDecrease,
59 | uint256 oldRateLimit,
60 | uint256 newRateLimit
61 | );
62 |
63 | /**
64 | * @dev Emitted when a rate limit increase is triggered.
65 | * @param key The identifier for the rate limit.
66 | * @param amountToIncrease The amount to increase from the current rate limit.
67 | * @param oldRateLimit The previous rate limit value before triggering.
68 | * @param newRateLimit The new rate limit value after triggering.
69 | */
70 | event RateLimitIncreaseTriggered(
71 | bytes32 indexed key,
72 | uint256 amountToIncrease,
73 | uint256 oldRateLimit,
74 | uint256 newRateLimit
75 | );
76 |
77 | /**********************************************************************************************/
78 | /*** State variables ***/
79 | /**********************************************************************************************/
80 |
81 | /**
82 | * @dev Returns the controller identifier as a bytes32 value.
83 | * @return The controller identifier.
84 | */
85 | function CONTROLLER() external view returns (bytes32);
86 |
87 | /**********************************************************************************************/
88 | /*** Admin functions ***/
89 | /**********************************************************************************************/
90 |
91 | /**
92 | * @dev Sets rate limit data for a specific key.
93 | * @param key The identifier for the rate limit.
94 | * @param maxAmount The maximum allowed amount for the rate limit.
95 | * @param slope The slope value used in the rate limit calculation.
96 | * @param lastAmount The amount left available at the last update.
97 | * @param lastUpdated The timestamp when the rate limit was last updated.
98 | */
99 | function setRateLimitData(
100 | bytes32 key,
101 | uint256 maxAmount,
102 | uint256 slope,
103 | uint256 lastAmount,
104 | uint256 lastUpdated
105 | ) external;
106 |
107 | /**
108 | * @dev Sets rate limit data for a specific key with
109 | * `lastAmount == maxAmount` and `lastUpdated == block.timestamp`.
110 | * @param key The identifier for the rate limit.
111 | * @param maxAmount The maximum allowed amount for the rate limit.
112 | * @param slope The slope value used in the rate limit calculation.
113 | */
114 | function setRateLimitData(bytes32 key, uint256 maxAmount, uint256 slope) external;
115 |
116 | /**
117 | * @dev Sets an unlimited rate limit.
118 | * @param key The identifier for the rate limit.
119 | */
120 | function setUnlimitedRateLimitData(bytes32 key) external;
121 |
122 | /**********************************************************************************************/
123 | /*** Getter Functions ***/
124 | /**********************************************************************************************/
125 |
126 | /**
127 | * @dev Retrieves the RateLimitData struct associated with a specific key.
128 | * @param key The identifier for the rate limit.
129 | * @return The data associated with the rate limit.
130 | */
131 | function getRateLimitData(bytes32 key) external view returns (RateLimitData memory);
132 |
133 | /**
134 | * @dev Retrieves the current rate limit for a specific key.
135 | * @param key The identifier for the rate limit.
136 | * @return The current rate limit value for the given key.
137 | */
138 | function getCurrentRateLimit(bytes32 key) external view returns (uint256);
139 |
140 | /**********************************************************************************************/
141 | /*** Controller functions ***/
142 | /**********************************************************************************************/
143 |
144 | /**
145 | * @dev Triggers the rate limit for a specific key and reduces the available
146 | * amount by the provided value.
147 | * @param key The identifier for the rate limit.
148 | * @param amountToDecrease The amount to decrease from the current rate limit.
149 | * @return newLimit The updated rate limit after the deduction.
150 | */
151 | function triggerRateLimitDecrease(bytes32 key, uint256 amountToDecrease)
152 | external returns (uint256 newLimit);
153 |
154 | /**
155 | * @dev Increases the rate limit for a given key up to the maxAmount. Does not revert if
156 | * the new rate limit exceeds the maxAmount.
157 | * @param key The identifier for the rate limit.
158 | * @param amountToIncrease The amount to increase from the current rate limit.
159 | * @return newLimit The updated rate limit after the addition.
160 | */
161 | function triggerRateLimitIncrease(bytes32 key, uint256 amountToIncrease)
162 | external returns (uint256 newLimit);
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/src/libraries/CCTPLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { IERC20 } from "forge-std/interfaces/IERC20.sol";
5 |
6 | import { IRateLimits } from "../interfaces/IRateLimits.sol";
7 | import { IALMProxy } from "../interfaces/IALMProxy.sol";
8 | import { ICCTPLike } from "../interfaces/CCTPInterfaces.sol";
9 |
10 | import { RateLimitHelpers } from "../RateLimitHelpers.sol";
11 |
12 | library CCTPLib {
13 |
14 | /**********************************************************************************************/
15 | /*** Structs ***/
16 | /**********************************************************************************************/
17 |
18 | struct TransferUSDCToCCTPParams {
19 | IALMProxy proxy;
20 | IRateLimits rateLimits;
21 | ICCTPLike cctp;
22 | IERC20 usdc;
23 | bytes32 domainRateLimitId;
24 | bytes32 cctpRateLimitId;
25 | bytes32 mintRecipient;
26 | uint32 destinationDomain;
27 | uint256 usdcAmount;
28 | }
29 |
30 | /**********************************************************************************************/
31 | /*** Events ***/
32 | /**********************************************************************************************/
33 |
34 | // NOTE: This is used to track individual transfers for offchain processing of CCTP transactions
35 | event CCTPTransferInitiated(
36 | uint64 indexed nonce,
37 | uint32 indexed destinationDomain,
38 | bytes32 indexed mintRecipient,
39 | uint256 usdcAmount
40 | );
41 |
42 | /**********************************************************************************************/
43 | /*** External functions ***/
44 | /**********************************************************************************************/
45 |
46 | function transferUSDCToCCTP(TransferUSDCToCCTPParams calldata params) external {
47 | _rateLimited(params.rateLimits, params.cctpRateLimitId, params.usdcAmount);
48 | _rateLimited(
49 | params.rateLimits,
50 | RateLimitHelpers.makeDomainKey(params.domainRateLimitId, params.destinationDomain),
51 | params.usdcAmount
52 | );
53 |
54 | require(params.mintRecipient != 0, "MainnetController/domain-not-configured");
55 |
56 | // Approve USDC to CCTP from the proxy (assumes the proxy has enough USDC)
57 | _approve(params.proxy, address(params.usdc), address(params.cctp), params.usdcAmount);
58 |
59 | // If amount is larger than limit it must be split into multiple calls
60 | uint256 burnLimit = params.cctp.localMinter().burnLimitsPerMessage(address(params.usdc));
61 |
62 | // This variable will get reduced in the loop below
63 | uint256 usdcAmountTemp = params.usdcAmount;
64 |
65 | while (usdcAmountTemp > burnLimit) {
66 | _initiateCCTPTransfer(
67 | params.proxy,
68 | params.cctp,
69 | params.usdc,
70 | burnLimit,
71 | params.mintRecipient,
72 | params.destinationDomain
73 | );
74 | usdcAmountTemp -= burnLimit;
75 | }
76 |
77 | // Send remaining amount (if any)
78 | if (usdcAmountTemp > 0) {
79 | _initiateCCTPTransfer(
80 | params.proxy,
81 | params.cctp,
82 | params.usdc,
83 | usdcAmountTemp,
84 | params.mintRecipient,
85 | params.destinationDomain
86 | );
87 | }
88 | }
89 |
90 | /**********************************************************************************************/
91 | /*** Relayer helper functions ***/
92 | /**********************************************************************************************/
93 |
94 | // NOTE: As USDC is the only asset transferred using CCTP, _forceApprove logic is unnecessary.
95 | function _approve(
96 | IALMProxy proxy,
97 | address token,
98 | address spender,
99 | uint256 amount
100 | )
101 | internal
102 | {
103 | proxy.doCall(token, abi.encodeCall(IERC20.approve, (spender, amount)));
104 | }
105 |
106 | function _initiateCCTPTransfer(
107 | IALMProxy proxy,
108 | ICCTPLike cctp,
109 | IERC20 usdc,
110 | uint256 usdcAmount,
111 | bytes32 mintRecipient,
112 | uint32 destinationDomain
113 | )
114 | internal
115 | {
116 | uint64 nonce = abi.decode(
117 | proxy.doCall(
118 | address(cctp),
119 | abi.encodeCall(
120 | cctp.depositForBurn,
121 | (
122 | usdcAmount,
123 | destinationDomain,
124 | mintRecipient,
125 | address(usdc)
126 | )
127 | )
128 | ),
129 | (uint64)
130 | );
131 |
132 | emit CCTPTransferInitiated(nonce, destinationDomain, mintRecipient, usdcAmount);
133 | }
134 |
135 | /**********************************************************************************************/
136 | /*** Rate Limit helper functions ***/
137 | /**********************************************************************************************/
138 |
139 | function _rateLimited(IRateLimits rateLimits, bytes32 key, uint256 amount) internal {
140 | rateLimits.triggerRateLimitDecrease(key, amount);
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/src/libraries/PSMLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { IERC20 } from "forge-std/interfaces/IERC20.sol";
5 |
6 | import { IRateLimits } from "../interfaces/IRateLimits.sol";
7 | import { IALMProxy } from "../interfaces/IALMProxy.sol";
8 |
9 | import { RateLimitHelpers } from "../RateLimitHelpers.sol";
10 |
11 | interface IDaiUsdsLike {
12 | function dai() external view returns (address);
13 | function daiToUsds(address usr, uint256 wad) external;
14 | function usdsToDai(address usr, uint256 wad) external;
15 | }
16 |
17 | interface IPSMLike {
18 | function buyGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
19 | function fill() external returns (uint256 wad);
20 | function gem() external view returns (address);
21 | function sellGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
22 | function to18ConversionFactor() external view returns (uint256);
23 | }
24 |
25 | library PSMLib {
26 |
27 | /**********************************************************************************************/
28 | /*** Structs ***/
29 | /**********************************************************************************************/
30 |
31 | struct SwapUSDSToUSDCParams {
32 | IALMProxy proxy;
33 | IRateLimits rateLimits;
34 | IDaiUsdsLike daiUsds;
35 | IPSMLike psm;
36 | IERC20 usds;
37 | IERC20 dai;
38 | bytes32 rateLimitId;
39 | uint256 usdcAmount;
40 | uint256 psmTo18ConversionFactor;
41 | }
42 |
43 | struct SwapUSDCToUSDSParams {
44 | IALMProxy proxy;
45 | IRateLimits rateLimits;
46 | IDaiUsdsLike daiUsds;
47 | IPSMLike psm;
48 | IERC20 dai;
49 | IERC20 usdc;
50 | bytes32 rateLimitId;
51 | uint256 usdcAmount;
52 | uint256 psmTo18ConversionFactor;
53 | }
54 |
55 | /**********************************************************************************************/
56 | /*** External functions ***/
57 | /**********************************************************************************************/
58 |
59 | function swapUSDSToUSDC(SwapUSDSToUSDCParams calldata params) external {
60 | _rateLimited(params.rateLimitId, params.usdcAmount, params.rateLimits);
61 |
62 | uint256 usdsAmount = params.usdcAmount * params.psmTo18ConversionFactor;
63 |
64 | // Approve USDS to DaiUsds migrator from the proxy (assumes the proxy has enough USDS)
65 | _approve(params.proxy, address(params.usds), address(params.daiUsds), usdsAmount);
66 |
67 | // Swap USDS to DAI 1:1
68 | params.proxy.doCall(
69 | address(params.daiUsds),
70 | abi.encodeCall(params.daiUsds.usdsToDai, (address(params.proxy), usdsAmount))
71 | );
72 |
73 | // Approve DAI to PSM from the proxy because conversion from USDS to DAI was 1:1
74 | _approve(params.proxy, address(params.dai), address(params.psm), usdsAmount);
75 |
76 | // Swap DAI to USDC through the PSM
77 | params.proxy.doCall(
78 | address(params.psm),
79 | abi.encodeCall(params.psm.buyGemNoFee, (address(params.proxy), params.usdcAmount))
80 | );
81 | }
82 |
83 | function swapUSDCToUSDS(SwapUSDCToUSDSParams calldata params) external {
84 | _cancelRateLimit(params.rateLimitId, params.usdcAmount, params.rateLimits);
85 |
86 | // Approve USDC to PSM from the proxy (assumes the proxy has enough USDC)
87 | _approve(params.proxy, address(params.usdc), address(params.psm), params.usdcAmount);
88 |
89 | // Max USDC that can be swapped to DAI in one call
90 | uint256 limit = params.dai.balanceOf(address(params.psm)) / params.psmTo18ConversionFactor;
91 |
92 | if (params.usdcAmount <= limit) {
93 | _swapUSDCToDAI(params.proxy, params.psm, params.usdcAmount);
94 | } else {
95 | uint256 remainingUsdcToSwap = params.usdcAmount;
96 |
97 | // Refill the PSM with DAI as many times as needed to get to the full `usdcAmount`.
98 | // If the PSM cannot be filled with the full amount, psm.fill() will revert
99 | // with `DssLitePsm/nothing-to-fill` since rush() will return 0.
100 | // This is desired behavior because this function should only succeed if the full
101 | // `usdcAmount` can be swapped.
102 | while (remainingUsdcToSwap > 0) {
103 | params.psm.fill();
104 |
105 | limit = params.dai.balanceOf(address(params.psm)) / params.psmTo18ConversionFactor;
106 |
107 | uint256 swapAmount = remainingUsdcToSwap < limit ? remainingUsdcToSwap : limit;
108 |
109 | _swapUSDCToDAI(params.proxy, params.psm, swapAmount);
110 |
111 | remainingUsdcToSwap -= swapAmount;
112 | }
113 | }
114 |
115 | uint256 daiAmount = params.usdcAmount * params.psmTo18ConversionFactor;
116 |
117 | // Approve DAI to DaiUsds migrator from the proxy (assumes the proxy has enough DAI)
118 | _approve(params.proxy, address(params.dai), address(params.daiUsds), daiAmount);
119 |
120 | // Swap DAI to USDS 1:1
121 | params.proxy.doCall(
122 | address(params.daiUsds),
123 | abi.encodeCall(params.daiUsds.daiToUsds, (address(params.proxy), daiAmount))
124 | );
125 | }
126 |
127 | /**********************************************************************************************/
128 | /*** Helper functions ***/
129 | /**********************************************************************************************/
130 |
131 | // NOTE: As swaps are only done between USDC and USDS and vice versa, using `_forceApprove`
132 | // is unnecessary.
133 | function _approve(
134 | IALMProxy proxy,
135 | address token,
136 | address spender,
137 | uint256 amount
138 | )
139 | internal
140 | {
141 | proxy.doCall(token, abi.encodeCall(IERC20.approve, (spender, amount)));
142 | }
143 |
144 | function _swapUSDCToDAI(IALMProxy proxy, IPSMLike psm, uint256 usdcAmount) internal {
145 | // Swap USDC to DAI through the PSM (1:1 since sellGemNoFee is used)
146 | proxy.doCall(
147 | address(psm),
148 | abi.encodeCall(psm.sellGemNoFee, (address(proxy), usdcAmount))
149 | );
150 | }
151 |
152 | /**********************************************************************************************/
153 | /*** Rate Limit helper functions ***/
154 | /**********************************************************************************************/
155 |
156 | function _rateLimited(bytes32 key, uint256 amount, IRateLimits rateLimits) internal {
157 | rateLimits.triggerRateLimitDecrease(key, amount);
158 | }
159 |
160 | function _cancelRateLimit(bytes32 key, uint256 amount, IRateLimits rateLimits) internal {
161 | rateLimits.triggerRateLimitIncrease(key, amount);
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/test/base-fork/Aave.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol";
5 |
6 | import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol";
7 |
8 | import "./ForkTestBase.t.sol";
9 |
10 | contract AaveV3BaseMarketTestBase is ForkTestBase {
11 |
12 | address constant ATOKEN_USDC = 0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB;
13 | address constant POOL = 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5;
14 |
15 | IAToken ausdc = IAToken(ATOKEN_USDC);
16 |
17 | uint256 startingAUSDCBalance;
18 |
19 | function setUp() public override {
20 | super.setUp();
21 |
22 | vm.startPrank(Base.SPARK_EXECUTOR);
23 |
24 | // NOTE: Hit SUPPLY_CAP_EXCEEDED when using 25m
25 | rateLimits.setRateLimitData(
26 | RateLimitHelpers.makeAssetKey(
27 | foreignController.LIMIT_AAVE_DEPOSIT(),
28 | ATOKEN_USDC
29 | ),
30 | 1_000_000e6,
31 | uint256(1_000_000e6) / 1 days
32 | );
33 | rateLimits.setRateLimitData(
34 | RateLimitHelpers.makeAssetKey(
35 | foreignController.LIMIT_AAVE_WITHDRAW(),
36 | ATOKEN_USDC
37 | ),
38 | 1_000_000e6,
39 | uint256(5_000_000e6) / 1 days
40 | );
41 |
42 | vm.stopPrank();
43 |
44 | startingAUSDCBalance = usdcBase.balanceOf(address(ausdc));
45 | }
46 |
47 | function _getBlock() internal pure override returns (uint256) {
48 | return 22841965; // November 24, 2024
49 | }
50 |
51 | }
52 |
53 | contract AaveV3BaseMarketDepositFailureTests is AaveV3BaseMarketTestBase {
54 |
55 | function test_depositAave_notRelayer() external {
56 | vm.expectRevert(abi.encodeWithSignature(
57 | "AccessControlUnauthorizedAccount(address,bytes32)",
58 | address(this),
59 | RELAYER
60 | ));
61 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e18);
62 | }
63 |
64 | function test_depositAave_zeroMaxAmount() external {
65 | vm.prank(relayer);
66 | vm.expectRevert("RateLimits/zero-maxAmount");
67 | foreignController.depositAave(makeAddr("fake-token"), 1e18);
68 | }
69 |
70 | function test_depositAave_usdcRateLimitedBoundary() external {
71 | deal(Base.USDC, address(almProxy), 1_000_000e6 + 1);
72 |
73 | vm.expectRevert("RateLimits/rate-limit-exceeded");
74 | vm.startPrank(relayer);
75 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6 + 1);
76 |
77 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6);
78 | }
79 |
80 | }
81 |
82 | contract AaveV3BaseMarketDepositSuccessTests is AaveV3BaseMarketTestBase {
83 |
84 | function test_depositAave_usdc() public {
85 | deal(Base.USDC, address(almProxy), 1_000_000e6);
86 |
87 | assertEq(usdcBase.allowance(address(almProxy), POOL), 0);
88 |
89 | assertEq(ausdc.balanceOf(address(almProxy)), 0);
90 | assertEq(usdcBase.balanceOf(address(almProxy)), 1_000_000e6);
91 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance);
92 |
93 | vm.prank(relayer);
94 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6);
95 |
96 | assertEq(usdcBase.allowance(address(almProxy), POOL), 0);
97 |
98 | assertEq(ausdc.balanceOf(address(almProxy)), 1_000_000e6);
99 | assertEq(usdcBase.balanceOf(address(almProxy)), 0);
100 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6);
101 | }
102 |
103 | }
104 |
105 | contract AaveV3BaseMarketWithdrawFailureTests is AaveV3BaseMarketTestBase {
106 |
107 | function test_withdrawAave_notRelayer() external {
108 | vm.expectRevert(abi.encodeWithSignature(
109 | "AccessControlUnauthorizedAccount(address,bytes32)",
110 | address(this),
111 | RELAYER
112 | ));
113 | foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e18);
114 | }
115 |
116 | function test_withdrawAave_zeroMaxAmount() external {
117 | // Longer setup because rate limit revert is at the end of the function
118 | vm.startPrank(Base.SPARK_EXECUTOR);
119 | rateLimits.setRateLimitData(
120 | RateLimitHelpers.makeAssetKey(
121 | foreignController.LIMIT_AAVE_WITHDRAW(),
122 | ATOKEN_USDC
123 | ),
124 | 0,
125 | 0
126 | );
127 | vm.stopPrank();
128 |
129 | deal(Base.USDC, address(almProxy), 1_000_000e6);
130 |
131 | vm.startPrank(relayer);
132 |
133 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6);
134 |
135 | vm.expectRevert("RateLimits/zero-maxAmount");
136 | foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6);
137 | }
138 |
139 | function test_withdrawAave_usdcRateLimitedBoundary() external {
140 | deal(Base.USDC, address(almProxy), 2_000_000e6);
141 |
142 | // Warp to get past rate limit
143 | vm.startPrank(relayer);
144 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6);
145 | skip(1 days);
146 | foreignController.depositAave(ATOKEN_USDC, 100_000e6);
147 |
148 | vm.expectRevert("RateLimits/rate-limit-exceeded");
149 | foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6 + 1);
150 |
151 | foreignController.withdrawAave(ATOKEN_USDC, 1_000_000e6);
152 | }
153 |
154 | }
155 |
156 | contract AaveV3BaseMarketWithdrawSuccessTests is AaveV3BaseMarketTestBase {
157 |
158 | function test_withdrawAave_usdc() public {
159 | bytes32 key = RateLimitHelpers.makeAssetKey(
160 | foreignController.LIMIT_AAVE_WITHDRAW(),
161 | ATOKEN_USDC
162 | );
163 |
164 | // NOTE: Using lower amount to not hit rate limit
165 | deal(Base.USDC, address(almProxy), 500_000e6);
166 | vm.prank(relayer);
167 | foreignController.depositAave(ATOKEN_USDC, 500_000e6);
168 |
169 | skip(1 days);
170 |
171 | uint256 fullBalance = ausdc.balanceOf(address(almProxy));
172 |
173 | assertGe(fullBalance, 500_000e6);
174 |
175 | assertEq(ausdc.balanceOf(address(almProxy)), fullBalance);
176 | assertEq(usdcBase.balanceOf(address(almProxy)), 0);
177 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6);
178 |
179 | assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6);
180 |
181 | // Partial withdraw
182 | vm.prank(relayer);
183 | assertEq(foreignController.withdrawAave(ATOKEN_USDC, 400_000e6), 400_000e6);
184 |
185 | assertEq(ausdc.balanceOf(address(almProxy)), fullBalance - 400_000e6 - 1);
186 | assertEq(usdcBase.balanceOf(address(almProxy)), 400_000e6);
187 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 100_000e6); // 500k - 400k
188 |
189 | assertEq(rateLimits.getCurrentRateLimit(key), 600_000e6);
190 |
191 | // Withdraw all
192 | vm.prank(relayer);
193 | assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance - 400_000e6 - 1);
194 |
195 | assertEq(ausdc.balanceOf(address(almProxy)), 0);
196 | assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance - 1);
197 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 500_000e6 - fullBalance + 1);
198 |
199 | assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6 - fullBalance + 1);
200 |
201 | // Interest accrued was withdrawn, reducing cash balance
202 | assertLe(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance);
203 | }
204 |
205 | function test_withdrawAave_usdc_unlimitedRateLimit() public {
206 | bytes32 key = RateLimitHelpers.makeAssetKey(
207 | foreignController.LIMIT_AAVE_WITHDRAW(),
208 | ATOKEN_USDC
209 | );
210 | vm.prank(Base.SPARK_EXECUTOR);
211 | rateLimits.setUnlimitedRateLimitData(key);
212 |
213 | deal(Base.USDC, address(almProxy), 1_000_000e6);
214 | vm.prank(relayer);
215 | foreignController.depositAave(ATOKEN_USDC, 1_000_000e6);
216 |
217 | skip(1 days);
218 |
219 | uint256 fullBalance = ausdc.balanceOf(address(almProxy));
220 |
221 | assertGe(fullBalance, 1_000_000e6);
222 |
223 | assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max);
224 |
225 | assertEq(ausdc.balanceOf(address(almProxy)), fullBalance);
226 | assertEq(usdcBase.balanceOf(address(almProxy)), 0);
227 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6);
228 |
229 | // Partial withdraw
230 | vm.prank(relayer);
231 | assertEq(foreignController.withdrawAave(ATOKEN_USDC, type(uint256).max), fullBalance);
232 |
233 | assertEq(rateLimits.getCurrentRateLimit(key), type(uint256).max); // No change
234 |
235 | assertEq(ausdc.balanceOf(address(almProxy)), 0);
236 | assertEq(usdcBase.balanceOf(address(almProxy)), fullBalance);
237 | assertEq(usdcBase.balanceOf(address(ausdc)), startingAUSDCBalance + 1_000_000e6 - fullBalance);
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/test/base-fork/Deploy.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { ControllerInstance } from "../../deploy/ControllerInstance.sol";
5 | import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol";
6 |
7 | import "./ForkTestBase.t.sol";
8 |
9 | contract ForeignControllerDeploySuccessTests is ForkTestBase {
10 |
11 | function test_deployFull() external {
12 | // Perform new deployments against existing fork environment
13 |
14 | ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({
15 | admin : Base.SPARK_EXECUTOR,
16 | psm : Base.PSM3,
17 | usdc : Base.USDC,
18 | cctp : Base.CCTP_TOKEN_MESSENGER
19 | });
20 |
21 | ALMProxy newAlmProxy = ALMProxy(payable(controllerInst.almProxy));
22 | ForeignController newController = ForeignController(controllerInst.controller);
23 | RateLimits newRateLimits = RateLimits(controllerInst.rateLimits);
24 |
25 | assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true);
26 | assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin
27 |
28 | assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true);
29 | assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin
30 |
31 | _assertControllerInitState(newController, address(newAlmProxy), address(newRateLimits));
32 | }
33 |
34 | function test_deployController() external {
35 | // Perform new deployments against existing fork environment
36 |
37 | ForeignController newController = ForeignController(ForeignControllerDeploy.deployController({
38 | admin : Base.SPARK_EXECUTOR,
39 | almProxy : address(almProxy),
40 | rateLimits : address(rateLimits),
41 | psm : Base.PSM3,
42 | usdc : Base.USDC,
43 | cctp : Base.CCTP_TOKEN_MESSENGER
44 | }));
45 |
46 | _assertControllerInitState(newController, address(almProxy), address(rateLimits));
47 | }
48 |
49 | function _assertControllerInitState(ForeignController controller, address almProxy, address rateLimits) internal view {
50 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, Base.SPARK_EXECUTOR), true);
51 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin
52 |
53 | assertEq(address(controller.proxy()), almProxy);
54 | assertEq(address(controller.rateLimits()), rateLimits);
55 | assertEq(address(controller.psm()), Base.PSM3);
56 | assertEq(address(controller.usdc()), Base.USDC);
57 | assertEq(address(controller.cctp()), Base.CCTP_TOKEN_MESSENGER);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/test/base-fork/ForkTestBase.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import "forge-std/Test.sol";
5 |
6 | import { IERC20 } from "forge-std/interfaces/IERC20.sol";
7 |
8 | import { ERC20Mock } from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol";
9 |
10 | import { Base } from "spark-address-registry/Base.sol";
11 |
12 | import { PSM3Deploy } from "spark-psm/deploy/PSM3Deploy.sol";
13 | import { IPSM3 } from "spark-psm/src/PSM3.sol";
14 |
15 | import { CCTPForwarder } from "xchain-helpers/forwarders/CCTPForwarder.sol";
16 |
17 | import { ForeignControllerDeploy } from "../../deploy/ControllerDeploy.sol";
18 | import { ControllerInstance } from "../../deploy/ControllerInstance.sol";
19 |
20 | import { ForeignControllerInit as Init } from "../../deploy/ForeignControllerInit.sol";
21 |
22 | import { ALMProxy } from "../../src/ALMProxy.sol";
23 | import { ForeignController } from "../../src/ForeignController.sol";
24 | import { RateLimits } from "../../src/RateLimits.sol";
25 |
26 | import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol";
27 |
28 | contract ForkTestBase is Test {
29 |
30 | // TODO: Refactor to use live addresses
31 |
32 | /**********************************************************************************************/
33 | /*** Constants/state variables ***/
34 | /**********************************************************************************************/
35 |
36 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
37 |
38 | bytes32 CONTROLLER;
39 | bytes32 FREEZER;
40 | bytes32 RELAYER;
41 |
42 | address freezer = Base.ALM_FREEZER;
43 | address relayer = Base.ALM_RELAYER;
44 |
45 | address pocket = makeAddr("pocket");
46 |
47 | /**********************************************************************************************/
48 | /*** Base addresses ***/
49 | /**********************************************************************************************/
50 |
51 | address constant SPARK_EXECUTOR = Base.SPARK_EXECUTOR;
52 | address constant CCTP_MESSENGER_BASE = Base.CCTP_TOKEN_MESSENGER;
53 | address constant USDC_BASE = Base.USDC;
54 | address constant SSR_ORACLE = Base.SSR_AUTH_ORACLE;
55 |
56 | /**********************************************************************************************/
57 | /*** ALM system deployments ***/
58 | /**********************************************************************************************/
59 |
60 | ALMProxy almProxy;
61 | RateLimits rateLimits;
62 | ForeignController foreignController;
63 |
64 | /**********************************************************************************************/
65 | /*** Casted addresses for testing ***/
66 | /**********************************************************************************************/
67 |
68 | IERC20 usdsBase;
69 | IERC20 susdsBase;
70 | IERC20 usdcBase;
71 |
72 | IPSM3 psmBase;
73 |
74 | /**********************************************************************************************/
75 | /*** Test setup ***/
76 | /**********************************************************************************************/
77 |
78 | function setUp() public virtual {
79 | /*** Step 1: Set up environment, deploy mock addresses ***/
80 |
81 | vm.createSelectFork(getChain('base').rpcUrl, _getBlock());
82 |
83 | usdsBase = IERC20(address(new ERC20Mock()));
84 | susdsBase = IERC20(address(new ERC20Mock()));
85 | usdcBase = IERC20(USDC_BASE);
86 |
87 | /*** Step 2: Deploy and configure PSM with a pocket ***/
88 |
89 | deal(address(usdsBase), address(this), 1e18); // For seeding PSM during deployment
90 |
91 | psmBase = IPSM3(PSM3Deploy.deploy(
92 | SPARK_EXECUTOR, USDC_BASE, address(usdsBase), address(susdsBase), SSR_ORACLE
93 | ));
94 |
95 | vm.prank(SPARK_EXECUTOR);
96 | psmBase.setPocket(pocket);
97 |
98 | vm.prank(pocket);
99 | usdcBase.approve(address(psmBase), type(uint256).max);
100 |
101 | /*** Step 3: Deploy ALM system ***/
102 |
103 | ControllerInstance memory controllerInst = ForeignControllerDeploy.deployFull({
104 | admin : SPARK_EXECUTOR,
105 | psm : address(psmBase),
106 | usdc : USDC_BASE,
107 | cctp : CCTP_MESSENGER_BASE
108 | });
109 |
110 | almProxy = ALMProxy(payable(controllerInst.almProxy));
111 | rateLimits = RateLimits(controllerInst.rateLimits);
112 | foreignController = ForeignController(controllerInst.controller);
113 |
114 | CONTROLLER = almProxy.CONTROLLER();
115 | FREEZER = foreignController.FREEZER();
116 | RELAYER = foreignController.RELAYER();
117 |
118 | /*** Step 3: Configure ALM system through Spark governance (Spark spell payload) ***/
119 |
120 | address[] memory relayers = new address[](1);
121 | relayers[0] = relayer;
122 |
123 | Init.ConfigAddressParams memory configAddresses = Init.ConfigAddressParams({
124 | freezer : freezer,
125 | relayers : relayers,
126 | oldController : address(0)
127 | });
128 |
129 | Init.CheckAddressParams memory checkAddresses = Init.CheckAddressParams({
130 | admin : Base.SPARK_EXECUTOR,
131 | psm : address(psmBase),
132 | cctp : Base.CCTP_TOKEN_MESSENGER,
133 | usdc : address(usdcBase),
134 | susds : address(susdsBase),
135 | usds : address(usdsBase)
136 | });
137 |
138 | Init.MintRecipient[] memory mintRecipients = new Init.MintRecipient[](1);
139 |
140 | mintRecipients[0] = Init.MintRecipient({
141 | domain : CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM,
142 | mintRecipient : bytes32(uint256(uint160(makeAddr("ethereumAlmProxy"))))
143 | });
144 |
145 | vm.startPrank(SPARK_EXECUTOR);
146 |
147 | Init.initAlmSystem(
148 | controllerInst,
149 | configAddresses,
150 | checkAddresses,
151 | mintRecipients
152 | );
153 |
154 | uint256 usdcMaxAmount = 5_000_000e6;
155 | uint256 usdcSlope = uint256(1_000_000e6) / 4 hours;
156 | uint256 usdsMaxAmount = 5_000_000e18;
157 | uint256 usdsSlope = uint256(1_000_000e18) / 4 hours;
158 |
159 | bytes32 depositKey = foreignController.LIMIT_PSM_DEPOSIT();
160 | bytes32 withdrawKey = foreignController.LIMIT_PSM_WITHDRAW();
161 |
162 | bytes32 domainKeyEthereum = RateLimitHelpers.makeDomainKey(
163 | foreignController.LIMIT_USDC_TO_DOMAIN(),
164 | CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM
165 | );
166 |
167 | // NOTE: Using minimal config for test base setup
168 | rateLimits.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(usdcBase)), usdcMaxAmount, usdcSlope);
169 | rateLimits.setRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(usdcBase)), usdcMaxAmount, usdcSlope);
170 | rateLimits.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(usdsBase)), usdsMaxAmount, usdsSlope);
171 | rateLimits.setRateLimitData(RateLimitHelpers.makeAssetKey(depositKey, address(susdsBase)), usdsMaxAmount, usdsSlope);
172 | rateLimits.setRateLimitData(foreignController.LIMIT_USDC_TO_CCTP(), usdcMaxAmount, usdcSlope);
173 | rateLimits.setRateLimitData(domainKeyEthereum, usdcMaxAmount, usdcSlope);
174 |
175 | rateLimits.setUnlimitedRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(usdsBase)));
176 | rateLimits.setUnlimitedRateLimitData(RateLimitHelpers.makeAssetKey(withdrawKey, address(susdsBase)));
177 |
178 | vm.stopPrank();
179 | }
180 |
181 | // Default configuration for the fork, can be overridden in inheriting tests
182 | function _getBlock() internal virtual pure returns (uint256) {
183 | return 20782500; // October 8, 2024
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/test/base-fork/MorphoAllocations.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { IERC4626 } from "forge-std/interfaces/IERC4626.sol";
5 |
6 | import { IMetaMorpho, Id, MarketAllocation } from "metamorpho/interfaces/IMetaMorpho.sol";
7 |
8 | import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol";
9 | import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol";
10 |
11 | import { RateLimitHelpers } from "../../src/RateLimitHelpers.sol";
12 |
13 | import "./ForkTestBase.t.sol";
14 |
15 | contract MorphoTestBase is ForkTestBase {
16 |
17 | address constant CBBTC = 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf;
18 | address constant CBBTC_USDC_ORACLE = 0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9;
19 | address constant MORPHO_DEFAULT_IRM = 0x46415998764C29aB2a25CbeA6254146D50D22687;
20 |
21 | IMetaMorpho morphoVault = IMetaMorpho(Base.MORPHO_VAULT_SUSDC);
22 | IMorpho morpho = IMorpho(Base.MORPHO);
23 |
24 | MarketParams usdcIdle = MarketParams({
25 | loanToken : Base.USDC,
26 | collateralToken : address(0),
27 | oracle : address(0),
28 | irm : address(0),
29 | lltv : 0
30 | });
31 | MarketParams usdcCBBTC = MarketParams({
32 | loanToken : Base.USDC,
33 | collateralToken : CBBTC,
34 | oracle : CBBTC_USDC_ORACLE,
35 | irm : MORPHO_DEFAULT_IRM,
36 | lltv : 0.86e18
37 | });
38 |
39 | function setUp() public override {
40 | super.setUp();
41 |
42 | // Spell onboarding
43 | vm.startPrank(Base.SPARK_EXECUTOR);
44 | morphoVault.setIsAllocator(address(almProxy), true);
45 | morphoVault.setIsAllocator(address(relayer), false);
46 | rateLimits.setRateLimitData(
47 | RateLimitHelpers.makeAssetKey(
48 | foreignController.LIMIT_4626_DEPOSIT(),
49 | address(morphoVault)
50 | ),
51 | 1_000_000e6,
52 | uint256(1_000_000e6) / 1 days
53 | );
54 | vm.stopPrank();
55 | }
56 |
57 | function _getBlock() internal pure override returns (uint256) {
58 | return 25340000; // Jan 21, 2024
59 | }
60 |
61 | function positionShares(MarketParams memory marketParams) internal view returns (uint256) {
62 | return morpho.position(MarketParamsLib.id(marketParams), address(morphoVault)).supplyShares;
63 | }
64 |
65 | function positionAssets(MarketParams memory marketParams) internal view returns (uint256) {
66 | return positionShares(marketParams)
67 | * marketAssets(marketParams)
68 | / morpho.market(MarketParamsLib.id(marketParams)).totalSupplyShares;
69 | }
70 |
71 | function marketAssets(MarketParams memory marketParams) internal view returns (uint256) {
72 | return morpho.market(MarketParamsLib.id(marketParams)).totalSupplyAssets;
73 | }
74 |
75 | }
76 |
77 | contract MorphoSetSupplyQueueMorphoFailureTests is MorphoTestBase {
78 |
79 | function test_setSupplyQueueMorpho_notRelayer() external {
80 | vm.expectRevert(abi.encodeWithSignature(
81 | "AccessControlUnauthorizedAccount(address,bytes32)",
82 | address(this),
83 | RELAYER
84 | ));
85 | foreignController.setSupplyQueueMorpho(address(morphoVault), new Id[](0));
86 | }
87 |
88 | function test_setSupplyQueueMorpho_invalidVault() external {
89 | vm.prank(relayer);
90 | vm.expectRevert("ForeignController/invalid-action");
91 | foreignController.setSupplyQueueMorpho(makeAddr("fake-vault"), new Id[](0));
92 | }
93 |
94 | }
95 |
96 | contract MorphoSetSupplyQueueMorphoSuccessTests is MorphoTestBase {
97 |
98 | function test_setSupplyQueueMorpho() external {
99 | // Switch order of existing markets
100 | Id[] memory supplyQueueUSDC = new Id[](2);
101 | supplyQueueUSDC[0] = MarketParamsLib.id(usdcIdle);
102 | supplyQueueUSDC[1] = MarketParamsLib.id(usdcCBBTC);
103 |
104 | assertEq(morphoVault.supplyQueueLength(), 2);
105 |
106 | assertEq(Id.unwrap(morphoVault.supplyQueue(0)), Id.unwrap(MarketParamsLib.id(usdcCBBTC)));
107 | assertEq(Id.unwrap(morphoVault.supplyQueue(1)), Id.unwrap(MarketParamsLib.id(usdcIdle)));
108 |
109 | vm.prank(relayer);
110 | foreignController.setSupplyQueueMorpho(address(morphoVault), supplyQueueUSDC);
111 |
112 | assertEq(morphoVault.supplyQueueLength(), 2);
113 |
114 | assertEq(Id.unwrap(morphoVault.supplyQueue(0)), Id.unwrap(MarketParamsLib.id(usdcIdle)));
115 | assertEq(Id.unwrap(morphoVault.supplyQueue(1)), Id.unwrap(MarketParamsLib.id(usdcCBBTC)));
116 | }
117 |
118 | }
119 |
120 | contract MorphoUpdateWithdrawQueueMorphoFailureTests is MorphoTestBase {
121 |
122 | function test_updateWithdrawQueueMorpho_notRelayer() external {
123 | vm.expectRevert(abi.encodeWithSignature(
124 | "AccessControlUnauthorizedAccount(address,bytes32)",
125 | address(this),
126 | RELAYER
127 | ));
128 | foreignController.updateWithdrawQueueMorpho(address(morphoVault), new uint256[](0));
129 | }
130 |
131 | function test_updateWithdrawQueueMorpho_invalidVault() external {
132 | vm.prank(relayer);
133 | vm.expectRevert("ForeignController/invalid-action");
134 | foreignController.updateWithdrawQueueMorpho(makeAddr("fake-vault"), new uint256[](0));
135 | }
136 |
137 | }
138 |
139 | contract MorphoUpdateWithdrawQueueMorphoSuccessTests is MorphoTestBase {
140 |
141 | function test_updateWithdrawQueueMorpho() external {
142 | // Switch order of existing markets
143 | uint256[] memory withdrawQueueUsdc = new uint256[](2);
144 | withdrawQueueUsdc[0] = 1;
145 | withdrawQueueUsdc[1] = 0;
146 |
147 | assertEq(morphoVault.withdrawQueueLength(), 2);
148 |
149 | assertEq(Id.unwrap(morphoVault.withdrawQueue(0)), Id.unwrap(MarketParamsLib.id(usdcIdle)));
150 | assertEq(Id.unwrap(morphoVault.withdrawQueue(1)), Id.unwrap(MarketParamsLib.id(usdcCBBTC)));
151 |
152 | vm.prank(relayer);
153 | foreignController.updateWithdrawQueueMorpho(address(morphoVault), withdrawQueueUsdc);
154 |
155 | assertEq(morphoVault.withdrawQueueLength(), 2);
156 |
157 | assertEq(Id.unwrap(morphoVault.withdrawQueue(0)), Id.unwrap(MarketParamsLib.id(usdcCBBTC)));
158 | assertEq(Id.unwrap(morphoVault.withdrawQueue(1)), Id.unwrap(MarketParamsLib.id(usdcIdle)));
159 | }
160 |
161 | }
162 |
163 | contract MorphoReallocateMorphoFailureTests is MorphoTestBase {
164 |
165 | function test_reallocateMorpho_notRelayer() external {
166 | vm.expectRevert(abi.encodeWithSignature(
167 | "AccessControlUnauthorizedAccount(address,bytes32)",
168 | address(this),
169 | RELAYER
170 | ));
171 | foreignController.reallocateMorpho(address(morphoVault), new MarketAllocation[](0));
172 | }
173 |
174 | function test_reallocateMorpho_invalidVault() external {
175 | vm.prank(relayer);
176 | vm.expectRevert("ForeignController/invalid-action");
177 | foreignController.reallocateMorpho(makeAddr("fake-vault"), new MarketAllocation[](0));
178 | }
179 |
180 | }
181 |
182 | contract MorphoReallocateMorphoSuccessTests is MorphoTestBase {
183 |
184 | function test_reallocateMorpho() external {
185 | vm.startPrank(Base.SPARK_EXECUTOR);
186 | rateLimits.setRateLimitData(
187 | RateLimitHelpers.makeAssetKey(
188 | foreignController.LIMIT_4626_DEPOSIT(),
189 | address(morphoVault)
190 | ),
191 | 25_000_000e6,
192 | uint256(5_000_000e6) / 1 days
193 | );
194 | vm.stopPrank();
195 |
196 | // Refresh markets so calculations don't include interest
197 | vm.prank(relayer);
198 | foreignController.depositERC4626(address(morphoVault), 0);
199 |
200 | uint256 positionCBBTC = positionAssets(usdcCBBTC);
201 | uint256 positionIdle = positionAssets(usdcIdle);
202 |
203 | uint256 marketAssetsCBBTC = marketAssets(usdcCBBTC);
204 | uint256 marketAssetsIdle = marketAssets(usdcIdle);
205 |
206 | assertEq(positionCBBTC, 12_128_319.737383e6);
207 | assertEq(positionIdle, 0);
208 |
209 | assertEq(marketAssetsCBBTC, 56_494_357.047568e6);
210 | assertEq(marketAssetsIdle, 5.205521e6);
211 |
212 | deal(Base.USDC, address(almProxy), 1_000_000e6);
213 | vm.prank(relayer);
214 | foreignController.depositERC4626(address(morphoVault), 1_000_000e6);
215 |
216 | assertEq(positionAssets(usdcCBBTC), positionCBBTC + 1_000_000e6);
217 | assertEq(positionAssets(usdcIdle), 0);
218 |
219 | assertEq(marketAssets(usdcCBBTC), marketAssetsCBBTC + 1_000_000e6);
220 | assertEq(marketAssets(usdcIdle), marketAssetsIdle);
221 |
222 | // Move new allocation into idle market
223 | MarketAllocation[] memory reallocations = new MarketAllocation[](2);
224 | reallocations[0] = MarketAllocation({
225 | marketParams : usdcCBBTC,
226 | assets : positionCBBTC
227 | });
228 | reallocations[1] = MarketAllocation({
229 | marketParams : usdcIdle,
230 | assets : 1_000_000e6
231 | });
232 |
233 | vm.prank(relayer);
234 | foreignController.reallocateMorpho(address(morphoVault), reallocations);
235 |
236 | // NOTE: No interest is accrued because deposit coverered all markets and is atomic
237 | assertEq(positionAssets(usdcCBBTC), positionCBBTC);
238 | assertEq(positionAssets(usdcIdle), 1_000_000e6);
239 |
240 | assertEq(marketAssets(usdcCBBTC), marketAssetsCBBTC);
241 | assertEq(marketAssets(usdcIdle), marketAssetsIdle + 1_000_000e6);
242 |
243 | // Move 400k back into CBBTC, note order has changed because of pulling from idle market
244 | reallocations = new MarketAllocation[](2);
245 | reallocations[0] = MarketAllocation({
246 | marketParams : usdcIdle,
247 | assets : 600_000e6
248 | });
249 | reallocations[1] = MarketAllocation({
250 | marketParams : usdcCBBTC,
251 | assets : positionCBBTC + 400_000e6
252 | });
253 |
254 | vm.prank(relayer);
255 | foreignController.reallocateMorpho(address(morphoVault), reallocations);
256 |
257 | assertEq(positionAssets(usdcCBBTC), positionCBBTC + 400_000e6);
258 | assertEq(positionAssets(usdcIdle), 600_000e6);
259 |
260 | assertEq(marketAssets(usdcCBBTC), marketAssetsCBBTC + 400_000e6);
261 | assertEq(marketAssets(usdcIdle), marketAssetsIdle + 600_000e6);
262 | }
263 |
264 | }
265 |
--------------------------------------------------------------------------------
/test/mainnet-fork/4626Calls.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | contract SUSDSTestBase is ForkTestBase {
7 |
8 | uint256 SUSDS_CONVERTED_ASSETS;
9 | uint256 SUSDS_CONVERTED_SHARES;
10 |
11 | uint256 SUSDS_TOTAL_ASSETS;
12 | uint256 SUSDS_TOTAL_SUPPLY;
13 |
14 | uint256 SUSDS_DRIP_AMOUNT;
15 |
16 | function setUp() override public {
17 | super.setUp();
18 |
19 | bytes32 depositKey = RateLimitHelpers.makeAssetKey(mainnetController.LIMIT_4626_DEPOSIT(), Ethereum.SUSDS);
20 | bytes32 withdrawKey = RateLimitHelpers.makeAssetKey(mainnetController.LIMIT_4626_WITHDRAW(), Ethereum.SUSDS);
21 |
22 | vm.startPrank(Ethereum.SPARK_PROXY);
23 | rateLimits.setRateLimitData(depositKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours);
24 | rateLimits.setRateLimitData(withdrawKey, 5_000_000e18, uint256(1_000_000e18) / 4 hours);
25 | vm.stopPrank();
26 |
27 | SUSDS_CONVERTED_ASSETS = susds.convertToAssets(1e18);
28 | SUSDS_CONVERTED_SHARES = susds.convertToShares(1e18);
29 |
30 | SUSDS_TOTAL_ASSETS = susds.totalAssets();
31 | SUSDS_TOTAL_SUPPLY = susds.totalSupply();
32 |
33 | // Setting this value directly because susds.drip() fails in setUp with
34 | // StateChangeDuringStaticCall and it is unclear why, something related to foundry.
35 | SUSDS_DRIP_AMOUNT = 849.454677397481388011e18;
36 |
37 | assertEq(SUSDS_CONVERTED_ASSETS, 1.003430776383974596e18);
38 | assertEq(SUSDS_CONVERTED_SHARES, 0.996580953599671364e18);
39 |
40 | assertEq(SUSDS_TOTAL_ASSETS, 485_597_342.757158870618550128e18);
41 | assertEq(SUSDS_TOTAL_SUPPLY, 483_937_062.910395855928183397e18);
42 | }
43 |
44 | }
45 |
46 | contract MainnetControllerDepositERC4626FailureTests is SUSDSTestBase {
47 |
48 | function test_depositERC4626_notRelayer() external {
49 | vm.expectRevert(abi.encodeWithSignature(
50 | "AccessControlUnauthorizedAccount(address,bytes32)",
51 | address(this),
52 | RELAYER
53 | ));
54 | mainnetController.depositERC4626(address(susds), 1e18);
55 | }
56 |
57 | function test_depositERC4626_zeroMaxAmount() external {
58 | vm.prank(relayer);
59 | vm.expectRevert("RateLimits/zero-maxAmount");
60 | mainnetController.depositERC4626(makeAddr("fake-token"), 1e18);
61 | }
62 |
63 | function test_depositERC4626_rateLimitBoundary() external {
64 | vm.startPrank(relayer);
65 | mainnetController.mintUSDS(5_000_000e18);
66 |
67 | // Have to warp to get back above rate limit
68 | skip(1 minutes);
69 | mainnetController.mintUSDS(1);
70 |
71 | vm.expectRevert("RateLimits/rate-limit-exceeded");
72 | mainnetController.depositERC4626(address(susds), 5_000_000e18 + 1);
73 |
74 | mainnetController.depositERC4626(address(susds), 5_000_000e18);
75 | }
76 |
77 | }
78 |
79 | contract MainnetControllerDepositERC4626Tests is SUSDSTestBase {
80 |
81 | function test_depositERC4626() external {
82 | vm.prank(relayer);
83 | mainnetController.mintUSDS(1e18);
84 |
85 | assertEq(usds.balanceOf(address(almProxy)), 1e18);
86 | assertEq(usds.balanceOf(address(mainnetController)), 0);
87 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS);
88 |
89 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
90 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
91 |
92 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY);
93 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS);
94 | assertEq(susds.balanceOf(address(almProxy)), 0);
95 |
96 | vm.prank(relayer);
97 | uint256 shares = mainnetController.depositERC4626(address(susds), 1e18);
98 |
99 | assertEq(shares, SUSDS_CONVERTED_SHARES);
100 |
101 | assertEq(usds.balanceOf(address(almProxy)), 0);
102 | assertEq(usds.balanceOf(address(mainnetController)), 0);
103 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS + SUSDS_DRIP_AMOUNT + 1e18);
104 |
105 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
106 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
107 |
108 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY + shares);
109 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS + 1e18);
110 | assertEq(susds.balanceOf(address(almProxy)), SUSDS_CONVERTED_SHARES);
111 | }
112 |
113 | }
114 |
115 | contract MainnetControllerWithdrawERC4626FailureTests is SUSDSTestBase {
116 |
117 | function test_withdrawERC4626_notRelayer() external {
118 | vm.expectRevert(abi.encodeWithSignature(
119 | "AccessControlUnauthorizedAccount(address,bytes32)",
120 | address(this),
121 | RELAYER
122 | ));
123 | mainnetController.withdrawERC4626(address(susds), 1e18);
124 | }
125 |
126 | function test_withdrawERC4626_zeroMaxAmount() external {
127 | vm.prank(relayer);
128 | vm.expectRevert("RateLimits/zero-maxAmount");
129 | mainnetController.withdrawERC4626(makeAddr("fake-token"), 1e18);
130 | }
131 |
132 | function test_withdrawERC4626_rateLimitBoundary() external {
133 | vm.startPrank(relayer);
134 | mainnetController.mintUSDS(5_000_000e18);
135 | mainnetController.depositERC4626(address(susds), 5_000_000e18);
136 |
137 | // Have to warp to get back above rate limit
138 | skip(1 minutes);
139 | mainnetController.mintUSDS(1);
140 | mainnetController.depositERC4626(address(susds), 1);
141 |
142 | vm.expectRevert("RateLimits/rate-limit-exceeded");
143 | mainnetController.withdrawERC4626(address(susds), 5_000_000e18 + 1);
144 |
145 | mainnetController.withdrawERC4626(address(susds), 5_000_000e18);
146 | }
147 |
148 | }
149 |
150 | contract MainnetControllerWithdrawERC4626Tests is SUSDSTestBase {
151 |
152 | function test_withdrawERC4626() external {
153 | vm.startPrank(relayer);
154 | mainnetController.mintUSDS(1e18);
155 | mainnetController.depositERC4626(address(susds), 1e18);
156 | vm.stopPrank();
157 |
158 | assertEq(usds.balanceOf(address(almProxy)), 0);
159 | assertEq(usds.balanceOf(address(mainnetController)), 0);
160 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS + SUSDS_DRIP_AMOUNT + 1e18);
161 |
162 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
163 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
164 |
165 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY + SUSDS_CONVERTED_SHARES);
166 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS + 1e18);
167 | assertEq(susds.balanceOf(address(almProxy)), SUSDS_CONVERTED_SHARES);
168 |
169 | // Max available with rounding
170 | vm.prank(relayer);
171 | uint256 shares = mainnetController.withdrawERC4626(address(susds), 1e18 - 1); // Rounding
172 |
173 | assertEq(shares, SUSDS_CONVERTED_SHARES);
174 |
175 | assertEq(usds.balanceOf(address(almProxy)), 1e18 - 1);
176 | assertEq(usds.balanceOf(address(mainnetController)), 0);
177 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS + SUSDS_DRIP_AMOUNT + 1); // Rounding
178 |
179 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
180 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
181 |
182 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY);
183 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS);
184 | assertEq(susds.balanceOf(address(almProxy)), 0);
185 | }
186 |
187 | }
188 |
189 | contract MainnetControllerRedeemERC4626FailureTests is SUSDSTestBase {
190 |
191 | function test_redeemERC4626_notRelayer() external {
192 | vm.expectRevert(abi.encodeWithSignature(
193 | "AccessControlUnauthorizedAccount(address,bytes32)",
194 | address(this),
195 | RELAYER
196 | ));
197 | mainnetController.redeemERC4626(address(susds), 1e18);
198 | }
199 |
200 | function test_redeemERC4626_zeroMaxAmount() external {
201 | // Longer setup because rate limit revert is at the end of the function
202 | vm.startPrank(Ethereum.SPARK_PROXY);
203 | rateLimits.setRateLimitData(
204 | RateLimitHelpers.makeAssetKey(
205 | mainnetController.LIMIT_4626_WITHDRAW(),
206 | Ethereum.SUSDS
207 | ),
208 | 0,
209 | 0
210 | );
211 | vm.stopPrank();
212 |
213 | vm.startPrank(relayer);
214 | mainnetController.mintUSDS(100e18);
215 | mainnetController.depositERC4626(address(susds), 100e18);
216 | vm.stopPrank();
217 |
218 | vm.prank(relayer);
219 | vm.expectRevert("RateLimits/zero-maxAmount");
220 | mainnetController.redeemERC4626(address(susds), 1e18);
221 | }
222 |
223 | function test_redeemERC4626_rateLimitBoundary() external {
224 | vm.startPrank(relayer);
225 | mainnetController.mintUSDS(5_000_000e18);
226 | mainnetController.depositERC4626(address(susds), 5_000_000e18);
227 |
228 | // Have to warp to get back above rate limit
229 | skip(10 minutes);
230 | mainnetController.mintUSDS(100e18);
231 | mainnetController.depositERC4626(address(susds), 100e18);
232 |
233 | uint256 overBoundaryShares = susds.convertToShares(5_000_000e18 + 2);
234 | uint256 atBoundaryShares = susds.convertToShares(5_000_000e18 + 1); // Still rounds down
235 |
236 | assertEq(susds.previewRedeem(overBoundaryShares), 5_000_000e18 + 1);
237 | assertEq(susds.previewRedeem(atBoundaryShares), 5_000_000e18);
238 |
239 | vm.expectRevert("RateLimits/rate-limit-exceeded");
240 | mainnetController.redeemERC4626(address(susds), overBoundaryShares);
241 |
242 | mainnetController.redeemERC4626(address(susds), atBoundaryShares);
243 | }
244 |
245 | }
246 |
247 | contract MainnetControllerRedeemERC4626Tests is SUSDSTestBase {
248 |
249 | function test_redeemERC4626() external {
250 | vm.startPrank(relayer);
251 | mainnetController.mintUSDS(1e18);
252 | mainnetController.depositERC4626(address(susds), 1e18);
253 | vm.stopPrank();
254 |
255 | assertEq(usds.balanceOf(address(almProxy)), 0);
256 | assertEq(usds.balanceOf(address(mainnetController)), 0);
257 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS + SUSDS_DRIP_AMOUNT + 1e18);
258 |
259 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
260 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
261 |
262 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY + SUSDS_CONVERTED_SHARES);
263 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS + 1e18);
264 | assertEq(susds.balanceOf(address(almProxy)), SUSDS_CONVERTED_SHARES);
265 |
266 | vm.prank(relayer);
267 | uint256 assets = mainnetController.redeemERC4626(address(susds), SUSDS_CONVERTED_SHARES);
268 |
269 | assertEq(assets, 1e18 - 1); // Rounding
270 |
271 | assertEq(usds.balanceOf(address(almProxy)), 1e18 - 1); // Rounding
272 | assertEq(usds.balanceOf(address(mainnetController)), 0);
273 | assertEq(usds.balanceOf(address(susds)), USDS_BAL_SUSDS + SUSDS_DRIP_AMOUNT + 1); // Rounding
274 |
275 | assertEq(usds.allowance(address(buffer), address(vault)), type(uint256).max);
276 | assertEq(usds.allowance(address(almProxy), address(susds)), 0);
277 |
278 | assertEq(susds.totalSupply(), SUSDS_TOTAL_SUPPLY);
279 | assertEq(susds.totalAssets(), SUSDS_TOTAL_ASSETS);
280 | assertEq(susds.balanceOf(address(almProxy)), 0);
281 | }
282 |
283 | }
284 |
--------------------------------------------------------------------------------
/test/mainnet-fork/Approve.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | import { ForeignController } from "../../src/ForeignController.sol";
7 | import { MainnetController } from "../../src/MainnetController.sol";
8 |
9 | import { CurveLib } from "../../src/libraries/CurveLib.sol";
10 |
11 | import { IALMProxy } from "../../src/interfaces/IALMProxy.sol";
12 |
13 | interface IHarness {
14 | function approve(address token, address spender, uint256 amount) external;
15 | function approveCurve(address proxy, address token, address spender, uint256 amount) external;
16 | }
17 |
18 | contract MainnetControllerHarness is MainnetController {
19 |
20 | using CurveLib for IALMProxy;
21 |
22 | constructor(
23 | address admin_,
24 | address proxy_,
25 | address rateLimits_,
26 | address vault_,
27 | address psm_,
28 | address daiUsds_,
29 | address cctp_
30 | ) MainnetController(admin_, proxy_, rateLimits_, vault_, psm_, daiUsds_, cctp_) {}
31 |
32 | function approve(address token, address spender, uint256 amount) external {
33 | _approve(token, spender, amount);
34 | }
35 |
36 | function approveCurve(address proxy, address token, address spender, uint256 amount) external {
37 | IALMProxy(proxy)._approve(token, spender, amount);
38 | }
39 |
40 | }
41 |
42 | contract ForeignControllerHarness is ForeignController {
43 |
44 | constructor(
45 | address admin_,
46 | address proxy_,
47 | address rateLimits_,
48 | address psm_,
49 | address usdc_,
50 | address cctp_
51 | ) ForeignController(admin_, proxy_, rateLimits_, psm_, usdc_, cctp_) {}
52 |
53 | function approve(address token, address spender, uint256 amount) external {
54 | _approve(token, spender, amount);
55 | }
56 |
57 | }
58 |
59 | contract ApproveTestBase is ForkTestBase {
60 |
61 | function _approveTest(address token, address harness) internal {
62 | address spender = makeAddr("spender");
63 |
64 | assertEq(IERC20(token).allowance(harness, spender), 0);
65 |
66 | IHarness(harness).approve(token, spender, 100);
67 |
68 | assertEq(IERC20(token).allowance(address(almProxy), spender), 100);
69 |
70 | IHarness(harness).approve(token, spender, 200); // Would revert without setting to zero
71 |
72 | assertEq(IERC20(token).allowance(address(almProxy), spender), 200);
73 | }
74 |
75 | function _approveCurveTest(address token, address harness) internal {
76 | address spender = makeAddr("spender");
77 |
78 | assertEq(IERC20(token).allowance(harness, spender), 0);
79 |
80 | IHarness(harness).approveCurve(address(almProxy), token, spender, 100);
81 |
82 | assertEq(IERC20(token).allowance(address(almProxy), spender), 100);
83 |
84 | IHarness(harness).approveCurve(address(almProxy), token, spender, 200); // Would revert without setting to zero
85 |
86 | assertEq(IERC20(token).allowance(address(almProxy), spender), 200);
87 | }
88 |
89 | }
90 |
91 | contract MainnetControllerApproveSuccessTests is ApproveTestBase {
92 |
93 | address harness;
94 |
95 | function setUp() public virtual override {
96 | super.setUp();
97 |
98 | MainnetControllerHarness harnessCode = new MainnetControllerHarness(
99 | SPARK_PROXY,
100 | address(mainnetController.proxy()),
101 | address(mainnetController.rateLimits()),
102 | address(mainnetController.vault()),
103 | address(mainnetController.psm()),
104 | address(mainnetController.daiUsds()),
105 | address(mainnetController.cctp())
106 | );
107 |
108 | vm.etch(address(mainnetController), address(harnessCode).code);
109 |
110 | harness = address(MainnetControllerHarness(address(mainnetController)));
111 | }
112 |
113 | function test_approveTokens() public {
114 | _approveTest(Ethereum.CBBTC, harness);
115 | _approveTest(Ethereum.DAI, harness);
116 | _approveTest(Ethereum.GNO, harness);
117 | _approveTest(Ethereum.MKR, harness);
118 | _approveTest(Ethereum.RETH, harness);
119 | _approveTest(Ethereum.SDAI, harness);
120 | _approveTest(Ethereum.SUSDE, harness);
121 | _approveTest(Ethereum.SUSDS, harness);
122 | _approveTest(Ethereum.USDC, harness);
123 | _approveTest(Ethereum.USDE, harness);
124 | _approveTest(Ethereum.USDS, harness);
125 | _approveTest(Ethereum.USCC, harness);
126 | _approveTest(Ethereum.USDT, harness);
127 | _approveTest(Ethereum.USTB, harness);
128 | _approveTest(Ethereum.WBTC, harness);
129 | _approveTest(Ethereum.WEETH, harness);
130 | _approveTest(Ethereum.WETH, harness);
131 | _approveTest(Ethereum.WSTETH, harness);
132 | }
133 |
134 | function test_approveCurveTokens() public {
135 | _approveCurveTest(Ethereum.CBBTC, harness);
136 | _approveCurveTest(Ethereum.DAI, harness);
137 | _approveCurveTest(Ethereum.GNO, harness);
138 | _approveCurveTest(Ethereum.MKR, harness);
139 | _approveCurveTest(Ethereum.RETH, harness);
140 | _approveCurveTest(Ethereum.SDAI, harness);
141 | _approveCurveTest(Ethereum.SUSDE, harness);
142 | _approveCurveTest(Ethereum.SUSDS, harness);
143 | _approveCurveTest(Ethereum.USDC, harness);
144 | _approveCurveTest(Ethereum.USDE, harness);
145 | _approveCurveTest(Ethereum.USDS, harness);
146 | _approveCurveTest(Ethereum.USCC, harness);
147 | _approveCurveTest(Ethereum.USDT, harness);
148 | _approveCurveTest(Ethereum.USTB, harness);
149 | _approveCurveTest(Ethereum.WBTC, harness);
150 | _approveCurveTest(Ethereum.WEETH, harness);
151 | _approveCurveTest(Ethereum.WETH, harness);
152 | _approveCurveTest(Ethereum.WSTETH, harness);
153 | }
154 |
155 | }
156 |
157 | // NOTE: This code is running against mainnet, but is used to demonstrate equivalent approve behaviour
158 | // for USDT-type contracts. Because of this, the foreignController has to be onboarded in the same
159 | // way as the mainnetController.
160 | contract ForeignControllerApproveSuccessTests is ApproveTestBase {
161 |
162 | address harness;
163 |
164 | function setUp() public virtual override {
165 | super.setUp();
166 |
167 | // NOTE: This etching setup is necessary to get coverage to work
168 |
169 | ForeignController foreignController = new ForeignController(
170 | SPARK_PROXY,
171 | address(almProxy),
172 | makeAddr("rateLimits"),
173 | makeAddr("psm"),
174 | makeAddr("usdc"),
175 | makeAddr("cctp")
176 | );
177 |
178 | ForeignControllerHarness harnessCode = new ForeignControllerHarness(
179 | SPARK_PROXY,
180 | address(almProxy),
181 | makeAddr("rateLimits"),
182 | makeAddr("psm"),
183 | makeAddr("usdc"),
184 | makeAddr("cctp")
185 | );
186 |
187 | // Allow the foreign controller to call the ALMProxy
188 | vm.startPrank(SPARK_PROXY);
189 | almProxy.grantRole(almProxy.CONTROLLER(), address(foreignController));
190 | vm.stopPrank();
191 |
192 | vm.etch(address(foreignController), address(harnessCode).code);
193 |
194 | harness = address(ForeignControllerHarness(address(foreignController)));
195 | }
196 |
197 | function test_approveTokens() public {
198 | _approveTest(Ethereum.CBBTC, harness);
199 | _approveTest(Ethereum.DAI, harness);
200 | _approveTest(Ethereum.GNO, harness);
201 | _approveTest(Ethereum.MKR, harness);
202 | _approveTest(Ethereum.RETH, harness);
203 | _approveTest(Ethereum.SDAI, harness);
204 | _approveTest(Ethereum.SUSDE, harness);
205 | _approveTest(Ethereum.SUSDS, harness);
206 | _approveTest(Ethereum.USDC, harness);
207 | _approveTest(Ethereum.USDE, harness);
208 | _approveTest(Ethereum.USDS, harness);
209 | _approveTest(Ethereum.USCC, harness);
210 | _approveTest(Ethereum.USDT, harness);
211 | _approveTest(Ethereum.USTB, harness);
212 | _approveTest(Ethereum.WBTC, harness);
213 | _approveTest(Ethereum.WEETH, harness);
214 | _approveTest(Ethereum.WETH, harness);
215 | _approveTest(Ethereum.WSTETH, harness);
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/test/mainnet-fork/Attacks.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | import { MainnetControllerBUIDLTestBase } from "./Buidl.t.sol";
7 | import { MainnetControllerEthenaE2ETests } from "./Ethena.t.sol";
8 | import { MapleTestBase } from "./Maple.t.sol";
9 |
10 | import { IMapleTokenLike } from "../../src/MainnetController.sol";
11 |
12 | interface IBuidlLike is IERC20 {
13 | function issueTokens(address to, uint256 amount) external;
14 | }
15 |
16 | interface IMapleTokenExtended is IMapleTokenLike {
17 | function manager() external view returns (address);
18 | }
19 |
20 | interface IPermissionManagerLike {
21 | function admin() external view returns (address);
22 | function setLenderAllowlist(
23 | address poolManager_,
24 | address[] calldata lenders_,
25 | bool[] calldata booleans_
26 | ) external;
27 | }
28 |
29 | interface IPoolManagerLike {
30 | function withdrawalManager() external view returns (address);
31 | function poolDelegate() external view returns (address);
32 | }
33 |
34 | interface IWhitelistLike {
35 | function addWallet(address account, string memory id) external;
36 | function registerInvestor(string memory id, string memory collisionHash) external;
37 | }
38 |
39 | contract EthenaAttackTests is MainnetControllerEthenaE2ETests {
40 |
41 | function test_attack_compromisedRelayer_lockingFundsInEthenaSilo() external {
42 | deal(address(susde), address(almProxy), 1_000_000e18);
43 |
44 | address silo = susde.silo();
45 |
46 | uint256 startingSiloBalance = usde.balanceOf(silo);
47 |
48 | vm.prank(relayer);
49 | mainnetController.cooldownAssetsSUSDe(1_000_000e18);
50 |
51 | skip(7 days);
52 |
53 | // Relayer is now compromised and wants to lock funds in the silo
54 | vm.prank(relayer);
55 | mainnetController.cooldownAssetsSUSDe(1);
56 |
57 | // Real relayer cannot withdraw when they want to
58 | vm.prank(relayer);
59 | vm.expectRevert(abi.encodeWithSignature("InvalidCooldown()"));
60 | mainnetController.unstakeSUSDe();
61 |
62 | // Frezer can remove the compromised relayer and fallback to the governance relayer
63 | vm.prank(freezer);
64 | mainnetController.removeRelayer(relayer);
65 |
66 | skip(7 days);
67 |
68 | // Compromised relayer cannot perform attack anymore
69 | vm.prank(relayer);
70 | vm.expectRevert(abi.encodeWithSignature(
71 | "AccessControlUnauthorizedAccount(address,bytes32)",
72 | relayer,
73 | RELAYER
74 | ));
75 | mainnetController.cooldownAssetsSUSDe(1);
76 |
77 | // Funds have been locked in the silo this whole time
78 | assertEq(usde.balanceOf(address(almProxy)), 0);
79 | assertEq(usde.balanceOf(silo), startingSiloBalance + 1_000_000e18 + 1); // 1 wei deposit as well
80 |
81 | // Backstop relayer can unstake the funds
82 | vm.prank(backstopRelayer);
83 | mainnetController.unstakeSUSDe();
84 |
85 | assertEq(usde.balanceOf(address(almProxy)), 1_000_000e18 + 1);
86 | assertEq(usde.balanceOf(silo), startingSiloBalance);
87 | }
88 |
89 | }
90 |
91 | contract MapleAttackTests is MapleTestBase {
92 |
93 | function test_attack_compromisedRelayer_delayRequestMapleRedemption() external {
94 | deal(address(usdc), address(almProxy), 1_000_000e6);
95 |
96 | vm.prank(relayer);
97 | mainnetController.depositERC4626(address(syrup), 1_000_000e6);
98 |
99 | // Malicious relayer delays the request for redemption for 1m
100 | // because new requests can't be fulfilled until the previous is fulfilled or cancelled
101 | vm.prank(relayer);
102 | mainnetController.requestMapleRedemption(address(syrup), 1);
103 |
104 | // Cannot process request
105 | vm.prank(relayer);
106 | vm.expectRevert("WM:AS:IN_QUEUE");
107 | mainnetController.requestMapleRedemption(address(syrup), 500_000e6);
108 |
109 | // Frezer can remove the compromised relayer and fallback to the governance relayer
110 | vm.prank(freezer);
111 | mainnetController.removeRelayer(relayer);
112 |
113 | // Compromised relayer cannot perform attack anymore
114 | vm.prank(relayer);
115 | vm.expectRevert(abi.encodeWithSignature(
116 | "AccessControlUnauthorizedAccount(address,bytes32)",
117 | relayer,
118 | RELAYER
119 | ));
120 | mainnetController.requestMapleRedemption(address(syrup), 1);
121 |
122 | // Governance relayer can cancel and submit the real request
123 | vm.startPrank(backstopRelayer);
124 | mainnetController.cancelMapleRedemption(address(syrup), 1);
125 | mainnetController.requestMapleRedemption(address(syrup), 500_000e6);
126 | vm.stopPrank();
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/test/mainnet-fork/Buidl.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | interface IWhitelistLike {
7 | function addWallet(address account, string memory id) external;
8 | function registerInvestor(string memory id, string memory collisionHash) external;
9 | }
10 |
11 | interface IBuidlLike is IERC20 {
12 | function issueTokens(address to, uint256 amount) external;
13 | }
14 |
15 | contract MainnetControllerBUIDLTestBase is ForkTestBase {
16 |
17 | address buidlDeposit = makeAddr("buidlDeposit");
18 |
19 | }
20 |
21 | contract MainnetControllerDepositBUIDLFailureTests is MainnetControllerBUIDLTestBase {
22 |
23 | function test_transferAsset_notRelayer() external {
24 | vm.expectRevert(abi.encodeWithSignature(
25 | "AccessControlUnauthorizedAccount(address,bytes32)",
26 | address(this),
27 | RELAYER
28 | ));
29 | mainnetController.transferAsset(address(usdc), buidlDeposit, 1_000_000e6);
30 | }
31 |
32 | function test_transferAsset_zeroMaxAmount() external {
33 | vm.prank(relayer);
34 | vm.expectRevert("RateLimits/zero-maxAmount");
35 | mainnetController.transferAsset(address(usdc), buidlDeposit, 0);
36 | }
37 |
38 | function test_transferAsset_rateLimitsBoundary() external {
39 | bytes32 key = RateLimitHelpers.makeAssetDestinationKey(
40 | mainnetController.LIMIT_ASSET_TRANSFER(),
41 | address(usdc),
42 | address(buidlDeposit)
43 | );
44 |
45 | vm.prank(Ethereum.SPARK_PROXY);
46 | rateLimits.setRateLimitData(key, 1_000_000e6, uint256(1_000_000e6) / 1 days);
47 |
48 | deal(address(usdc), address(almProxy), 1_000_000e6);
49 |
50 | vm.startPrank(relayer);
51 | vm.expectRevert("RateLimits/rate-limit-exceeded");
52 | mainnetController.transferAsset(address(usdc), buidlDeposit, 1_000_000e6 + 1);
53 |
54 | mainnetController.transferAsset(address(usdc), buidlDeposit, 1_000_000e6);
55 | }
56 |
57 | }
58 |
59 | contract MainnetControllerDepositBUIDLSuccessTests is MainnetControllerBUIDLTestBase {
60 |
61 | function test_transferAsset() external {
62 | bytes32 key = RateLimitHelpers.makeAssetDestinationKey(
63 | mainnetController.LIMIT_ASSET_TRANSFER(),
64 | address(usdc),
65 | address(buidlDeposit)
66 | );
67 |
68 | vm.prank(Ethereum.SPARK_PROXY);
69 | rateLimits.setRateLimitData(key, 1_000_000e6, uint256(1_000_000e6) / 1 days);
70 |
71 | deal(address(usdc), address(almProxy), 1_000_000e6);
72 |
73 | assertEq(rateLimits.getCurrentRateLimit(key), 1_000_000e6);
74 |
75 | assertEq(usdc.balanceOf(address(almProxy)), 1_000_000e6);
76 | assertEq(usdc.balanceOf(buidlDeposit), 0);
77 |
78 | vm.prank(relayer);
79 | mainnetController.transferAsset(address(usdc), buidlDeposit, 1_000_000e6);
80 |
81 | assertEq(rateLimits.getCurrentRateLimit(key), 0);
82 |
83 | assertEq(usdc.balanceOf(address(almProxy)), 0);
84 | assertEq(usdc.balanceOf(buidlDeposit), 1_000_000e6);
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/test/mainnet-fork/DaiUsds.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | contract MainnetControllerSwapUSDSToDAIFailureTests is ForkTestBase {
7 |
8 | function test_swapUSDSToDAI_notRelayer() external {
9 | vm.expectRevert(abi.encodeWithSignature(
10 | "AccessControlUnauthorizedAccount(address,bytes32)",
11 | address(this),
12 | RELAYER
13 | ));
14 | mainnetController.swapUSDSToDAI(1_000_000e18);
15 | }
16 |
17 | }
18 |
19 | contract MainnetControllerSwapUSDSToDAITests is ForkTestBase {
20 |
21 | function test_swapUSDSToDAI() external {
22 | vm.prank(relayer);
23 | mainnetController.mintUSDS(1_000_000e18);
24 |
25 | assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18);
26 | assertEq(usds.totalSupply(), USDS_SUPPLY + 1_000_000e18);
27 |
28 | assertEq(dai.balanceOf(address(almProxy)), 0);
29 | assertEq(dai.totalSupply(), DAI_SUPPLY);
30 |
31 | assertEq(usds.allowance(address(almProxy), DAI_USDS), 0);
32 |
33 | vm.prank(relayer);
34 | mainnetController.swapUSDSToDAI(1_000_000e18);
35 |
36 | assertEq(usds.balanceOf(address(almProxy)), 0);
37 | assertEq(usds.totalSupply(), USDS_SUPPLY);
38 |
39 | assertEq(dai.balanceOf(address(almProxy)), 1_000_000e18);
40 | assertEq(dai.totalSupply(), DAI_SUPPLY + 1_000_000e18);
41 |
42 | assertEq(usds.allowance(address(almProxy), DAI_USDS), 0);
43 | }
44 |
45 | }
46 |
47 | contract MainnetControllerSwapDAIToUSDSFailureTests is ForkTestBase {
48 |
49 | function test_swapDAIToUSDS_notRelayer() external {
50 | vm.expectRevert(abi.encodeWithSignature(
51 | "AccessControlUnauthorizedAccount(address,bytes32)",
52 | address(this),
53 | RELAYER
54 | ));
55 | mainnetController.swapDAIToUSDS(1_000_000e18);
56 | }
57 |
58 | }
59 |
60 | contract MainnetControllerSwapDAIToUSDSTests is ForkTestBase {
61 |
62 | function test_swapDAIToUSDS() external {
63 | deal(address(dai), address(almProxy), 1_000_000e18);
64 |
65 | assertEq(usds.balanceOf(address(almProxy)), 0);
66 | assertEq(usds.totalSupply(), USDS_SUPPLY);
67 |
68 | assertEq(dai.balanceOf(address(almProxy)), 1_000_000e18);
69 | assertEq(dai.totalSupply(), DAI_SUPPLY); // Supply not updated on deal
70 |
71 | assertEq(dai.allowance(address(almProxy), DAI_USDS), 0);
72 |
73 | vm.prank(relayer);
74 | mainnetController.swapDAIToUSDS(1_000_000e18);
75 |
76 | assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18);
77 | assertEq(usds.totalSupply(), USDS_SUPPLY + 1_000_000e18);
78 |
79 | assertEq(dai.balanceOf(address(almProxy)), 0);
80 | assertEq(dai.totalSupply(), DAI_SUPPLY - 1_000_000e18);
81 |
82 | assertEq(dai.allowance(address(almProxy), DAI_USDS), 0);
83 | }
84 |
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/test/mainnet-fork/Deploy.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity >=0.8.0;
3 |
4 | import { ControllerInstance } from "../../deploy/ControllerInstance.sol";
5 | import { MainnetControllerDeploy } from "../../deploy/ControllerDeploy.sol";
6 |
7 | import "./ForkTestBase.t.sol";
8 |
9 | contract MainnetControllerDeploySuccessTests is ForkTestBase {
10 |
11 | function test_deployFull() external {
12 | // Perform new deployments against existing fork environment
13 |
14 | ControllerInstance memory controllerInst = MainnetControllerDeploy.deployFull({
15 | admin : SPARK_PROXY,
16 | vault : vault,
17 | psm : PSM,
18 | daiUsds : DAI_USDS,
19 | cctp : CCTP_MESSENGER
20 | });
21 |
22 | ALMProxy newAlmProxy = ALMProxy(payable(controllerInst.almProxy));
23 | MainnetController newController = MainnetController(controllerInst.controller);
24 | RateLimits newRateLimits = RateLimits(controllerInst.rateLimits);
25 |
26 | assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true);
27 | assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin
28 |
29 | assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true);
30 | assertEq(newRateLimits.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false); // Deployer never gets admin
31 |
32 | _assertControllerInitState(newController, address(newAlmProxy), address(newRateLimits), vault, buffer);
33 | }
34 |
35 | function test_deployController() external {
36 | // Perform new deployments against existing fork environment
37 |
38 | MainnetController newController = MainnetController(MainnetControllerDeploy.deployController({
39 | admin : SPARK_PROXY,
40 | almProxy : address(almProxy),
41 | rateLimits : address(rateLimits),
42 | vault : vault,
43 | psm : PSM,
44 | daiUsds : DAI_USDS,
45 | cctp : CCTP_MESSENGER
46 | }));
47 |
48 | _assertControllerInitState(newController, address(almProxy), address(rateLimits), vault, buffer);
49 | }
50 |
51 | function _assertControllerInitState(MainnetController controller, address almProxy, address rateLimits, address vault, address buffer) internal view {
52 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, SPARK_PROXY), true);
53 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, address(this)), false);
54 |
55 | assertEq(address(controller.proxy()), almProxy);
56 | assertEq(address(controller.rateLimits()), rateLimits);
57 | assertEq(address(controller.vault()), vault);
58 | assertEq(address(controller.buffer()), buffer);
59 | assertEq(address(controller.psm()), Ethereum.PSM);
60 | assertEq(address(controller.daiUsds()), Ethereum.DAI_USDS);
61 | assertEq(address(controller.cctp()), Ethereum.CCTP_TOKEN_MESSENGER);
62 | assertEq(address(controller.ethenaMinter()), Ethereum.ETHENA_MINTER);
63 | assertEq(address(controller.susde()), Ethereum.SUSDE);
64 | assertEq(address(controller.dai()), Ethereum.DAI);
65 | assertEq(address(controller.usdc()), Ethereum.USDC);
66 | assertEq(address(controller.usds()), Ethereum.USDS);
67 | assertEq(address(controller.usde()), Ethereum.USDE);
68 |
69 | assertEq(controller.psmTo18ConversionFactor(), 1e12);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/test/mainnet-fork/VaultCalls.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import "./ForkTestBase.t.sol";
5 |
6 | contract MainnetControllerMintUSDSFailureTests is ForkTestBase {
7 |
8 | function test_mintUSDS_notRelayer() external {
9 | vm.expectRevert(abi.encodeWithSignature(
10 | "AccessControlUnauthorizedAccount(address,bytes32)",
11 | address(this),
12 | RELAYER
13 | ));
14 | mainnetController.mintUSDS(1e18);
15 | }
16 |
17 | function test_mintUSDS_zeroMaxAmount() external {
18 | vm.startPrank(Ethereum.SPARK_PROXY);
19 | rateLimits.setRateLimitData(mainnetController.LIMIT_USDS_MINT(), 0, 0);
20 | vm.stopPrank();
21 |
22 | vm.prank(relayer);
23 | vm.expectRevert("RateLimits/zero-maxAmount");
24 | mainnetController.mintUSDS(1e18);
25 | }
26 |
27 | function test_mintUSDS_rateLimitBoundary() external {
28 | vm.startPrank(relayer);
29 |
30 | vm.expectRevert("RateLimits/rate-limit-exceeded");
31 | mainnetController.mintUSDS(5_000_000e18 + 1);
32 |
33 | mainnetController.mintUSDS(5_000_000e18);
34 | }
35 |
36 | }
37 |
38 | contract MainnetControllerMintUSDSSuccessTests is ForkTestBase {
39 |
40 | function test_mintUSDS() external {
41 | ( uint256 ink, uint256 art ) = dss.vat.urns(ilk, vault);
42 | ( uint256 Art,,,, ) = dss.vat.ilks(ilk);
43 |
44 | assertEq(dss.vat.dai(USDS_JOIN), VAT_DAI_USDS_JOIN);
45 |
46 | assertEq(Art, 0);
47 | assertEq(ink, INK);
48 | assertEq(art, 0);
49 |
50 | assertEq(usds.balanceOf(address(almProxy)), 0);
51 | assertEq(usds.totalSupply(), USDS_SUPPLY);
52 |
53 | vm.prank(relayer);
54 | mainnetController.mintUSDS(1e18);
55 |
56 | ( ink, art ) = dss.vat.urns(ilk, vault);
57 | ( Art,,,, ) = dss.vat.ilks(ilk);
58 |
59 | assertEq(dss.vat.dai(USDS_JOIN), VAT_DAI_USDS_JOIN + 1e45);
60 |
61 | assertEq(Art, 1e18);
62 | assertEq(ink, INK);
63 | assertEq(art, 1e18);
64 |
65 | assertEq(usds.balanceOf(address(almProxy)), 1e18);
66 | assertEq(usds.totalSupply(), USDS_SUPPLY + 1e18);
67 | }
68 |
69 | function test_mintUSDS_rateLimited() external {
70 | bytes32 key = mainnetController.LIMIT_USDS_MINT();
71 | vm.startPrank(relayer);
72 |
73 | assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18);
74 | assertEq(usds.balanceOf(address(almProxy)), 0);
75 |
76 | mainnetController.mintUSDS(1_000_000e18);
77 |
78 | assertEq(rateLimits.getCurrentRateLimit(key), 4_000_000e18);
79 | assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18);
80 |
81 | skip(1 hours);
82 |
83 | assertEq(rateLimits.getCurrentRateLimit(key), 4_249_999.9999999999999984e18);
84 | assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18);
85 |
86 | mainnetController.mintUSDS(4_249_999.9999999999999984e18);
87 |
88 | assertEq(rateLimits.getCurrentRateLimit(key), 0);
89 | assertEq(usds.balanceOf(address(almProxy)), 5_249_999.9999999999999984e18);
90 |
91 | vm.expectRevert("RateLimits/rate-limit-exceeded");
92 | mainnetController.mintUSDS(1);
93 |
94 | vm.stopPrank();
95 | }
96 |
97 | }
98 |
99 | contract MainnetControllerBurnUSDSFailureTests is ForkTestBase {
100 |
101 | function test_burnUSDS_notRelayer() external {
102 | vm.expectRevert(abi.encodeWithSignature(
103 | "AccessControlUnauthorizedAccount(address,bytes32)",
104 | address(this),
105 | RELAYER
106 | ));
107 | mainnetController.burnUSDS(1e18);
108 | }
109 |
110 | function test_burnUSDS_zeroMaxAmount() external {
111 | vm.startPrank(Ethereum.SPARK_PROXY);
112 | rateLimits.setRateLimitData(mainnetController.LIMIT_USDS_MINT(), 0, 0);
113 | vm.stopPrank();
114 |
115 | vm.prank(relayer);
116 | vm.expectRevert("RateLimits/zero-maxAmount");
117 | mainnetController.burnUSDS(1e18);
118 | }
119 |
120 | }
121 |
122 | contract MainnetControllerBurnUSDSSuccessTests is ForkTestBase {
123 |
124 | function test_burnUSDS() external {
125 | // Setup
126 | vm.prank(relayer);
127 | mainnetController.mintUSDS(1e18);
128 |
129 | ( uint256 ink, uint256 art ) = dss.vat.urns(ilk, vault);
130 | ( uint256 Art,,,, ) = dss.vat.ilks(ilk);
131 |
132 | assertEq(dss.vat.dai(USDS_JOIN), VAT_DAI_USDS_JOIN + 1e45);
133 |
134 | assertEq(Art, 1e18);
135 | assertEq(ink, INK);
136 | assertEq(art, 1e18);
137 |
138 | assertEq(usds.balanceOf(address(almProxy)), 1e18);
139 | assertEq(usds.totalSupply(), USDS_SUPPLY + 1e18);
140 |
141 | vm.prank(relayer);
142 | mainnetController.burnUSDS(1e18);
143 |
144 | ( ink, art ) = dss.vat.urns(ilk, vault);
145 | ( Art,,,, ) = dss.vat.ilks(ilk);
146 |
147 | assertEq(dss.vat.dai(USDS_JOIN), VAT_DAI_USDS_JOIN);
148 |
149 | assertEq(Art, 0);
150 | assertEq(ink, INK);
151 | assertEq(art, 0);
152 |
153 | assertEq(usds.balanceOf(address(almProxy)), 0);
154 | assertEq(usds.totalSupply(), USDS_SUPPLY);
155 | }
156 |
157 | function test_burnUSDS_rateLimited() external {
158 | bytes32 key = mainnetController.LIMIT_USDS_MINT();
159 | vm.startPrank(relayer);
160 |
161 | assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18);
162 | assertEq(usds.balanceOf(address(almProxy)), 0);
163 |
164 | mainnetController.mintUSDS(1_000_000e18);
165 |
166 | assertEq(rateLimits.getCurrentRateLimit(key), 4_000_000e18);
167 | assertEq(usds.balanceOf(address(almProxy)), 1_000_000e18);
168 |
169 | mainnetController.burnUSDS(500_000e18);
170 |
171 | assertEq(rateLimits.getCurrentRateLimit(key), 4_500_000e18);
172 | assertEq(usds.balanceOf(address(almProxy)), 500_000e18);
173 |
174 | skip(4 hours);
175 |
176 | assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18);
177 | assertEq(usds.balanceOf(address(almProxy)), 500_000e18);
178 |
179 | mainnetController.burnUSDS(500_000e18);
180 |
181 | assertEq(rateLimits.getCurrentRateLimit(key), 5_000_000e18);
182 | assertEq(usds.balanceOf(address(almProxy)), 0);
183 |
184 | vm.stopPrank();
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/test/unit/UnitTestBase.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import "forge-std/Test.sol";
5 |
6 | contract UnitTestBase is Test {
7 |
8 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
9 |
10 | bytes32 constant CONTROLLER = keccak256("CONTROLLER");
11 | bytes32 constant FREEZER = keccak256("FREEZER");
12 | bytes32 constant RELAYER = keccak256("RELAYER");
13 |
14 | address admin = makeAddr("admin");
15 | address freezer = makeAddr("freezer");
16 | address relayer = makeAddr("relayer");
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/test/unit/controllers/Admin.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { ForeignController } from "../../../src/ForeignController.sol";
5 | import { MainnetController } from "../../../src/MainnetController.sol";
6 |
7 | import { MockDaiUsds } from "../mocks/MockDaiUsds.sol";
8 | import { MockPSM } from "../mocks/MockPSM.sol";
9 | import { MockVault } from "../mocks/MockVault.sol";
10 |
11 | import "../UnitTestBase.t.sol";
12 |
13 | contract MainnetControllerAdminTestBase is UnitTestBase {
14 |
15 | event LayerZeroRecipientSet(uint32 indexed destinationDomain, bytes32 layerZeroRecipient);
16 | event MaxSlippageSet(address indexed pool, uint256 maxSlippage);
17 | event MintRecipientSet(uint32 indexed destinationDomain, bytes32 mintRecipient);
18 |
19 | bytes32 layerZeroRecipient1 = bytes32(uint256(uint160(makeAddr("layerZeroRecipient1"))));
20 | bytes32 layerZeroRecipient2 = bytes32(uint256(uint160(makeAddr("layerZeroRecipient2"))));
21 | bytes32 mintRecipient1 = bytes32(uint256(uint160(makeAddr("mintRecipient1"))));
22 | bytes32 mintRecipient2 = bytes32(uint256(uint160(makeAddr("mintRecipient2"))));
23 |
24 | MainnetController mainnetController;
25 |
26 | function setUp() public {
27 | MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai"));
28 | MockPSM psm = new MockPSM(makeAddr("usdc"));
29 | MockVault vault = new MockVault(makeAddr("buffer"));
30 |
31 | mainnetController = new MainnetController(
32 | admin,
33 | makeAddr("almProxy"),
34 | makeAddr("rateLimits"),
35 | address(vault),
36 | address(psm),
37 | address(daiUsds),
38 | makeAddr("cctp")
39 | );
40 | }
41 |
42 | }
43 |
44 | contract MainnetControllerSetMintRecipientTests is MainnetControllerAdminTestBase {
45 |
46 | function test_setMintRecipient_unauthorizedAccount() public {
47 | vm.expectRevert(abi.encodeWithSignature(
48 | "AccessControlUnauthorizedAccount(address,bytes32)",
49 | address(this),
50 | DEFAULT_ADMIN_ROLE
51 | ));
52 | mainnetController.setMintRecipient(1, mintRecipient1);
53 |
54 | vm.prank(freezer);
55 | vm.expectRevert(abi.encodeWithSignature(
56 | "AccessControlUnauthorizedAccount(address,bytes32)",
57 | freezer,
58 | DEFAULT_ADMIN_ROLE
59 | ));
60 | mainnetController.setMintRecipient(1, mintRecipient1);
61 | }
62 |
63 | function test_setMintRecipient() public {
64 | assertEq(mainnetController.mintRecipients(1), bytes32(0));
65 | assertEq(mainnetController.mintRecipients(2), bytes32(0));
66 |
67 | vm.prank(admin);
68 | vm.expectEmit(address(mainnetController));
69 | emit MintRecipientSet(1, mintRecipient1);
70 | mainnetController.setMintRecipient(1, mintRecipient1);
71 |
72 | assertEq(mainnetController.mintRecipients(1), mintRecipient1);
73 |
74 | vm.prank(admin);
75 | vm.expectEmit(address(mainnetController));
76 | emit MintRecipientSet(2, mintRecipient2);
77 | mainnetController.setMintRecipient(2, mintRecipient2);
78 |
79 | assertEq(mainnetController.mintRecipients(2), mintRecipient2);
80 |
81 | vm.prank(admin);
82 | vm.expectEmit(address(mainnetController));
83 | emit MintRecipientSet(1, mintRecipient2);
84 | mainnetController.setMintRecipient(1, mintRecipient2);
85 |
86 | assertEq(mainnetController.mintRecipients(1), mintRecipient2);
87 | }
88 |
89 | }
90 |
91 | contract MainnetControllerSetLayerZeroRecipientTests is MainnetControllerAdminTestBase {
92 |
93 | function test_setLayerZeroRecipient_unauthorizedAccount() public {
94 | vm.expectRevert(abi.encodeWithSignature(
95 | "AccessControlUnauthorizedAccount(address,bytes32)",
96 | address(this),
97 | DEFAULT_ADMIN_ROLE
98 | ));
99 | mainnetController.setLayerZeroRecipient(1, layerZeroRecipient1);
100 |
101 | vm.prank(freezer);
102 | vm.expectRevert(abi.encodeWithSignature(
103 | "AccessControlUnauthorizedAccount(address,bytes32)",
104 | freezer,
105 | DEFAULT_ADMIN_ROLE
106 | ));
107 | mainnetController.setMintRecipient(1, mintRecipient1);
108 | }
109 |
110 | function test_setLayerZeroRecipient() public {
111 | assertEq(mainnetController.layerZeroRecipients(1), bytes32(0));
112 | assertEq(mainnetController.layerZeroRecipients(2), bytes32(0));
113 |
114 | vm.prank(admin);
115 | vm.expectEmit(address(mainnetController));
116 | emit LayerZeroRecipientSet(1, layerZeroRecipient1);
117 | mainnetController.setLayerZeroRecipient(1, layerZeroRecipient1);
118 |
119 | assertEq(mainnetController.layerZeroRecipients(1), layerZeroRecipient1);
120 |
121 | vm.prank(admin);
122 | vm.expectEmit(address(mainnetController));
123 | emit LayerZeroRecipientSet(2, layerZeroRecipient2);
124 | mainnetController.setLayerZeroRecipient(2, layerZeroRecipient2);
125 |
126 | assertEq(mainnetController.layerZeroRecipients(2), layerZeroRecipient2);
127 |
128 | vm.prank(admin);
129 | vm.expectEmit(address(mainnetController));
130 | emit LayerZeroRecipientSet(1, layerZeroRecipient2);
131 | mainnetController.setLayerZeroRecipient(1, layerZeroRecipient2);
132 |
133 | assertEq(mainnetController.layerZeroRecipients(1), layerZeroRecipient2);
134 | }
135 |
136 | }
137 |
138 | contract MainnetControllerSetMaxSlippageTests is MainnetControllerAdminTestBase {
139 |
140 | function test_setMaxSlippage_unauthorizedAccount() public {
141 | vm.expectRevert(abi.encodeWithSignature(
142 | "AccessControlUnauthorizedAccount(address,bytes32)",
143 | address(this),
144 | DEFAULT_ADMIN_ROLE
145 | ));
146 | mainnetController.setMaxSlippage(makeAddr("pool"), 0.01e18);
147 |
148 | vm.prank(freezer);
149 | vm.expectRevert(abi.encodeWithSignature(
150 | "AccessControlUnauthorizedAccount(address,bytes32)",
151 | freezer,
152 | DEFAULT_ADMIN_ROLE
153 | ));
154 | mainnetController.setMaxSlippage(makeAddr("pool"), 0.01e18);
155 | }
156 |
157 | function test_setMaxSlippage() public {
158 | address pool = makeAddr("pool");
159 |
160 | assertEq(mainnetController.maxSlippages(pool), 0);
161 |
162 | vm.prank(admin);
163 | vm.expectEmit(address(mainnetController));
164 | emit MaxSlippageSet(pool, 0.01e18);
165 | mainnetController.setMaxSlippage(pool, 0.01e18);
166 |
167 | assertEq(mainnetController.maxSlippages(pool), 0.01e18);
168 |
169 | vm.prank(admin);
170 | vm.expectEmit(address(mainnetController));
171 | emit MaxSlippageSet(pool, 0.02e18);
172 | mainnetController.setMaxSlippage(pool, 0.02e18);
173 |
174 | assertEq(mainnetController.maxSlippages(pool), 0.02e18);
175 | }
176 |
177 | }
178 |
179 | contract ForeignControllerAdminTests is UnitTestBase {
180 |
181 | event LayerZeroRecipientSet(uint32 indexed destinationDomain, bytes32 layerZeroRecipient);
182 | event MintRecipientSet(uint32 indexed destinationDomain, bytes32 mintRecipient);
183 |
184 | ForeignController foreignController;
185 |
186 | bytes32 layerZeroRecipient1 = bytes32(uint256(uint160(makeAddr("layerZeroRecipient1"))));
187 | bytes32 layerZeroRecipient2 = bytes32(uint256(uint160(makeAddr("layerZeroRecipient2"))));
188 | bytes32 mintRecipient1 = bytes32(uint256(uint160(makeAddr("mintRecipient1"))));
189 | bytes32 mintRecipient2 = bytes32(uint256(uint160(makeAddr("mintRecipient2"))));
190 |
191 | function setUp() public {
192 | foreignController = new ForeignController(
193 | admin,
194 | makeAddr("almProxy"),
195 | makeAddr("rateLimits"),
196 | makeAddr("psm"),
197 | makeAddr("usdc"),
198 | makeAddr("cctp")
199 | );
200 | }
201 |
202 | function test_setMintRecipient_unauthorizedAccount() public {
203 | vm.expectRevert(abi.encodeWithSignature(
204 | "AccessControlUnauthorizedAccount(address,bytes32)",
205 | address(this),
206 | DEFAULT_ADMIN_ROLE
207 | ));
208 | foreignController.setMintRecipient(1, mintRecipient1);
209 |
210 | vm.prank(freezer);
211 | vm.expectRevert(abi.encodeWithSignature(
212 | "AccessControlUnauthorizedAccount(address,bytes32)",
213 | freezer,
214 | DEFAULT_ADMIN_ROLE
215 | ));
216 | foreignController.setMintRecipient(1, mintRecipient1);
217 | }
218 |
219 | function test_setLayerZeroRecipient_unauthorizedAccount() public {
220 | vm.expectRevert(abi.encodeWithSignature(
221 | "AccessControlUnauthorizedAccount(address,bytes32)",
222 | address(this),
223 | DEFAULT_ADMIN_ROLE
224 | ));
225 | foreignController.setLayerZeroRecipient(1, layerZeroRecipient1);
226 |
227 | vm.prank(freezer);
228 | vm.expectRevert(abi.encodeWithSignature(
229 | "AccessControlUnauthorizedAccount(address,bytes32)",
230 | freezer,
231 | DEFAULT_ADMIN_ROLE
232 | ));
233 | foreignController.setLayerZeroRecipient(1, layerZeroRecipient1);
234 | }
235 |
236 | function test_setMintRecipient() public {
237 | assertEq(foreignController.mintRecipients(1), bytes32(0));
238 | assertEq(foreignController.mintRecipients(2), bytes32(0));
239 |
240 | vm.prank(admin);
241 | vm.expectEmit(address(foreignController));
242 | emit MintRecipientSet(1, mintRecipient1);
243 | foreignController.setMintRecipient(1, mintRecipient1);
244 |
245 | assertEq(foreignController.mintRecipients(1), mintRecipient1);
246 |
247 | vm.prank(admin);
248 | vm.expectEmit(address(foreignController));
249 | emit MintRecipientSet(2, mintRecipient2);
250 | foreignController.setMintRecipient(2, mintRecipient2);
251 |
252 | assertEq(foreignController.mintRecipients(2), mintRecipient2);
253 |
254 | vm.prank(admin);
255 | vm.expectEmit(address(foreignController));
256 | emit MintRecipientSet(1, mintRecipient2);
257 | foreignController.setMintRecipient(1, mintRecipient2);
258 |
259 | assertEq(foreignController.mintRecipients(1), mintRecipient2);
260 | }
261 |
262 | function test_setLayerZeroRecipient() public {
263 | assertEq(foreignController.layerZeroRecipients(1), bytes32(0));
264 | assertEq(foreignController.layerZeroRecipients(2), bytes32(0));
265 |
266 | vm.prank(admin);
267 | vm.expectEmit(address(foreignController));
268 | emit LayerZeroRecipientSet(1, layerZeroRecipient1);
269 | foreignController.setLayerZeroRecipient(1, layerZeroRecipient1);
270 |
271 | assertEq(foreignController.layerZeroRecipients(1), layerZeroRecipient1);
272 |
273 | vm.prank(admin);
274 | vm.expectEmit(address(foreignController));
275 | emit LayerZeroRecipientSet(2, layerZeroRecipient2);
276 | foreignController.setLayerZeroRecipient(2, layerZeroRecipient2);
277 |
278 | assertEq(foreignController.layerZeroRecipients(2), layerZeroRecipient2);
279 |
280 | vm.prank(admin);
281 | vm.expectEmit(address(foreignController));
282 | emit LayerZeroRecipientSet(1, layerZeroRecipient2);
283 | foreignController.setLayerZeroRecipient(1, layerZeroRecipient2);
284 |
285 | assertEq(foreignController.layerZeroRecipients(1), layerZeroRecipient2);
286 | }
287 |
288 | }
289 |
290 |
--------------------------------------------------------------------------------
/test/unit/controllers/Constructor.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { ForeignController } from "../../../src/ForeignController.sol";
5 | import { MainnetController } from "../../../src/MainnetController.sol";
6 |
7 | import { MockDaiUsds } from "../mocks/MockDaiUsds.sol";
8 | import { MockPSM } from "../mocks/MockPSM.sol";
9 | import { MockPSM3 } from "../mocks/MockPSM3.sol";
10 | import { MockVault } from "../mocks/MockVault.sol";
11 |
12 | import "../UnitTestBase.t.sol";
13 |
14 | contract MainnetControllerConstructorTests is UnitTestBase {
15 |
16 | function test_constructor() public {
17 | MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai"));
18 | MockPSM psm = new MockPSM(makeAddr("usdc"));
19 | MockVault vault = new MockVault(makeAddr("buffer"));
20 |
21 | MainnetController mainnetController = new MainnetController(
22 | admin,
23 | makeAddr("almProxy"),
24 | makeAddr("rateLimits"),
25 | address(vault),
26 | address(psm),
27 | address(daiUsds),
28 | makeAddr("cctp")
29 | );
30 |
31 | assertEq(mainnetController.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
32 |
33 | assertEq(address(mainnetController.proxy()), makeAddr("almProxy"));
34 | assertEq(address(mainnetController.rateLimits()), makeAddr("rateLimits"));
35 | assertEq(address(mainnetController.vault()), address(vault));
36 | assertEq(address(mainnetController.buffer()), makeAddr("buffer")); // Buffer param in MockVault
37 | assertEq(address(mainnetController.psm()), address(psm));
38 | assertEq(address(mainnetController.daiUsds()), address(daiUsds));
39 | assertEq(address(mainnetController.cctp()), makeAddr("cctp"));
40 | assertEq(address(mainnetController.dai()), makeAddr("dai")); // Dai param in MockDaiUsds
41 | assertEq(address(mainnetController.usdc()), makeAddr("usdc")); // Gem param in MockPSM
42 |
43 | assertEq(mainnetController.psmTo18ConversionFactor(), psm.to18ConversionFactor());
44 | assertEq(mainnetController.psmTo18ConversionFactor(), 1e12);
45 | }
46 |
47 | }
48 |
49 | contract ForeignControllerConstructorTests is UnitTestBase {
50 |
51 | address almProxy = makeAddr("almProxy");
52 | address rateLimits = makeAddr("rateLimits");
53 | address cctp = makeAddr("cctp");
54 | address psm = makeAddr("psm");
55 | address usdc = makeAddr("usdc");
56 |
57 | function test_constructor() public {
58 | ForeignController foreignController = new ForeignController(
59 | admin,
60 | almProxy,
61 | rateLimits,
62 | psm,
63 | usdc,
64 | cctp
65 | );
66 |
67 | assertEq(foreignController.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
68 |
69 | assertEq(address(foreignController.proxy()), almProxy);
70 | assertEq(address(foreignController.rateLimits()), rateLimits);
71 | assertEq(address(foreignController.psm()), psm);
72 | assertEq(address(foreignController.usdc()), usdc); // asset1 param in MockPSM3
73 | assertEq(address(foreignController.cctp()), cctp);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/test/unit/controllers/Freezer.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { MainnetController } from "../../../src/MainnetController.sol";
5 | import { ForeignController } from "../../../src/ForeignController.sol";
6 |
7 | import { MockDaiUsds } from "../mocks/MockDaiUsds.sol";
8 | import { MockPSM } from "../mocks/MockPSM.sol";
9 | import { MockPSM3 } from "../mocks/MockPSM3.sol";
10 | import { MockVault } from "../mocks/MockVault.sol";
11 |
12 | import "../UnitTestBase.t.sol";
13 |
14 | contract MainnetControllerRemoveRelayerTests is UnitTestBase {
15 |
16 | MainnetController controller;
17 |
18 | address relayer1 = makeAddr("relayer1");
19 | address relayer2 = makeAddr("relayer2");
20 |
21 | event RelayerRemoved(address indexed relayer);
22 |
23 | function setUp() public virtual {
24 | MockDaiUsds daiUsds = new MockDaiUsds(makeAddr("dai"));
25 | MockPSM psm = new MockPSM(makeAddr("usdc"));
26 | MockVault vault = new MockVault(makeAddr("buffer"));
27 |
28 | controller = new MainnetController(
29 | admin,
30 | makeAddr("almProxy"),
31 | makeAddr("rateLimits"),
32 | address(vault),
33 | address(psm),
34 | address(daiUsds),
35 | makeAddr("cctp")
36 | );
37 |
38 | vm.startPrank(admin);
39 |
40 | controller.grantRole(FREEZER, freezer);
41 | controller.grantRole(RELAYER, relayer1);
42 | controller.grantRole(RELAYER, relayer2);
43 |
44 | vm.stopPrank();
45 | }
46 |
47 | function test_removeRelayer_unauthorizedAccount() public {
48 | vm.expectRevert(abi.encodeWithSignature(
49 | "AccessControlUnauthorizedAccount(address,bytes32)",
50 | address(this),
51 | FREEZER
52 | ));
53 | controller.removeRelayer(relayer);
54 |
55 | vm.prank(admin);
56 | vm.expectRevert(abi.encodeWithSignature(
57 | "AccessControlUnauthorizedAccount(address,bytes32)",
58 | admin,
59 | FREEZER
60 | ));
61 | controller.removeRelayer(relayer);
62 | }
63 |
64 | function test_removeRelayer() public {
65 | assertEq(controller.hasRole(RELAYER, relayer1), true);
66 | assertEq(controller.hasRole(RELAYER, relayer2), true);
67 |
68 | vm.prank(freezer);
69 | vm.expectEmit(address(controller));
70 | emit RelayerRemoved(relayer1);
71 | controller.removeRelayer(relayer1);
72 |
73 | assertEq(controller.hasRole(RELAYER, relayer1), false);
74 | assertEq(controller.hasRole(RELAYER, relayer2), true);
75 |
76 | vm.prank(freezer);
77 | vm.expectEmit(address(controller));
78 | emit RelayerRemoved(relayer2);
79 | controller.removeRelayer(relayer2);
80 |
81 | assertEq(controller.hasRole(RELAYER, relayer1), false);
82 | assertEq(controller.hasRole(RELAYER, relayer2), false);
83 | }
84 |
85 | }
86 |
87 | contract ForeignControllerRemoveRelayerTests is UnitTestBase {
88 |
89 | ForeignController controller;
90 |
91 | address relayer1 = makeAddr("relayer1");
92 | address relayer2 = makeAddr("relayer2");
93 | address susds = makeAddr("susds");
94 | address usdc = makeAddr("usdc");
95 | address usds = makeAddr("usds");
96 |
97 | event RelayerRemoved(address indexed relayer);
98 |
99 | function setUp() public {
100 | MockPSM3 psm3 = new MockPSM3(usds, usdc, susds);
101 |
102 | controller = new ForeignController(
103 | admin,
104 | makeAddr("almProxy"),
105 | makeAddr("rateLimits"),
106 | address(psm3),
107 | usdc,
108 | makeAddr("cctp")
109 | );
110 |
111 | vm.startPrank(admin);
112 |
113 | controller.grantRole(FREEZER, freezer);
114 | controller.grantRole(RELAYER, relayer1);
115 | controller.grantRole(RELAYER, relayer2);
116 |
117 | vm.stopPrank();
118 | }
119 |
120 | function test_removeRelayer_unauthorizedAccount() public {
121 | vm.expectRevert(abi.encodeWithSignature(
122 | "AccessControlUnauthorizedAccount(address,bytes32)",
123 | address(this),
124 | FREEZER
125 | ));
126 | controller.removeRelayer(relayer);
127 |
128 | vm.prank(admin);
129 | vm.expectRevert(abi.encodeWithSignature(
130 | "AccessControlUnauthorizedAccount(address,bytes32)",
131 | admin,
132 | FREEZER
133 | ));
134 | controller.removeRelayer(relayer);
135 | }
136 |
137 | function test_removeRelayer() public {
138 | assertEq(controller.hasRole(RELAYER, relayer1), true);
139 | assertEq(controller.hasRole(RELAYER, relayer2), true);
140 |
141 | vm.prank(freezer);
142 | vm.expectEmit(address(controller));
143 | emit RelayerRemoved(relayer1);
144 | controller.removeRelayer(relayer1);
145 |
146 | assertEq(controller.hasRole(RELAYER, relayer1), false);
147 | assertEq(controller.hasRole(RELAYER, relayer2), true);
148 |
149 | vm.prank(freezer);
150 | vm.expectEmit(address(controller));
151 | emit RelayerRemoved(relayer2);
152 | controller.removeRelayer(relayer2);
153 |
154 | assertEq(controller.hasRole(RELAYER, relayer1), false);
155 | assertEq(controller.hasRole(RELAYER, relayer2), false);
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/test/unit/deployments/Deploy.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import "../../../deploy/ControllerDeploy.sol"; // All imports needed so not importing explicitly
5 |
6 | import { MockDaiUsds } from "../mocks/MockDaiUsds.sol";
7 | import { MockPSM } from "../mocks/MockPSM.sol";
8 | import { MockVault } from "../mocks/MockVault.sol";
9 |
10 | import "../UnitTestBase.t.sol";
11 |
12 | contract ForeignControllerDeployTests is UnitTestBase {
13 |
14 | function test_deployController() public {
15 | address admin = makeAddr("admin");
16 | address psm = makeAddr("psm");
17 | address usdc = makeAddr("usdc");
18 | address cctp = makeAddr("cctp");
19 |
20 | address almProxy = address(new ALMProxy(admin));
21 | address rateLimits = address(new RateLimits(admin));
22 |
23 | ForeignController controller = ForeignController(
24 | ForeignControllerDeploy.deployController(
25 | admin,
26 | almProxy,
27 | rateLimits,
28 | psm,
29 | usdc,
30 | cctp
31 | )
32 | );
33 |
34 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
35 |
36 | assertEq(address(controller.proxy()), almProxy);
37 | assertEq(address(controller.rateLimits()), rateLimits);
38 | assertEq(address(controller.psm()), psm);
39 | assertEq(address(controller.usdc()), usdc);
40 | assertEq(address(controller.cctp()), cctp);
41 | }
42 |
43 | function test_deployFull() public {
44 | address admin = makeAddr("admin");
45 | address psm = makeAddr("psm");
46 | address usdc = makeAddr("usdc");
47 | address cctp = makeAddr("cctp");
48 |
49 | ControllerInstance memory instance
50 | = ForeignControllerDeploy.deployFull(admin, psm, usdc, cctp);
51 |
52 | ALMProxy almProxy = ALMProxy(payable(instance.almProxy));
53 | ForeignController controller = ForeignController(instance.controller);
54 | RateLimits rateLimits = RateLimits(instance.rateLimits);
55 |
56 | assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
57 | assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
58 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
59 |
60 | assertEq(address(controller.proxy()), instance.almProxy);
61 | assertEq(address(controller.rateLimits()), instance.rateLimits);
62 | assertEq(address(controller.psm()), psm);
63 | assertEq(address(controller.usdc()), usdc);
64 | assertEq(address(controller.cctp()), cctp);
65 | }
66 |
67 | }
68 |
69 | contract MainnetControllerDeployTests is UnitTestBase {
70 |
71 | struct TestVars {
72 | address daiUsds;
73 | address psm;
74 | address admin;
75 | address vault;
76 | address cctp;
77 | }
78 |
79 | function test_deployController() public {
80 | TestVars memory vars; // Avoid stack too deep
81 |
82 | vars.daiUsds = address(new MockDaiUsds(makeAddr("dai")));
83 | vars.psm = address(new MockPSM(makeAddr("usdc")));
84 | vars.vault = address(new MockVault(makeAddr("buffer")));
85 |
86 | vars.admin = makeAddr("admin");
87 | vars.cctp = makeAddr("cctp");
88 |
89 | address almProxy = address(new ALMProxy(admin));
90 | address rateLimits = address(new RateLimits(admin));
91 |
92 | MainnetController controller = MainnetController(
93 | MainnetControllerDeploy.deployController(
94 | admin,
95 | almProxy,
96 | rateLimits,
97 | vars.vault,
98 | vars.psm,
99 | vars.daiUsds,
100 | vars.cctp
101 | )
102 | );
103 |
104 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
105 |
106 | assertEq(address(controller.proxy()), almProxy);
107 | assertEq(address(controller.rateLimits()), rateLimits);
108 | assertEq(address(controller.vault()), vars.vault);
109 | assertEq(address(controller.buffer()), makeAddr("buffer")); // Buffer param in MockVault
110 | assertEq(address(controller.psm()), vars.psm);
111 | assertEq(address(controller.daiUsds()), vars.daiUsds);
112 | assertEq(address(controller.cctp()), vars.cctp);
113 | assertEq(address(controller.dai()), makeAddr("dai")); // Dai param in MockDaiUsds
114 | assertEq(address(controller.usdc()), makeAddr("usdc")); // Gem param in MockPSM
115 |
116 | assertEq(controller.psmTo18ConversionFactor(), 1e12);
117 | }
118 |
119 | function test_deployFull() public {
120 | TestVars memory vars; // Avoid stack too deep
121 |
122 | vars.daiUsds = address(new MockDaiUsds(makeAddr("dai")));
123 | vars.psm = address(new MockPSM(makeAddr("usdc")));
124 | vars.vault = address(new MockVault(makeAddr("buffer")));
125 |
126 | vars.admin = makeAddr("admin");
127 | vars.cctp = makeAddr("cctp");
128 |
129 | ControllerInstance memory instance = MainnetControllerDeploy.deployFull(
130 | admin,
131 | vars.vault,
132 | vars.psm,
133 | vars.daiUsds,
134 | vars.cctp
135 | );
136 |
137 | ALMProxy almProxy = ALMProxy(payable(instance.almProxy));
138 | MainnetController controller = MainnetController(instance.controller);
139 | RateLimits rateLimits = RateLimits(instance.rateLimits);
140 |
141 | assertEq(almProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
142 | assertEq(controller.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
143 | assertEq(rateLimits.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
144 |
145 | assertEq(address(controller.proxy()), instance.almProxy);
146 | assertEq(address(controller.rateLimits()), instance.rateLimits);
147 | assertEq(address(controller.vault()), vars.vault);
148 | assertEq(address(controller.buffer()), makeAddr("buffer")); // Buffer param in MockVault
149 | assertEq(address(controller.psm()), vars.psm);
150 | assertEq(address(controller.daiUsds()), vars.daiUsds);
151 | assertEq(address(controller.cctp()), vars.cctp);
152 | assertEq(address(controller.dai()), makeAddr("dai")); // Dai param in MockDaiUsds
153 | assertEq(address(controller.usdc()), makeAddr("usdc")); // Gem param in MockPSM
154 |
155 | assertEq(controller.psmTo18ConversionFactor(), 1e12);
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/test/unit/mocks/MockDaiUsds.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | contract MockDaiUsds {
5 |
6 | address public dai;
7 |
8 | constructor(address _dai) {
9 | dai = _dai;
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/test/unit/mocks/MockPSM.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | contract MockPSM {
5 |
6 | address public gem;
7 |
8 | uint256 public to18ConversionFactor = 1e12;
9 |
10 | constructor(address _gem) {
11 | gem = _gem;
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/test/unit/mocks/MockPSM3.sol:
--------------------------------------------------------------------------------
1 |
2 | // SPDX-License-Identifier: AGPL-3.0-or-later
3 | pragma solidity ^0.8.21;
4 |
5 | contract MockPSM3 {
6 |
7 | address public asset0;
8 | address public asset1;
9 | address public asset2;
10 |
11 | constructor(address _asset0, address _asset1, address _asset2) {
12 | asset0 = _asset0;
13 | asset1 = _asset1;
14 | asset2 = _asset2;
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/test/unit/mocks/MockTarget.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | contract MockTarget {
5 |
6 | event ExampleEvent(
7 | address indexed exampleAddress,
8 | uint256 exampleValue,
9 | uint256 exampleReturn,
10 | address caller,
11 | uint256 value
12 | );
13 |
14 | function exampleCall(address exampleAddress, uint256 exampleValue)
15 | public payable returns (uint256 exampleReturn)
16 | {
17 | exampleReturn = exampleValue * 2;
18 | emit ExampleEvent(
19 | exampleAddress,
20 | exampleValue,
21 | exampleReturn,
22 | msg.sender,
23 | msg.value
24 | );
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/test/unit/mocks/MockVault.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | contract MockVault {
5 |
6 | address public buffer;
7 |
8 | constructor(address _buffer) {
9 | buffer = _buffer;
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/test/unit/proxy/Constructor.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { ALMProxy } from "../../../src/ALMProxy.sol";
5 |
6 | import "../UnitTestBase.t.sol";
7 |
8 | contract ALMProxyConstructorTests is UnitTestBase {
9 |
10 | function test_constructor() public {
11 | ALMProxy newAlmProxy = new ALMProxy(admin);
12 |
13 | assertEq(newAlmProxy.hasRole(DEFAULT_ADMIN_ROLE, admin), true);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/test/unit/proxy/DoCall.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { ALMProxy } from "../../../src/ALMProxy.sol";
5 |
6 | import { MockTarget } from "../mocks/MockTarget.sol";
7 |
8 | import "../UnitTestBase.t.sol";
9 |
10 | contract ALMProxyCallTestBase is UnitTestBase {
11 |
12 | event ExampleEvent(
13 | address indexed exampleAddress,
14 | uint256 exampleValue,
15 | uint256 exampleReturn,
16 | address caller,
17 | uint256 value
18 | );
19 |
20 | ALMProxy almProxy;
21 |
22 | address target;
23 |
24 | address controller = makeAddr("controller");
25 | address exampleAddress = makeAddr("exampleAddress");
26 |
27 | bytes data = abi.encodeWithSignature(
28 | "exampleCall(address,uint256)",
29 | exampleAddress,
30 | 42
31 | );
32 |
33 | function setUp() public {
34 | almProxy = new ALMProxy(admin);
35 |
36 | vm.prank(admin);
37 | almProxy.grantRole(CONTROLLER, controller);
38 |
39 | target = address(new MockTarget());
40 | }
41 |
42 | }
43 |
44 | contract ALMProxyDoCallFailureTests is ALMProxyCallTestBase {
45 |
46 | function test_doCall_unauthorizedAccount() public {
47 | vm.expectRevert(abi.encodeWithSignature(
48 | "AccessControlUnauthorizedAccount(address,bytes32)",
49 | address(this),
50 | CONTROLLER
51 | ));
52 | almProxy.doCall(target, data);
53 |
54 | vm.prank(admin);
55 | vm.expectRevert(abi.encodeWithSignature(
56 | "AccessControlUnauthorizedAccount(address,bytes32)",
57 | admin,
58 | CONTROLLER
59 | ));
60 | almProxy.doCall(target, data);
61 | }
62 |
63 | }
64 |
65 | contract ALMProxyDoCallTests is ALMProxyCallTestBase {
66 |
67 | function test_doCall() public {
68 | // ALM Proxy is msg.sender, target emits the event
69 | vm.expectEmit(target);
70 | emit ExampleEvent(exampleAddress, 42, 84, address(almProxy), 0);
71 | vm.prank(controller);
72 | bytes memory returnData = almProxy.doCall(target, data);
73 |
74 | assertEq(abi.decode(returnData, (uint256)), 84);
75 | }
76 |
77 | }
78 |
79 | contract ALMProxyDoCallWithValueFailureTests is ALMProxyCallTestBase {
80 |
81 | function test_doCallWithValue_unauthorizedAccount() public {
82 | vm.expectRevert(abi.encodeWithSignature(
83 | "AccessControlUnauthorizedAccount(address,bytes32)",
84 | address(this),
85 | CONTROLLER
86 | ));
87 | almProxy.doCallWithValue(target, data, 1e18);
88 |
89 | vm.prank(admin);
90 | vm.expectRevert(abi.encodeWithSignature(
91 | "AccessControlUnauthorizedAccount(address,bytes32)",
92 | admin,
93 | CONTROLLER
94 | ));
95 | almProxy.doCallWithValue(target, data, 1e18);
96 | }
97 |
98 | function test_doCallWithValue_notEnoughBalanceBoundary() public {
99 | vm.deal(address(almProxy), 1e18 - 1);
100 |
101 | vm.startPrank(controller);
102 |
103 | vm.expectRevert(abi.encodeWithSignature(
104 | "AddressInsufficientBalance(address)",
105 | address(almProxy)
106 | ));
107 | almProxy.doCallWithValue(target, data, 1e18);
108 |
109 | vm.deal(address(almProxy), 1e18);
110 |
111 | almProxy.doCallWithValue(target, data, 1e18);
112 | }
113 |
114 | }
115 |
116 | contract ALMProxyDoCallWithValueTests is ALMProxyCallTestBase {
117 |
118 | function test_doCallWithValue() public {
119 | vm.deal(address(almProxy), 1e18);
120 |
121 | // ALM Proxy is msg.sender, target emits the event
122 | vm.expectEmit(target);
123 | emit ExampleEvent(exampleAddress, 42, 84, address(almProxy), 1e18);
124 | vm.prank(controller);
125 | bytes memory returnData = almProxy.doCallWithValue(target, data, 1e18);
126 |
127 | assertEq(abi.decode(returnData, (uint256)), 84);
128 | }
129 |
130 | function test_doCallWithValue_msgValue() public {
131 | vm.deal(controller, 1e18);
132 |
133 | // ALM Proxy is msg.sender, target emits the event, msg.value sent to proxy then target
134 | vm.expectEmit(target);
135 | emit ExampleEvent(exampleAddress, 42, 84, address(almProxy), 1e18);
136 | vm.prank(controller);
137 | bytes memory returnData = almProxy.doCallWithValue{value: 1e18}(target, data, 1e18);
138 |
139 | assertEq(abi.decode(returnData, (uint256)), 84);
140 | }
141 |
142 | }
143 |
144 | contract ALMProxyDoDelegateCallFailureTests is ALMProxyCallTestBase {
145 |
146 | function test_doDelegateCall_unauthorizedAccount() public {
147 | vm.expectRevert(abi.encodeWithSignature(
148 | "AccessControlUnauthorizedAccount(address,bytes32)",
149 | address(this),
150 | CONTROLLER
151 | ));
152 | almProxy.doDelegateCall(target, data);
153 |
154 | vm.prank(admin);
155 | vm.expectRevert(abi.encodeWithSignature(
156 | "AccessControlUnauthorizedAccount(address,bytes32)",
157 | admin,
158 | CONTROLLER
159 | ));
160 | almProxy.doDelegateCall(target, data);
161 | }
162 |
163 | }
164 |
165 | contract ALMProxyDoDelegateCallTests is ALMProxyCallTestBase {
166 |
167 | function test_doDelegateCall() public {
168 | // L1 Controller is msg.sender, almProxy emits the event
169 | vm.expectEmit(address(almProxy));
170 | emit ExampleEvent(exampleAddress, 42, 84, controller, 0);
171 | vm.prank(controller);
172 | bytes memory returnData = almProxy.doDelegateCall(target, data);
173 |
174 | assertEq(abi.decode(returnData, (uint256)), 84);
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/test/unit/proxy/ReceiveEth.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import { ALMProxy } from "../../../src/ALMProxy.sol";
5 |
6 | import "../UnitTestBase.t.sol";
7 |
8 | contract ALMProxyReceiveEthTests is UnitTestBase {
9 |
10 | function test_receiveEth() public {
11 | ALMProxy almProxy = new ALMProxy(admin);
12 |
13 | deal(address(this), 10 ether);
14 |
15 | assertEq(address(this).balance, 10 ether);
16 | assertEq(address(almProxy).balance, 0);
17 |
18 | payable(address(almProxy)).transfer(10 ether);
19 |
20 | assertEq(address(this).balance, 0);
21 | assertEq(address(almProxy).balance, 10 ether);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/test/unit/rate-limits/RateLimitHelpers.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.21;
3 |
4 | import "../UnitTestBase.t.sol";
5 |
6 | import { RateLimits, IRateLimits } from "../../../src/RateLimits.sol";
7 | import { RateLimitHelpers } from "../../../src/RateLimitHelpers.sol";
8 |
9 | contract RateLimitHelpersWrapper {
10 |
11 | function makeAssetKey(bytes32 key, address asset) public pure returns (bytes32) {
12 | return RateLimitHelpers.makeAssetKey(key, asset);
13 | }
14 |
15 | function makeAssetDestinationKey(bytes32 key, address asset, address destination) public pure returns (bytes32) {
16 | return RateLimitHelpers.makeAssetDestinationKey(key, asset, destination);
17 | }
18 |
19 | function makeDomainKey(bytes32 key, uint32 domain) public pure returns (bytes32) {
20 | return RateLimitHelpers.makeDomainKey(key, domain);
21 | }
22 |
23 | }
24 |
25 | contract RateLimitHelpersTestBase is UnitTestBase {
26 |
27 | bytes32 constant KEY = "KEY";
28 | string constant NAME = "NAME";
29 |
30 | address controller = makeAddr("controller");
31 |
32 | RateLimits rateLimits;
33 | RateLimitHelpersWrapper wrapper;
34 |
35 | function setUp() public {
36 | // Set wrapper as admin so it can set rate limits
37 | wrapper = new RateLimitHelpersWrapper();
38 | rateLimits = new RateLimits(address(wrapper));
39 | }
40 |
41 | function _assertLimitData(
42 | bytes32 key,
43 | uint256 maxAmount,
44 | uint256 slope,
45 | uint256 lastAmount,
46 | uint256 lastUpdated
47 | )
48 | internal view
49 | {
50 | IRateLimits.RateLimitData memory d = rateLimits.getRateLimitData(key);
51 |
52 | assertEq(d.maxAmount, maxAmount);
53 | assertEq(d.slope, slope);
54 | assertEq(d.lastAmount, lastAmount);
55 | assertEq(d.lastUpdated, lastUpdated);
56 | }
57 |
58 | }
59 |
60 | contract RateLimitHelpersPureFunctionTests is RateLimitHelpersTestBase {
61 |
62 | function test_makeAssetKey() public view {
63 | assertEq(
64 | wrapper.makeAssetKey(KEY, address(this)),
65 | keccak256(abi.encode(KEY, address(this)))
66 | );
67 | }
68 |
69 | function test_makeAssetDestinationKey() public view {
70 | assertEq(
71 | wrapper.makeAssetDestinationKey(KEY, address(this), address(0)),
72 | keccak256(abi.encode(KEY, address(this), address(0)))
73 | );
74 | }
75 |
76 | function test_makeDomainKey() public view {
77 | assertEq(
78 | wrapper.makeDomainKey(KEY, 123),
79 | keccak256(abi.encode(KEY, 123))
80 | );
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------