diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index b3a563ec4..374a58bae 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -8,18 +8,12 @@ on: options: - fuji - mainnet - - goerli - sepolia - - mumbai - polygon - arbitrum - - arbitrum-goerli - base - - base-goerli - - linea-goerli - optimism - mantle - - scroll-goerli - scroll deployment: description: Deployment Name (e.g. "usdc") @@ -44,13 +38,14 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 3d74677e2..9dfa8079a 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -8,18 +8,12 @@ on: options: - fuji - mainnet - - goerli - sepolia - - mumbai - polygon - arbitrum - - arbitrum-goerli - base - - base-goerli - - linea-goerli - optimism - mantle - - scroll-goerli - scroll deployment: description: Deployment Name (e.g. "usdc") @@ -61,14 +55,15 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Get governance network run: | case ${{ github.event.inputs.network }} in - polygon | arbitrum | base | optimism | mantle) + polygon | arbitrum | base | optimism | mantle | scroll) echo "GOV_NETWORK=mainnet" >> $GITHUB_ENV ;; - mumbai | arbitrum-goerli | base-goerli | linea-goerli | scroll-goerli | scroll) - echo "GOV_NETWORK=goerli" >> $GITHUB_ENV ;; + sepolia) + echo "GOV_NETWORK=sepolia" >> $GITHUB_ENV ;; *) echo "No governance network for selected network" ;; esac @@ -78,7 +73,7 @@ jobs: with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' @@ -87,7 +82,7 @@ jobs: with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ env.GOV_NETWORK }}" - ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\"}')[env.GOV_NETWORK] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\"}')[env.GOV_NETWORK] }}" port: 8685 if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' && github.event.inputs.impersonateAccount == '' @@ -113,7 +108,7 @@ jobs: run_id: ${{ github.event.inputs.run_id }} name: ${{ github.event.inputs.network }}-${{ github.event.inputs.deployment }}-${{ github.event.inputs.migration }} path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/ - if: github.event.inputs.run_id != '' + if: github.event.inputs.run_id != '' && github.event.inputs.run_id != 0 - name: Run Deploy Market and Enact Migration (impersonate) run: | @@ -147,6 +142,17 @@ jobs: GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} GOV_NETWORK: ${{ env.GOV_NETWORK }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} + if: github.event.inputs.impersonateAccount != '' && github.event.inputs.run_id != 0 + - name: Run Prepare and Enact Migration (impersonate) + run: | + yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --prepare --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} + env: + DEBUG: true + ETH_PK: "${{ inputs.eth_pk }}" + NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} + GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} + GOV_NETWORK: ${{ env.GOV_NETWORK }} + REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'false' - name: Commit changes if: ${{ github.event.inputs.simulate == 'false' }} diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index 88a156cf6..9bd5f8334 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -8,14 +8,10 @@ on: options: - fuji - mainnet - - goerli - sepolia - - mumbai - polygon - arbitrum - - arbitrum-goerli - base - - base-goerli - optimism - mantle deployment: @@ -44,13 +40,14 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://rpc.ankr.com/optimism/$ANKR_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://rpc.ankr.com/base/$ANKR_KEY\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' diff --git a/.github/workflows/run-contract-linter.yaml b/.github/workflows/run-contract-linter.yaml index 731cb5e1e..cc840f887 100644 --- a/.github/workflows/run-contract-linter.yaml +++ b/.github/workflows/run-contract-linter.yaml @@ -16,6 +16,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/run-coverage.yaml b/.github/workflows/run-coverage.yaml index cb3672b14..7eff80fba 100644 --- a/.github/workflows/run-coverage.yaml +++ b/.github/workflows/run-coverage.yaml @@ -18,6 +18,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-eslint.yaml b/.github/workflows/run-eslint.yaml index 3e9a3e05c..42b758479 100644 --- a/.github/workflows/run-eslint.yaml +++ b/.github/workflows/run-eslint.yaml @@ -16,6 +16,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-forge-tests.yaml b/.github/workflows/run-forge-tests.yaml index 42b4dcbed..1c343ea1b 100644 --- a/.github/workflows/run-forge-tests.yaml +++ b/.github/workflows/run-forge-tests.yaml @@ -21,7 +21,7 @@ jobs: run: forge install - name: Run tests - run: forge test -vvv --via-ir + run: forge test -vvv --via-ir --optimizer-runs 1 env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} @@ -31,7 +31,8 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} - name: Build Comet with older solc versions run: | - forge build --contracts contracts/Comet.sol --use solc:0.8.15 --via-ir + forge build --contracts contracts/Comet.sol --use solc:0.8.15 --via-ir --optimizer-runs 1 diff --git a/.github/workflows/run-gas-profiler.yaml b/.github/workflows/run-gas-profiler.yaml index 62fb5879e..fb6718e73 100644 --- a/.github/workflows/run-gas-profiler.yaml +++ b/.github/workflows/run-gas-profiler.yaml @@ -17,6 +17,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 4446b0b42..49d63b2a1 100644 --- a/.github/workflows/run-scenarios.yaml +++ b/.github/workflows/run-scenarios.yaml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, mantle-usde, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, sepolia-usdc, sepolia-weth, fuji, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, base-usdbc, base-weth, base-usdc, base-aero, optimism-usdc, optimism-usdt, optimism-weth, mantle-usde, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} @@ -20,6 +20,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/run-semgrep.yaml b/.github/workflows/run-semgrep.yaml index 533539d05..e329c9813 100644 --- a/.github/workflows/run-semgrep.yaml +++ b/.github/workflows/run-semgrep.yaml @@ -21,6 +21,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} container: # A Docker image with Semgrep installed. Do not change this. image: returntocorp/semgrep diff --git a/.github/workflows/run-slither.yaml b/.github/workflows/run-slither.yaml index 2ca5a871d..80dfa2f8c 100644 --- a/.github/workflows/run-slither.yaml +++ b/.github/workflows/run-slither.yaml @@ -28,4 +28,4 @@ jobs: run: solc-select install 0.8.15;solc-select use 0.8.15 - name: Function clash analysis - run: yarn slither:fn-clashes &> tmp_res; cat tmp_res | grep 'Function id collision found' && exit 1 || exit 0 + run: yarn slither:fn-clashes &> tmp_res; cat tmp_res | grep 'Function id collision found' && exit 1 || exit 0 \ No newline at end of file diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index 452207832..a5e2d681d 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -16,6 +16,7 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} + SCROLLSCAN_KEY: ${{ secrets.SCROLLSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 702c8e297..b4dc00289 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -7,10 +7,10 @@ Migrations are simple scripts which deploy or modify contracts. The goal of migr To create a new migration, run: ```sh -yarn hardhat gen:migration --network goerli --deployment usdc my_migration +yarn hardhat gen:migration --network sepolia --deployment usdc my_migration ``` -This will create a new file, such as `deployments/goerli/usdc/migrations/164443237_my_migration.ts` with a base migration script. There are currently two steps to a migration script, but this is likely to change soon: +This will create a new file, such as `deployments/sepolia/usdc/migrations/164443237_my_migration.ts` with a base migration script. There are currently two steps to a migration script, but this is likely to change soon: 1. Prepare: steps used to create artifacts, such as new on-chain contracts. The output from this step is stored (e.g. "NewCometImplementation: 0x...") 2. Enact: steps used to make these artifacts current, such as upgrading the proxy to the address from the previous step. @@ -20,31 +20,31 @@ This will create a new file, such as `deployments/goerli/usdc/migrations/1644432 You can run the preparation for a migration locally via: ```sh -yarn hardhat migrate --network goerli --deployment usdc --prepare 164443237_my_migration +yarn hardhat migrate --network sepolia --deployment usdc --prepare 164443237_my_migration ``` or the enactment via: ```sh -yarn hardhat migrate --network goerli --deployment usdc --enact 164443237_my_migration +yarn hardhat migrate --network sepolia --deployment usdc --enact 164443237_my_migration ``` or both preparation and enactment via: ```sh -yarn hardhat migrate --network goerli --deployment usdc --prepare --enact 164443237_my_migration +yarn hardhat migrate --network sepolia --deployment usdc --prepare --enact 164443237_my_migration ``` Also, you can simulate either of the previous steps to see what effect they would have without actually modifying the on-chain state: ```sh -yarn hardhat migrate --network goerli --deployment usdc --prepare --simulate 164443237_my_migration +yarn hardhat migrate --network sepolia --deployment usdc --prepare --simulate 164443237_my_migration ``` When simulating a migration, you can also impersonate an address to run the migration as. This can be helpful when trying to test a migration that makes a proposal, which requires an address with enough COMP: ```sh -yarn hardhat migrate --network goerli --deployment usdc --prepare --simulate --impersonate ADDRESS_TO_IMPERSONATE 164443237_my_migration +yarn hardhat migrate --network sepolia --deployment usdc --prepare --simulate --impersonate ADDRESS_TO_IMPERSONATE 164443237_my_migration ``` ## Running a Migration in GitHub @@ -53,7 +53,7 @@ The preferred way to run a migration is in GitHub, via manual workflow dispatch. ## Migration Artifacts -After preparation, a migration stores some artifacts under `deployments/goerli/usdc/artifacts/164443237_my_migration.json`. These will be loaded and can be referenced in the enact step of that migration. +After preparation, a migration stores some artifacts under `deployments/sepolia/usdc/artifacts/164443237_my_migration.json`. These will be loaded and can be referenced in the enact step of that migration. ## Testing Migrations diff --git a/README.md b/README.md index 64f799814..cb8b93978 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ This can also be used together with `--overwrite`, to produce the verification a #### Other considerations Make sure that the deploying address has a sufficient amount of the chain's -native asset (i.e. 2 ETH for Goerli, 2 AVAX for Fuji) +native asset (i.e. 2 ETH for Sepolia, 2 AVAX for Fuji) ### Clone Multisig @@ -277,7 +277,7 @@ Uniswap for a profit. To run the bot, you'll need the address of a deployed version of the Liquidator contract (or you can deploy a new instance of it yourself): -`LIQUIDATOR_ADDRESS="0xABC..." DEPLOYMENT="usdc" yarn liquidation-bot --network goerli` +`LIQUIDATOR_ADDRESS="0xABC..." DEPLOYMENT="usdc" yarn liquidation-bot --network sepolia` Initiating transactions this way via the public mempool will [almost certainly get frontrun](https://youtu.be/UZ-NNd6yjFM), but you might be diff --git a/SCENARIO.md b/SCENARIO.md index 8bbc439f3..da681cbfa 100644 --- a/SCENARIO.md +++ b/SCENARIO.md @@ -8,7 +8,7 @@ Scenarios are high-level property and ad-hoc tests for the Comet protocol. To ru You can run scenarios against a given base as: -`npx hardhat scenario --bases development,goerli,fuji` +`npx hardhat scenario --bases development,sepolia,fuji` You can run spider persistently first if you wish: diff --git a/contracts/AssetList.sol b/contracts/AssetList.sol new file mode 100644 index 000000000..467de215c --- /dev/null +++ b/contracts/AssetList.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./IPriceFeed.sol"; +import "./IERC20NonStandard.sol"; +import "./CometMainInterface.sol"; +import "./CometCore.sol"; + +/** + * @title Compound's Asset List + * @author Compound + */ +contract AssetList { + /// @dev The decimals required for a price feed + uint8 internal constant PRICE_FEED_DECIMALS = 8; + + /// @dev The scale for factors + uint64 internal constant FACTOR_SCALE = 1e18; + + /// @dev The max value for a collateral factor (1) + uint64 internal constant MAX_COLLATERAL_FACTOR = FACTOR_SCALE; + + uint256[] internal assets_a; + uint256[] internal assets_b; + + /// @notice The number of assets this contract actually supports + uint8 public immutable numAssets; + + constructor(CometConfiguration.AssetConfig[] memory assetConfigs) { + uint8 _numAssets = uint8(assetConfigs.length); + numAssets = _numAssets; + uint256[] memory _assets_a = new uint256[](_numAssets); + uint256[] memory _assets_b = new uint256[](_numAssets); + for (uint i = 0; i < _numAssets; ) { + (uint256 asset_a, uint256 asset_b) = getPackedAssetInternal(assetConfigs, i); + _assets_a[i] = asset_a; + _assets_b[i] = asset_b; + unchecked { i++; } + } + assets_a = _assets_a; + assets_b = _assets_b; + } + + /** + * @dev Checks and gets the packed asset info for storage + */ + function getPackedAssetInternal(CometConfiguration.AssetConfig[] memory assetConfigs, uint i) internal view returns (uint256, uint256) { + CometConfiguration.AssetConfig memory assetConfig; + if (i < assetConfigs.length) { + assembly { + assetConfig := mload(add(add(assetConfigs, 0x20), mul(i, 0x20))) + } + } else { + return (0, 0); + } + address asset = assetConfig.asset; + address priceFeed = assetConfig.priceFeed; + uint8 decimals_ = assetConfig.decimals; + + // Short-circuit if asset is nil + if (asset == address(0)) { + return (0, 0); + } + + // Sanity check price feed and asset decimals + if (IPriceFeed(priceFeed).decimals() != PRICE_FEED_DECIMALS) revert CometMainInterface.BadDecimals(); + if (IERC20NonStandard(asset).decimals() != decimals_) revert CometMainInterface.BadDecimals(); + + // Ensure collateral factors are within range + if (assetConfig.borrowCollateralFactor >= assetConfig.liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); + if (assetConfig.liquidateCollateralFactor > MAX_COLLATERAL_FACTOR) revert CometMainInterface.LiquidateCFTooLarge(); + + unchecked { + // Keep 4 decimals for each factor + uint64 descale = FACTOR_SCALE / 1e4; + uint16 borrowCollateralFactor = uint16(assetConfig.borrowCollateralFactor / descale); + uint16 liquidateCollateralFactor = uint16(assetConfig.liquidateCollateralFactor / descale); + uint16 liquidationFactor = uint16(assetConfig.liquidationFactor / descale); + + // Be nice and check descaled values are still within range + if (borrowCollateralFactor >= liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); + + // Keep whole units of asset for supply cap + uint64 supplyCap = uint64(assetConfig.supplyCap / (10 ** decimals_)); + + uint256 word_a = (uint160(asset) << 0 | + uint256(borrowCollateralFactor) << 160 | + uint256(liquidateCollateralFactor) << 176 | + uint256(liquidationFactor) << 192); + uint256 word_b = (uint160(priceFeed) << 0 | + uint256(decimals_) << 160 | + uint256(supplyCap) << 168); + + return (word_a, word_b); + } + } + + /** + * @notice Get the i-th asset info, according to the order they were passed in originally + * @param i The index of the asset info to get + * @return The asset info object + */ + function getAssetInfo(uint8 i) public view returns (CometCore.AssetInfo memory) { + if (i >= numAssets) revert CometMainInterface.BadAsset(); + + uint256 word_a = assets_a[i]; + uint256 word_b = assets_b[i]; + + address asset = address(uint160(word_a & type(uint160).max)); + uint64 rescale = FACTOR_SCALE / 1e4; + uint64 borrowCollateralFactor = uint64(((word_a >> 160) & type(uint16).max) * rescale); + uint64 liquidateCollateralFactor = uint64(((word_a >> 176) & type(uint16).max) * rescale); + uint64 liquidationFactor = uint64(((word_a >> 192) & type(uint16).max) * rescale); + + address priceFeed = address(uint160(word_b & type(uint160).max)); + uint8 decimals_ = uint8(((word_b >> 160) & type(uint8).max)); + uint64 scale = uint64(10 ** decimals_); + uint128 supplyCap = uint128(((word_b >> 168) & type(uint64).max) * scale); + + return CometCore.AssetInfo({ + offset: i, + asset: asset, + priceFeed: priceFeed, + scale: scale, + borrowCollateralFactor: borrowCollateralFactor, + liquidateCollateralFactor: liquidateCollateralFactor, + liquidationFactor: liquidationFactor, + supplyCap: supplyCap + }); + } +} \ No newline at end of file diff --git a/contracts/AssetListFactory.sol b/contracts/AssetListFactory.sol new file mode 100644 index 000000000..52c190adc --- /dev/null +++ b/contracts/AssetListFactory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./AssetList.sol"; + +/** + * @title Compound's Asset List Factory + * @author Compound + */ +contract AssetListFactory { + event AssetListCreated(address indexed assetList, CometCore.AssetConfig[] assetConfigs); + + function createAssetList(CometCore.AssetConfig[] memory assetConfigs) external returns (address assetList) { + assetList = address(new AssetList(assetConfigs)); + emit AssetListCreated(assetList, assetConfigs); + } +} \ No newline at end of file diff --git a/contracts/CometExt.sol b/contracts/CometExt.sol index 46962b3ad..a5a75c68e 100644 --- a/contracts/CometExt.sol +++ b/contracts/CometExt.sol @@ -44,7 +44,7 @@ contract CometExt is CometExtInterface { function baseIndexScale() override external pure returns (uint64) { return BASE_INDEX_SCALE; } function factorScale() override external pure returns (uint64) { return FACTOR_SCALE; } function priceScale() override external pure returns (uint64) { return PRICE_SCALE; } - function maxAssets() override external pure returns (uint8) { return MAX_ASSETS; } + function maxAssets() override virtual external pure returns (uint8) { return MAX_ASSETS; } /** * @notice Aggregate variables tracked for the entire market diff --git a/contracts/CometExtAssetList.sol b/contracts/CometExtAssetList.sol new file mode 100644 index 000000000..bd6d454c0 --- /dev/null +++ b/contracts/CometExtAssetList.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./CometExt.sol"; + +contract CometExtAssetList is CometExt { + + /// @notice The address of the asset list factory + address immutable public assetListFactory; + + /** + * @notice Construct a new protocol instance + * @param config The mapping of initial/constant parameters + * @param assetListFactoryAddress The address of the asset list factory + **/ + constructor(ExtConfiguration memory config, address assetListFactoryAddress) CometExt(config) { + assetListFactory = assetListFactoryAddress; + } + + uint8 internal constant MAX_ASSETS_FOR_ASSET_LIST = 24; + + function maxAssets() override external pure returns (uint8) { return MAX_ASSETS_FOR_ASSET_LIST; } +} \ No newline at end of file diff --git a/contracts/CometExtendedAssetList.sol b/contracts/CometExtendedAssetList.sol new file mode 100644 index 000000000..71af9774d --- /dev/null +++ b/contracts/CometExtendedAssetList.sol @@ -0,0 +1,1253 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./CometMainInterface.sol"; +import "./IERC20NonStandard.sol"; +import "./IPriceFeed.sol"; +import "./IAssetListFactory.sol"; +import "./IAssetListFactoryHolder.sol"; +import "./IAssetList.sol"; + +/** + * @title Compound's Comet Contract + * @notice An efficient monolithic money market protocol + * @author Compound + */ +contract CometExtendedAssetList is CometMainInterface { + /** General configuration constants **/ + + /// @notice The admin of the protocol + address public override immutable governor; + + /// @notice The account which may trigger pauses + address public override immutable pauseGuardian; + + /// @notice The address of the base token contract + address public override immutable baseToken; + + /// @notice The address of the price feed for the base token + address public override immutable baseTokenPriceFeed; + + /// @notice The address of the extension contract delegate + address public override immutable extensionDelegate; + + /// @notice The point in the supply rates separating the low interest rate slope and the high interest rate slope (factor) + /// @dev uint64 + uint public override immutable supplyKink; + + /// @notice Per second supply interest rate slope applied when utilization is below kink (factor) + /// @dev uint64 + uint public override immutable supplyPerSecondInterestRateSlopeLow; + + /// @notice Per second supply interest rate slope applied when utilization is above kink (factor) + /// @dev uint64 + uint public override immutable supplyPerSecondInterestRateSlopeHigh; + + /// @notice Per second supply base interest rate (factor) + /// @dev uint64 + uint public override immutable supplyPerSecondInterestRateBase; + + /// @notice The point in the borrow rate separating the low interest rate slope and the high interest rate slope (factor) + /// @dev uint64 + uint public override immutable borrowKink; + + /// @notice Per second borrow interest rate slope applied when utilization is below kink (factor) + /// @dev uint64 + uint public override immutable borrowPerSecondInterestRateSlopeLow; + + /// @notice Per second borrow interest rate slope applied when utilization is above kink (factor) + /// @dev uint64 + uint public override immutable borrowPerSecondInterestRateSlopeHigh; + + /// @notice Per second borrow base interest rate (factor) + /// @dev uint64 + uint public override immutable borrowPerSecondInterestRateBase; + + /// @notice The fraction of the liquidation penalty that goes to buyers of collateral instead of the protocol + /// @dev uint64 + uint public override immutable storeFrontPriceFactor; + + /// @notice The scale for base token (must be less than 18 decimals) + /// @dev uint64 + uint public override immutable baseScale; + + /// @notice The scale for reward tracking + /// @dev uint64 + uint public override immutable trackingIndexScale; + + /// @notice The speed at which supply rewards are tracked (in trackingIndexScale) + /// @dev uint64 + uint public override immutable baseTrackingSupplySpeed; + + /// @notice The speed at which borrow rewards are tracked (in trackingIndexScale) + /// @dev uint64 + uint public override immutable baseTrackingBorrowSpeed; + + /// @notice The minimum amount of base principal wei for rewards to accrue + /// @dev This must be large enough so as to prevent division by base wei from overflowing the 64 bit indices + /// @dev uint104 + uint public override immutable baseMinForRewards; + + /// @notice The minimum base amount required to initiate a borrow + uint public override immutable baseBorrowMin; + + /// @notice The minimum base token reserves which must be held before collateral is hodled + uint public override immutable targetReserves; + + /// @notice The number of decimals for wrapped base token + uint8 public override immutable decimals; + + /// @notice The number of assets this contract actually supports + uint8 public override immutable numAssets; + + /// @notice Factor to divide by when accruing rewards in order to preserve 6 decimals (i.e. baseScale / 1e6) + uint internal immutable accrualDescaleFactor; + + /// @notice The address of the asset list + address immutable public assetList; + + uint8 internal constant MAX_ASSETS_FOR_ASSET_LIST = 24; + + /** + * @notice Construct a new protocol instance + * @param config The mapping of initial/constant parameters + **/ + constructor(Configuration memory config) { + // Sanity checks + uint8 decimals_ = IERC20NonStandard(config.baseToken).decimals(); + if (decimals_ > MAX_BASE_DECIMALS) revert BadDecimals(); + if (config.storeFrontPriceFactor > FACTOR_SCALE) revert BadDiscount(); + if (config.assetConfigs.length > MAX_ASSETS_FOR_ASSET_LIST) revert TooManyAssets(); + if (config.baseMinForRewards == 0) revert BadMinimum(); + if (IPriceFeed(config.baseTokenPriceFeed).decimals() != PRICE_FEED_DECIMALS) revert BadDecimals(); + + // Copy configuration + unchecked { + governor = config.governor; + pauseGuardian = config.pauseGuardian; + baseToken = config.baseToken; + baseTokenPriceFeed = config.baseTokenPriceFeed; + extensionDelegate = config.extensionDelegate; + storeFrontPriceFactor = config.storeFrontPriceFactor; + + decimals = decimals_; + baseScale = uint64(10 ** decimals_); + trackingIndexScale = config.trackingIndexScale; + if (baseScale < BASE_ACCRUAL_SCALE) revert BadDecimals(); + accrualDescaleFactor = baseScale / BASE_ACCRUAL_SCALE; + + baseMinForRewards = config.baseMinForRewards; + baseTrackingSupplySpeed = config.baseTrackingSupplySpeed; + baseTrackingBorrowSpeed = config.baseTrackingBorrowSpeed; + + baseBorrowMin = config.baseBorrowMin; + targetReserves = config.targetReserves; + } + + // Set interest rate model configs + unchecked { + supplyKink = config.supplyKink; + supplyPerSecondInterestRateSlopeLow = config.supplyPerYearInterestRateSlopeLow / SECONDS_PER_YEAR; + supplyPerSecondInterestRateSlopeHigh = config.supplyPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR; + supplyPerSecondInterestRateBase = config.supplyPerYearInterestRateBase / SECONDS_PER_YEAR; + borrowKink = config.borrowKink; + borrowPerSecondInterestRateSlopeLow = config.borrowPerYearInterestRateSlopeLow / SECONDS_PER_YEAR; + borrowPerSecondInterestRateSlopeHigh = config.borrowPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR; + borrowPerSecondInterestRateBase = config.borrowPerYearInterestRateBase / SECONDS_PER_YEAR; + } + + // Set asset info + numAssets = uint8(config.assetConfigs.length); + + assetList = IAssetListFactory(IAssetListFactoryHolder(extensionDelegate).assetListFactory()).createAssetList(config.assetConfigs); + } + + /** + * @dev Prevents marked functions from being reentered + * Note: this restrict contracts from calling comet functions in their hooks. + * Doing so will cause the transaction to revert. + */ + modifier nonReentrant() { + nonReentrantBefore(); + _; + nonReentrantAfter(); + } + + /** + * @dev Checks that the reentrancy flag is not set and then sets the flag + */ + function nonReentrantBefore() internal { + bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT; + uint256 status; + assembly ("memory-safe") { + status := sload(slot) + } + + if (status == REENTRANCY_GUARD_ENTERED) revert ReentrantCallBlocked(); + assembly ("memory-safe") { + sstore(slot, REENTRANCY_GUARD_ENTERED) + } + } + + /** + * @dev Unsets the reentrancy flag + */ + function nonReentrantAfter() internal { + bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT; + uint256 status; + assembly ("memory-safe") { + sstore(slot, REENTRANCY_GUARD_NOT_ENTERED) + } + } + + /** + * @notice Initialize storage for the contract + * @dev Can be used from constructor or proxy + */ + function initializeStorage() override external { + if (lastAccrualTime != 0) revert AlreadyInitialized(); + + // Initialize aggregates + lastAccrualTime = getNowInternal(); + baseSupplyIndex = BASE_INDEX_SCALE; + baseBorrowIndex = BASE_INDEX_SCALE; + + // Implicit initialization (not worth increasing contract size) + // trackingSupplyIndex = 0; + // trackingBorrowIndex = 0; + } + + /** + * @notice Get the i-th asset info, according to the order they were passed in originally + * @param i The index of the asset info to get + * @return The asset info object + */ + function getAssetInfo(uint8 i) override public view returns (AssetInfo memory) { + return IAssetList(assetList).getAssetInfo(i); + } + + /** + * @dev Determine index of asset that matches given address + */ + function getAssetInfoByAddress(address asset) override public view returns (AssetInfo memory) { + for (uint8 i = 0; i < numAssets; ) { + AssetInfo memory assetInfo = getAssetInfo(i); + if (assetInfo.asset == asset) { + return assetInfo; + } + unchecked { i++; } + } + revert BadAsset(); + } + + /** + * @return The current timestamp + **/ + function getNowInternal() virtual internal view returns (uint40) { + if (block.timestamp >= 2**40) revert TimestampTooLarge(); + return uint40(block.timestamp); + } + + /** + * @dev Calculate accrued interest indices for base token supply and borrows + **/ + function accruedInterestIndices(uint timeElapsed) internal view returns (uint64, uint64) { + uint64 baseSupplyIndex_ = baseSupplyIndex; + uint64 baseBorrowIndex_ = baseBorrowIndex; + if (timeElapsed > 0) { + uint utilization = getUtilization(); + uint supplyRate = getSupplyRate(utilization); + uint borrowRate = getBorrowRate(utilization); + baseSupplyIndex_ += safe64(mulFactor(baseSupplyIndex_, supplyRate * timeElapsed)); + baseBorrowIndex_ += safe64(mulFactor(baseBorrowIndex_, borrowRate * timeElapsed)); + } + return (baseSupplyIndex_, baseBorrowIndex_); + } + + /** + * @dev Accrue interest (and rewards) in base token supply and borrows + **/ + function accrueInternal() internal { + uint40 now_ = getNowInternal(); + uint timeElapsed = uint256(now_ - lastAccrualTime); + if (timeElapsed > 0) { + (baseSupplyIndex, baseBorrowIndex) = accruedInterestIndices(timeElapsed); + if (totalSupplyBase >= baseMinForRewards) { + trackingSupplyIndex += safe64(divBaseWei(baseTrackingSupplySpeed * timeElapsed, totalSupplyBase)); + } + if (totalBorrowBase >= baseMinForRewards) { + trackingBorrowIndex += safe64(divBaseWei(baseTrackingBorrowSpeed * timeElapsed, totalBorrowBase)); + } + lastAccrualTime = now_; + } + } + + /** + * @notice Accrue interest and rewards for an account + **/ + function accrueAccount(address account) override external { + accrueInternal(); + + UserBasic memory basic = userBasic[account]; + updateBasePrincipal(account, basic, basic.principal); + } + + /** + * @dev Note: Does not accrue interest first + * @param utilization The utilization to check the supply rate for + * @return The per second supply rate at `utilization` + */ + function getSupplyRate(uint utilization) override public view returns (uint64) { + if (utilization <= supplyKink) { + // interestRateBase + interestRateSlopeLow * utilization + return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, utilization)); + } else { + // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink) + return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, supplyKink) + mulFactor(supplyPerSecondInterestRateSlopeHigh, (utilization - supplyKink))); + } + } + + /** + * @dev Note: Does not accrue interest first + * @param utilization The utilization to check the borrow rate for + * @return The per second borrow rate at `utilization` + */ + function getBorrowRate(uint utilization) override public view returns (uint64) { + if (utilization <= borrowKink) { + // interestRateBase + interestRateSlopeLow * utilization + return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, utilization)); + } else { + // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink) + return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, borrowKink) + mulFactor(borrowPerSecondInterestRateSlopeHigh, (utilization - borrowKink))); + } + } + + /** + * @dev Note: Does not accrue interest first + * @return The utilization rate of the base asset + */ + function getUtilization() override public view returns (uint) { + uint totalSupply_ = presentValueSupply(baseSupplyIndex, totalSupplyBase); + uint totalBorrow_ = presentValueBorrow(baseBorrowIndex, totalBorrowBase); + if (totalSupply_ == 0) { + return 0; + } else { + return totalBorrow_ * FACTOR_SCALE / totalSupply_; + } + } + + /** + * @notice Get the current price from a feed + * @param priceFeed The address of a price feed + * @return The price, scaled by `PRICE_SCALE` + */ + function getPrice(address priceFeed) override public view returns (uint256) { + (, int price, , , ) = IPriceFeed(priceFeed).latestRoundData(); + if (price <= 0) revert BadPrice(); + return uint256(price); + } + + /** + * @notice Gets the total balance of protocol collateral reserves for an asset + * @dev Note: Reverts if collateral reserves are somehow negative, which should not be possible + * @param asset The collateral asset + */ + function getCollateralReserves(address asset) override public view returns (uint) { + return IERC20NonStandard(asset).balanceOf(address(this)) - totalsCollateral[asset].totalSupplyAsset; + } + + /** + * @notice Gets the total amount of protocol reserves of the base asset + */ + function getReserves() override public view returns (int) { + (uint64 baseSupplyIndex_, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); + uint balance = IERC20NonStandard(baseToken).balanceOf(address(this)); + uint totalSupply_ = presentValueSupply(baseSupplyIndex_, totalSupplyBase); + uint totalBorrow_ = presentValueBorrow(baseBorrowIndex_, totalBorrowBase); + return signed256(balance) - signed256(totalSupply_) + signed256(totalBorrow_); + } + + /** + * @notice Check whether an account has enough collateral to borrow + * @param account The address to check + * @return Whether the account is minimally collateralized enough to borrow + */ + function isBorrowCollateralized(address account) override public view returns (bool) { + int104 principal = userBasic[account].principal; + + if (principal >= 0) { + return true; + } + + uint16 assetsIn = userBasic[account].assetsIn; + uint8 _reserved = userBasic[account]._reserved; + int liquidity = signedMulPrice( + presentValue(principal), + getPrice(baseTokenPriceFeed), + uint64(baseScale) + ); + + for (uint8 i = 0; i < numAssets; ) { + if (isInAsset(assetsIn, i, _reserved)) { + if (liquidity >= 0) { + return true; + } + + AssetInfo memory asset = getAssetInfo(i); + uint newAmount = mulPrice( + userCollateral[account][asset.asset].balance, + getPrice(asset.priceFeed), + asset.scale + ); + liquidity += signed256(mulFactor( + newAmount, + asset.borrowCollateralFactor + )); + } + unchecked { i++; } + } + + return liquidity >= 0; + } + + /** + * @notice Check whether an account has enough collateral to not be liquidated + * @param account The address to check + * @return Whether the account is minimally collateralized enough to not be liquidated + */ + function isLiquidatable(address account) override public view returns (bool) { + int104 principal = userBasic[account].principal; + + if (principal >= 0) { + return false; + } + + uint16 assetsIn = userBasic[account].assetsIn; + uint8 _reserved = userBasic[account]._reserved; + int liquidity = signedMulPrice( + presentValue(principal), + getPrice(baseTokenPriceFeed), + uint64(baseScale) + ); + + for (uint8 i = 0; i < numAssets; ) { + if (isInAsset(assetsIn, i, _reserved)) { + if (liquidity >= 0) { + return false; + } + + AssetInfo memory asset = getAssetInfo(i); + uint newAmount = mulPrice( + userCollateral[account][asset.asset].balance, + getPrice(asset.priceFeed), + asset.scale + ); + liquidity += signed256(mulFactor( + newAmount, + asset.liquidateCollateralFactor + )); + } + unchecked { i++; } + } + + return liquidity < 0; + } + + /** + * @dev The change in principal broken into repay and supply amounts + */ + function repayAndSupplyAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) { + // If the new principal is less than the old principal, then no amount has been repaid or supplied + if (newPrincipal < oldPrincipal) return (0, 0); + + if (newPrincipal <= 0) { + return (uint104(newPrincipal - oldPrincipal), 0); + } else if (oldPrincipal >= 0) { + return (0, uint104(newPrincipal - oldPrincipal)); + } else { + return (uint104(-oldPrincipal), uint104(newPrincipal)); + } + } + + /** + * @dev The change in principal broken into withdraw and borrow amounts + */ + function withdrawAndBorrowAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) { + // If the new principal is greater than the old principal, then no amount has been withdrawn or borrowed + if (newPrincipal > oldPrincipal) return (0, 0); + + if (newPrincipal >= 0) { + return (uint104(oldPrincipal - newPrincipal), 0); + } else if (oldPrincipal <= 0) { + return (0, uint104(oldPrincipal - newPrincipal)); + } else { + return (uint104(oldPrincipal), uint104(-newPrincipal)); + } + } + + /** + * @notice Pauses different actions within Comet + * @param supplyPaused Boolean for pausing supply actions + * @param transferPaused Boolean for pausing transfer actions + * @param withdrawPaused Boolean for pausing withdraw actions + * @param absorbPaused Boolean for pausing absorb actions + * @param buyPaused Boolean for pausing buy actions + */ + function pause( + bool supplyPaused, + bool transferPaused, + bool withdrawPaused, + bool absorbPaused, + bool buyPaused + ) override external { + if (msg.sender != governor && msg.sender != pauseGuardian) revert Unauthorized(); + + pauseFlags = + uint8(0) | + (toUInt8(supplyPaused) << PAUSE_SUPPLY_OFFSET) | + (toUInt8(transferPaused) << PAUSE_TRANSFER_OFFSET) | + (toUInt8(withdrawPaused) << PAUSE_WITHDRAW_OFFSET) | + (toUInt8(absorbPaused) << PAUSE_ABSORB_OFFSET) | + (toUInt8(buyPaused) << PAUSE_BUY_OFFSET); + + emit PauseAction(supplyPaused, transferPaused, withdrawPaused, absorbPaused, buyPaused); + } + + /** + * @return Whether or not supply actions are paused + */ + function isSupplyPaused() override public view returns (bool) { + return toBool(pauseFlags & (uint8(1) << PAUSE_SUPPLY_OFFSET)); + } + + /** + * @return Whether or not transfer actions are paused + */ + function isTransferPaused() override public view returns (bool) { + return toBool(pauseFlags & (uint8(1) << PAUSE_TRANSFER_OFFSET)); + } + + /** + * @return Whether or not withdraw actions are paused + */ + function isWithdrawPaused() override public view returns (bool) { + return toBool(pauseFlags & (uint8(1) << PAUSE_WITHDRAW_OFFSET)); + } + + /** + * @return Whether or not absorb actions are paused + */ + function isAbsorbPaused() override public view returns (bool) { + return toBool(pauseFlags & (uint8(1) << PAUSE_ABSORB_OFFSET)); + } + + /** + * @return Whether or not buy actions are paused + */ + function isBuyPaused() override public view returns (bool) { + return toBool(pauseFlags & (uint8(1) << PAUSE_BUY_OFFSET)); + } + + /** + * @dev Multiply a number by a factor + */ + function mulFactor(uint n, uint factor) internal pure returns (uint) { + return n * factor / FACTOR_SCALE; + } + + /** + * @dev Divide a number by an amount of base + */ + function divBaseWei(uint n, uint baseWei) internal view returns (uint) { + return n * baseScale / baseWei; + } + + /** + * @dev Multiply a `fromScale` quantity by a price, returning a common price quantity + */ + function mulPrice(uint n, uint price, uint64 fromScale) internal pure returns (uint) { + return n * price / fromScale; + } + + /** + * @dev Multiply a signed `fromScale` quantity by a price, returning a common price quantity + */ + function signedMulPrice(int n, uint price, uint64 fromScale) internal pure returns (int) { + return n * signed256(price) / int256(uint256(fromScale)); + } + + /** + * @dev Divide a common price quantity by a price, returning a `toScale` quantity + */ + function divPrice(uint n, uint price, uint64 toScale) internal pure returns (uint) { + return n * toScale / price; + } + + /** + * @dev Whether user has a non-zero balance of an asset, given assetsIn flags + * @dev _reserved is used to check bits 16-23 of assetsIn + */ + function isInAsset(uint16 assetsIn, uint8 assetOffset, uint8 _reserved) internal pure returns (bool) { + if (assetOffset < 16) { + // check bit in assetsIn (for bits 0-15) + return (assetsIn & (uint16(1) << assetOffset)) != 0; + } else if (assetOffset < 24) { + // check bit in reserved (for bits 16-23) + return (_reserved & (uint8(1) << (assetOffset - 16))) != 0; + } + return false; // if assetOffset >= 24 (should not happen) + } + + /** + * @dev Update assetsIn bit vector if user has entered or exited an asset + */ + function updateAssetsIn( + address account, + AssetInfo memory assetInfo, + uint128 initialUserBalance, + uint128 finalUserBalance + ) internal { + if (initialUserBalance == 0 && finalUserBalance != 0) { + // set bit for asset + if (assetInfo.offset < 16) { + // set bit in assetsIn for bits 0-15 + userBasic[account].assetsIn |= (uint16(1) << assetInfo.offset); + } else if (assetInfo.offset < 24) { + // set bit in _reserved for bits 16-23 + userBasic[account]._reserved |= (uint8(1) << (assetInfo.offset - 16)); + } + } else if (initialUserBalance != 0 && finalUserBalance == 0) { + // clear bit for asset + if (assetInfo.offset < 16) { + // clear bit in assetsIn for bits 0-15 + userBasic[account].assetsIn &= ~(uint16(1) << assetInfo.offset); + } else if (assetInfo.offset < 24) { + // clear bit in _reserved for bits 16-23 + userBasic[account]._reserved &= ~(uint8(1) << (assetInfo.offset - 16)); + } + } + } + + /** + * @dev Write updated principal to store and tracking participation + */ + function updateBasePrincipal(address account, UserBasic memory basic, int104 principalNew) internal { + int104 principal = basic.principal; + basic.principal = principalNew; + + if (principal >= 0) { + uint indexDelta = uint256(trackingSupplyIndex - basic.baseTrackingIndex); + basic.baseTrackingAccrued += safe64(uint104(principal) * indexDelta / trackingIndexScale / accrualDescaleFactor); + } else { + uint indexDelta = uint256(trackingBorrowIndex - basic.baseTrackingIndex); + basic.baseTrackingAccrued += safe64(uint104(-principal) * indexDelta / trackingIndexScale / accrualDescaleFactor); + } + + if (principalNew >= 0) { + basic.baseTrackingIndex = trackingSupplyIndex; + } else { + basic.baseTrackingIndex = trackingBorrowIndex; + } + + userBasic[account] = basic; + } + + /** + * @dev Safe ERC20 transfer in and returns the final amount transferred (taking into account any fees) + * @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferIn(address asset, address from, uint amount) internal returns (uint) { + uint256 preTransferBalance = IERC20NonStandard(asset).balanceOf(address(this)); + IERC20NonStandard(asset).transferFrom(from, address(this), amount); + bool success; + assembly ("memory-safe") { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a compliant ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of override external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + if (!success) revert TransferInFailed(); + return IERC20NonStandard(asset).balanceOf(address(this)) - preTransferBalance; + } + + /** + * @dev Safe ERC20 transfer out + * @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferOut(address asset, address to, uint amount) internal { + IERC20NonStandard(asset).transfer(to, amount); + bool success; + assembly ("memory-safe") { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a compliant ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of override external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + if (!success) revert TransferOutFailed(); + } + + /** + * @notice Supply an amount of asset to the protocol + * @param asset The asset to supply + * @param amount The quantity to supply + */ + function supply(address asset, uint amount) override external { + return supplyInternal(msg.sender, msg.sender, msg.sender, asset, amount); + } + + /** + * @notice Supply an amount of asset to dst + * @param dst The address which will hold the balance + * @param asset The asset to supply + * @param amount The quantity to supply + */ + function supplyTo(address dst, address asset, uint amount) override external { + return supplyInternal(msg.sender, msg.sender, dst, asset, amount); + } + + /** + * @notice Supply an amount of asset from `from` to dst, if allowed + * @param from The supplier address + * @param dst The address which will hold the balance + * @param asset The asset to supply + * @param amount The quantity to supply + */ + function supplyFrom(address from, address dst, address asset, uint amount) override external { + return supplyInternal(msg.sender, from, dst, asset, amount); + } + + /** + * @dev Supply either collateral or base asset, depending on the asset, if operator is allowed + * @dev Note: Specifying an `amount` of uint256.max will repay all of `dst`'s accrued base borrow balance + */ + function supplyInternal(address operator, address from, address dst, address asset, uint amount) internal nonReentrant { + if (isSupplyPaused()) revert Paused(); + if (!hasPermission(from, operator)) revert Unauthorized(); + + if (asset == baseToken) { + if (amount == type(uint256).max) { + amount = borrowBalanceOf(dst); + } + return supplyBase(from, dst, amount); + } else { + return supplyCollateral(from, dst, asset, safe128(amount)); + } + } + + /** + * @dev Supply an amount of base asset from `from` to dst + */ + function supplyBase(address from, address dst, uint256 amount) internal { + amount = doTransferIn(baseToken, from, amount); + + accrueInternal(); + + UserBasic memory dstUser = userBasic[dst]; + int104 dstPrincipal = dstUser.principal; + int256 dstBalance = presentValue(dstPrincipal) + signed256(amount); + int104 dstPrincipalNew = principalValue(dstBalance); + + (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(dstPrincipal, dstPrincipalNew); + + totalSupplyBase += supplyAmount; + totalBorrowBase -= repayAmount; + + updateBasePrincipal(dst, dstUser, dstPrincipalNew); + + emit Supply(from, dst, amount); + + if (supplyAmount > 0) { + emit Transfer(address(0), dst, presentValueSupply(baseSupplyIndex, supplyAmount)); + } + } + + /** + * @dev Supply an amount of collateral asset from `from` to dst + */ + function supplyCollateral(address from, address dst, address asset, uint128 amount) internal { + amount = safe128(doTransferIn(asset, from, amount)); + + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + TotalsCollateral memory totals = totalsCollateral[asset]; + totals.totalSupplyAsset += amount; + if (totals.totalSupplyAsset > assetInfo.supplyCap) revert SupplyCapExceeded(); + + uint128 dstCollateral = userCollateral[dst][asset].balance; + uint128 dstCollateralNew = dstCollateral + amount; + + totalsCollateral[asset] = totals; + userCollateral[dst][asset].balance = dstCollateralNew; + + updateAssetsIn(dst, assetInfo, dstCollateral, dstCollateralNew); + + emit SupplyCollateral(from, dst, asset, amount); + } + + /** + * @notice ERC20 transfer an amount of base token to dst + * @param dst The recipient address + * @param amount The quantity to transfer + * @return true + */ + function transfer(address dst, uint amount) override external returns (bool) { + transferInternal(msg.sender, msg.sender, dst, baseToken, amount); + return true; + } + + /** + * @notice ERC20 transfer an amount of base token from src to dst, if allowed + * @param src The sender address + * @param dst The recipient address + * @param amount The quantity to transfer + * @return true + */ + function transferFrom(address src, address dst, uint amount) override external returns (bool) { + transferInternal(msg.sender, src, dst, baseToken, amount); + return true; + } + + /** + * @notice Transfer an amount of asset to dst + * @param dst The recipient address + * @param asset The asset to transfer + * @param amount The quantity to transfer + */ + function transferAsset(address dst, address asset, uint amount) override external { + return transferInternal(msg.sender, msg.sender, dst, asset, amount); + } + + /** + * @notice Transfer an amount of asset from src to dst, if allowed + * @param src The sender address + * @param dst The recipient address + * @param asset The asset to transfer + * @param amount The quantity to transfer + */ + function transferAssetFrom(address src, address dst, address asset, uint amount) override external { + return transferInternal(msg.sender, src, dst, asset, amount); + } + + /** + * @dev Transfer either collateral or base asset, depending on the asset, if operator is allowed + * @dev Note: Specifying an `amount` of uint256.max will transfer all of `src`'s accrued base balance + */ + function transferInternal(address operator, address src, address dst, address asset, uint amount) internal nonReentrant { + if (isTransferPaused()) revert Paused(); + if (!hasPermission(src, operator)) revert Unauthorized(); + if (src == dst) revert NoSelfTransfer(); + + if (asset == baseToken) { + if (amount == type(uint256).max) { + amount = balanceOf(src); + } + return transferBase(src, dst, amount); + } else { + return transferCollateral(src, dst, asset, safe128(amount)); + } + } + + /** + * @dev Transfer an amount of base asset from src to dst, borrowing if possible/necessary + */ + function transferBase(address src, address dst, uint256 amount) internal { + accrueInternal(); + + UserBasic memory srcUser = userBasic[src]; + UserBasic memory dstUser = userBasic[dst]; + + int104 srcPrincipal = srcUser.principal; + int104 dstPrincipal = dstUser.principal; + int256 srcBalance = presentValue(srcPrincipal) - signed256(amount); + int256 dstBalance = presentValue(dstPrincipal) + signed256(amount); + int104 srcPrincipalNew = principalValue(srcBalance); + int104 dstPrincipalNew = principalValue(dstBalance); + + (uint104 withdrawAmount, uint104 borrowAmount) = withdrawAndBorrowAmount(srcPrincipal, srcPrincipalNew); + (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(dstPrincipal, dstPrincipalNew); + + // Note: Instead of `total += addAmount - subAmount` to avoid underflow errors. + totalSupplyBase = totalSupplyBase + supplyAmount - withdrawAmount; + totalBorrowBase = totalBorrowBase + borrowAmount - repayAmount; + + updateBasePrincipal(src, srcUser, srcPrincipalNew); + updateBasePrincipal(dst, dstUser, dstPrincipalNew); + + if (srcBalance < 0) { + if (uint256(-srcBalance) < baseBorrowMin) revert BorrowTooSmall(); + if (!isBorrowCollateralized(src)) revert NotCollateralized(); + } + + if (withdrawAmount > 0) { + emit Transfer(src, address(0), presentValueSupply(baseSupplyIndex, withdrawAmount)); + } + + if (supplyAmount > 0) { + emit Transfer(address(0), dst, presentValueSupply(baseSupplyIndex, supplyAmount)); + } + } + + /** + * @dev Transfer an amount of collateral asset from src to dst + */ + function transferCollateral(address src, address dst, address asset, uint128 amount) internal { + uint128 srcCollateral = userCollateral[src][asset].balance; + uint128 dstCollateral = userCollateral[dst][asset].balance; + uint128 srcCollateralNew = srcCollateral - amount; + uint128 dstCollateralNew = dstCollateral + amount; + + userCollateral[src][asset].balance = srcCollateralNew; + userCollateral[dst][asset].balance = dstCollateralNew; + + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + updateAssetsIn(src, assetInfo, srcCollateral, srcCollateralNew); + updateAssetsIn(dst, assetInfo, dstCollateral, dstCollateralNew); + + // Note: no accrue interest, BorrowCF < LiquidationCF covers small changes + if (!isBorrowCollateralized(src)) revert NotCollateralized(); + + emit TransferCollateral(src, dst, asset, amount); + } + + /** + * @notice Withdraw an amount of asset from the protocol + * @param asset The asset to withdraw + * @param amount The quantity to withdraw + */ + function withdraw(address asset, uint amount) override external { + return withdrawInternal(msg.sender, msg.sender, msg.sender, asset, amount); + } + + /** + * @notice Withdraw an amount of asset to `to` + * @param to The recipient address + * @param asset The asset to withdraw + * @param amount The quantity to withdraw + */ + function withdrawTo(address to, address asset, uint amount) override external { + return withdrawInternal(msg.sender, msg.sender, to, asset, amount); + } + + /** + * @notice Withdraw an amount of asset from src to `to`, if allowed + * @param src The sender address + * @param to The recipient address + * @param asset The asset to withdraw + * @param amount The quantity to withdraw + */ + function withdrawFrom(address src, address to, address asset, uint amount) override external { + return withdrawInternal(msg.sender, src, to, asset, amount); + } + + /** + * @dev Withdraw either collateral or base asset, depending on the asset, if operator is allowed + * @dev Note: Specifying an `amount` of uint256.max will withdraw all of `src`'s accrued base balance + */ + function withdrawInternal(address operator, address src, address to, address asset, uint amount) internal nonReentrant { + if (isWithdrawPaused()) revert Paused(); + if (!hasPermission(src, operator)) revert Unauthorized(); + + if (asset == baseToken) { + if (amount == type(uint256).max) { + amount = balanceOf(src); + } + return withdrawBase(src, to, amount); + } else { + return withdrawCollateral(src, to, asset, safe128(amount)); + } + } + + /** + * @dev Withdraw an amount of base asset from src to `to`, borrowing if possible/necessary + */ + function withdrawBase(address src, address to, uint256 amount) internal { + accrueInternal(); + + UserBasic memory srcUser = userBasic[src]; + int104 srcPrincipal = srcUser.principal; + int256 srcBalance = presentValue(srcPrincipal) - signed256(amount); + int104 srcPrincipalNew = principalValue(srcBalance); + + (uint104 withdrawAmount, uint104 borrowAmount) = withdrawAndBorrowAmount(srcPrincipal, srcPrincipalNew); + + totalSupplyBase -= withdrawAmount; + totalBorrowBase += borrowAmount; + + updateBasePrincipal(src, srcUser, srcPrincipalNew); + + if (srcBalance < 0) { + if (uint256(-srcBalance) < baseBorrowMin) revert BorrowTooSmall(); + if (!isBorrowCollateralized(src)) revert NotCollateralized(); + } + + doTransferOut(baseToken, to, amount); + + emit Withdraw(src, to, amount); + + if (withdrawAmount > 0) { + emit Transfer(src, address(0), presentValueSupply(baseSupplyIndex, withdrawAmount)); + } + } + + /** + * @dev Withdraw an amount of collateral asset from src to `to` + */ + function withdrawCollateral(address src, address to, address asset, uint128 amount) internal { + uint128 srcCollateral = userCollateral[src][asset].balance; + uint128 srcCollateralNew = srcCollateral - amount; + + totalsCollateral[asset].totalSupplyAsset -= amount; + userCollateral[src][asset].balance = srcCollateralNew; + + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + updateAssetsIn(src, assetInfo, srcCollateral, srcCollateralNew); + + // Note: no accrue interest, BorrowCF < LiquidationCF covers small changes + if (!isBorrowCollateralized(src)) revert NotCollateralized(); + + doTransferOut(asset, to, amount); + + emit WithdrawCollateral(src, to, asset, amount); + } + + /** + * @notice Absorb a list of underwater accounts onto the protocol balance sheet + * @param absorber The recipient of the incentive paid to the caller of absorb + * @param accounts The list of underwater accounts to absorb + */ + function absorb(address absorber, address[] calldata accounts) override external { + if (isAbsorbPaused()) revert Paused(); + + uint startGas = gasleft(); + accrueInternal(); + for (uint i = 0; i < accounts.length; ) { + absorbInternal(absorber, accounts[i]); + unchecked { i++; } + } + uint gasUsed = startGas - gasleft(); + + // Note: liquidator points are an imperfect tool for governance, + // to be used while evaluating strategies for incentivizing absorption. + // Using gas price instead of base fee would more accurately reflect spend, + // but is also subject to abuse if refunds were to be given automatically. + LiquidatorPoints memory points = liquidatorPoints[absorber]; + points.numAbsorbs++; + points.numAbsorbed += safe64(accounts.length); + points.approxSpend += safe128(gasUsed * block.basefee); + liquidatorPoints[absorber] = points; + } + + /** + * @dev Transfer user's collateral and debt to the protocol itself. + */ + function absorbInternal(address absorber, address account) internal { + if (!isLiquidatable(account)) revert NotLiquidatable(); + + UserBasic memory accountUser = userBasic[account]; + int104 oldPrincipal = accountUser.principal; + int256 oldBalance = presentValue(oldPrincipal); + uint16 assetsIn = accountUser.assetsIn; + uint8 _reserved = accountUser._reserved; + + uint256 basePrice = getPrice(baseTokenPriceFeed); + uint256 deltaValue = 0; + + for (uint8 i = 0; i < numAssets; ) { + if (isInAsset(assetsIn, i, _reserved)) { + AssetInfo memory assetInfo = getAssetInfo(i); + address asset = assetInfo.asset; + uint128 seizeAmount = userCollateral[account][asset].balance; + userCollateral[account][asset].balance = 0; + totalsCollateral[asset].totalSupplyAsset -= seizeAmount; + + uint256 value = mulPrice(seizeAmount, getPrice(assetInfo.priceFeed), assetInfo.scale); + deltaValue += mulFactor(value, assetInfo.liquidationFactor); + + emit AbsorbCollateral(absorber, account, asset, seizeAmount, value); + } + unchecked { i++; } + } + + uint256 deltaBalance = divPrice(deltaValue, basePrice, uint64(baseScale)); + int256 newBalance = oldBalance + signed256(deltaBalance); + // New balance will not be negative, all excess debt absorbed by reserves + if (newBalance < 0) { + newBalance = 0; + } + + int104 newPrincipal = principalValue(newBalance); + updateBasePrincipal(account, accountUser, newPrincipal); + + // reset assetsIn + userBasic[account].assetsIn = 0; + userBasic[account]._reserved = 0; + + (uint104 repayAmount, uint104 supplyAmount) = repayAndSupplyAmount(oldPrincipal, newPrincipal); + + // Reserves are decreased by increasing total supply and decreasing borrows + // the amount of debt repaid by reserves is `newBalance - oldBalance` + totalSupplyBase += supplyAmount; + totalBorrowBase -= repayAmount; + + uint256 basePaidOut = unsigned256(newBalance - oldBalance); + uint256 valueOfBasePaidOut = mulPrice(basePaidOut, basePrice, uint64(baseScale)); + emit AbsorbDebt(absorber, account, basePaidOut, valueOfBasePaidOut); + + if (newPrincipal > 0) { + emit Transfer(address(0), account, presentValueSupply(baseSupplyIndex, unsigned104(newPrincipal))); + } + } + + /** + * @notice Buy collateral from the protocol using base tokens, increasing protocol reserves + A minimum collateral amount should be specified to indicate the maximum slippage acceptable for the buyer. + * @param asset The asset to buy + * @param minAmount The minimum amount of collateral tokens that should be received by the buyer + * @param baseAmount The amount of base tokens used to buy the collateral + * @param recipient The recipient address + */ + function buyCollateral(address asset, uint minAmount, uint baseAmount, address recipient) override external nonReentrant { + if (isBuyPaused()) revert Paused(); + + int reserves = getReserves(); + if (reserves >= 0 && uint(reserves) >= targetReserves) revert NotForSale(); + + // Note: Re-entrancy can skip the reserves check above on a second buyCollateral call. + baseAmount = doTransferIn(baseToken, msg.sender, baseAmount); + + uint collateralAmount = quoteCollateral(asset, baseAmount); + if (collateralAmount < minAmount) revert TooMuchSlippage(); + if (collateralAmount > getCollateralReserves(asset)) revert InsufficientReserves(); + + // Note: Pre-transfer hook can re-enter buyCollateral with a stale collateral ERC20 balance. + // Assets should not be listed which allow re-entry from pre-transfer now, as too much collateral could be bought. + // This is also a problem if quoteCollateral derives its discount from the collateral ERC20 balance. + doTransferOut(asset, recipient, safe128(collateralAmount)); + + emit BuyCollateral(msg.sender, asset, baseAmount, collateralAmount); + } + + /** + * @notice Gets the quote for a collateral asset in exchange for an amount of base asset + * @param asset The collateral asset to get the quote for + * @param baseAmount The amount of the base asset to get the quote for + * @return The quote in terms of the collateral asset + */ + function quoteCollateral(address asset, uint baseAmount) override public view returns (uint) { + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + uint256 assetPrice = getPrice(assetInfo.priceFeed); + // Store front discount is derived from the collateral asset's liquidationFactor and storeFrontPriceFactor + // discount = storeFrontPriceFactor * (1e18 - liquidationFactor) + uint256 discountFactor = mulFactor(storeFrontPriceFactor, FACTOR_SCALE - assetInfo.liquidationFactor); + uint256 assetPriceDiscounted = mulFactor(assetPrice, FACTOR_SCALE - discountFactor); + uint256 basePrice = getPrice(baseTokenPriceFeed); + // # of collateral assets + // = (TotalValueOfBaseAmount / DiscountedPriceOfCollateralAsset) * assetScale + // = ((basePrice * baseAmount / baseScale) / assetPriceDiscounted) * assetScale + return basePrice * baseAmount * assetInfo.scale / assetPriceDiscounted / baseScale; + } + + /** + * @notice Withdraws base token reserves if called by the governor + * @param to An address of the receiver of withdrawn reserves + * @param amount The amount of reserves to be withdrawn from the protocol + */ + function withdrawReserves(address to, uint amount) override external { + if (msg.sender != governor) revert Unauthorized(); + + int reserves = getReserves(); + if (reserves < 0 || amount > unsigned256(reserves)) revert InsufficientReserves(); + + doTransferOut(baseToken, to, amount); + + emit WithdrawReserves(to, amount); + } + + /** + * @notice Sets Comet's ERC20 allowance of an asset for a manager + * @dev Only callable by governor + * @dev Note: Setting the `asset` as Comet's address will allow the manager + * to withdraw from Comet's Comet balance + * @dev Note: For USDT, if there is non-zero prior allowance, it must be reset to 0 first before setting a new value in proposal + * @param asset The asset that the manager will gain approval of + * @param manager The account which will be allowed or disallowed + * @param amount The amount of an asset to approve + */ + function approveThis(address manager, address asset, uint amount) override external { + if (msg.sender != governor) revert Unauthorized(); + + IERC20NonStandard(asset).approve(manager, amount); + } + + /** + * @notice Get the total number of tokens in circulation + * @dev Note: uses updated interest indices to calculate + * @return The supply of tokens + **/ + function totalSupply() override external view returns (uint256) { + (uint64 baseSupplyIndex_, ) = accruedInterestIndices(getNowInternal() - lastAccrualTime); + return presentValueSupply(baseSupplyIndex_, totalSupplyBase); + } + + /** + * @notice Get the total amount of debt + * @dev Note: uses updated interest indices to calculate + * @return The amount of debt + **/ + function totalBorrow() override external view returns (uint256) { + (, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); + return presentValueBorrow(baseBorrowIndex_, totalBorrowBase); + } + + /** + * @notice Query the current positive base balance of an account or zero + * @dev Note: uses updated interest indices to calculate + * @param account The account whose balance to query + * @return The present day base balance magnitude of the account, if positive + */ + function balanceOf(address account) override public view returns (uint256) { + (uint64 baseSupplyIndex_, ) = accruedInterestIndices(getNowInternal() - lastAccrualTime); + int104 principal = userBasic[account].principal; + return principal > 0 ? presentValueSupply(baseSupplyIndex_, unsigned104(principal)) : 0; + } + + /** + * @notice Query the current negative base balance of an account or zero + * @dev Note: uses updated interest indices to calculate + * @param account The account whose balance to query + * @return The present day base balance magnitude of the account, if negative + */ + function borrowBalanceOf(address account) override public view returns (uint256) { + (, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime); + int104 principal = userBasic[account].principal; + return principal < 0 ? presentValueBorrow(baseBorrowIndex_, unsigned104(-principal)) : 0; + } + + /** + * @notice Fallback to calling the extension delegate for everything else + */ + fallback() external payable { + address delegate = extensionDelegate; + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), delegate, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } +} \ No newline at end of file diff --git a/contracts/CometFactoryExtendedAssetList.sol b/contracts/CometFactoryExtendedAssetList.sol new file mode 100644 index 000000000..5d30705f7 --- /dev/null +++ b/contracts/CometFactoryExtendedAssetList.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./CometExtendedAssetList.sol"; +import "./CometConfiguration.sol"; + +contract CometFactoryExtendedAssetList is CometConfiguration { + function clone(Configuration calldata config) external returns (address) { + return address(new CometExtendedAssetList(config)); + } +} \ No newline at end of file diff --git a/contracts/IAssetList.sol b/contracts/IAssetList.sol new file mode 100644 index 000000000..584e6cc2b --- /dev/null +++ b/contracts/IAssetList.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./CometCore.sol"; + +/** + * @title Compound's Asset List + * @author Compound + */ +interface IAssetList { + function getAssetInfo(uint8 i) external view returns (CometCore.AssetInfo memory); +} \ No newline at end of file diff --git a/contracts/IAssetListFactory.sol b/contracts/IAssetListFactory.sol new file mode 100644 index 000000000..9f41c28a5 --- /dev/null +++ b/contracts/IAssetListFactory.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; +import "./CometCore.sol"; + +/** + * @title Compound's Asset List Factory + * @author Compound + */ +interface IAssetListFactory { + function createAssetList(CometCore.AssetConfig[] memory assetConfigs) external returns (address assetList); +} \ No newline at end of file diff --git a/contracts/IAssetListFactoryHolder.sol b/contracts/IAssetListFactoryHolder.sol new file mode 100644 index 000000000..0691cada7 --- /dev/null +++ b/contracts/IAssetListFactoryHolder.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +/** + * @title Compound's Asset List Factory Holder Interface + * @author Compound + */ +interface IAssetListFactoryHolder { + function assetListFactory() external view returns (address); +} \ No newline at end of file diff --git a/contracts/test/CometHarnessExtendedAssetList.sol b/contracts/test/CometHarnessExtendedAssetList.sol new file mode 100644 index 000000000..73bc27b94 --- /dev/null +++ b/contracts/test/CometHarnessExtendedAssetList.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../CometExtendedAssetList.sol"; + +contract CometHarnessExtendedAssetList is CometExtendedAssetList { + uint public nowOverride; + + constructor(Configuration memory config) CometExtendedAssetList(config) {} + + function getNowInternal() override internal view returns (uint40) { + return nowOverride > 0 ? uint40(nowOverride) : super.getNowInternal(); + } + + function getNow() public view returns (uint40) { + return getNowInternal(); + } + + function setNow(uint now_) external { + nowOverride = now_; + } + + function setTotalsBasic(TotalsBasic memory totals) external { + baseSupplyIndex = totals.baseSupplyIndex; + baseBorrowIndex = totals.baseBorrowIndex; + trackingSupplyIndex = totals.trackingSupplyIndex; + trackingBorrowIndex = totals.trackingBorrowIndex; + totalSupplyBase = totals.totalSupplyBase; + totalBorrowBase = totals.totalBorrowBase; + lastAccrualTime = totals.lastAccrualTime; + } + + function setTotalsCollateral(address asset, TotalsCollateral memory totals) external { + totalsCollateral[asset] = totals; + } + + function setBasePrincipal(address account, int104 principal) external { + userBasic[account].principal = principal; + } + + function setCollateralBalance(address account, address asset, uint128 balance) external { + uint128 oldBalance = userCollateral[account][asset].balance; + userCollateral[account][asset].balance = balance; + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + updateAssetsIn(account, assetInfo, oldBalance, balance); + } + + function updateAssetsInExternal( + address account, + address asset, + uint128 initialUserBalance, + uint128 finalUserBalance + ) external { + AssetInfo memory assetInfo = getAssetInfoByAddress(asset); + updateAssetsIn(account, assetInfo, initialUserBalance, finalUserBalance); + } + + function getAssetList(address account) external view returns (address[] memory result) { + uint16 assetsIn = userBasic[account].assetsIn; + uint8 _reserved = userBasic[account]._reserved; + + uint8 count = 0; + for (uint8 i = 0; i < numAssets; i++) { + if (isInAsset(assetsIn, i, _reserved)) { + count++; + } + } + + result = new address[](count); + + uint j = 0; + for (uint8 i = 0; i < numAssets; i++) { + if (isInAsset(assetsIn, i, _reserved)) { + result[j] = getAssetInfo(i).asset; + j++; + } + } + + return result; + } + + function accrue() external { + accrueInternal(); + } +} \ No newline at end of file diff --git a/contracts/test/CometHarnessInterfaceExtendedAssetList.sol b/contracts/test/CometHarnessInterfaceExtendedAssetList.sol new file mode 100644 index 000000000..7c53d1353 --- /dev/null +++ b/contracts/test/CometHarnessInterfaceExtendedAssetList.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../CometInterface.sol"; + +abstract contract CometHarnessInterfaceExtendedAssetList is CometInterface { + function accrue() virtual external; + function getNow() virtual external view returns (uint40); + function setNow(uint now_) virtual external; + function setTotalsBasic(TotalsBasic memory totals) virtual external; + function setTotalsCollateral(address asset, TotalsCollateral memory totals) virtual external; + function setBasePrincipal(address account, int104 principal) virtual external; + function setCollateralBalance(address account, address asset, uint128 balance) virtual external; + function updateAssetsInExternal(address account, address asset, uint128 initialUserBalance, uint128 finalUserBalance) virtual external; + function getAssetList(address account) virtual external view returns (address[] memory); + function assetList() virtual external view returns (address); +} diff --git a/deployments/arbitrum-goerli/usdc.e/configuration.json b/deployments/arbitrum-goerli/usdc.e/configuration.json deleted file mode 100644 index 1d759f556..000000000 --- a/deployments/arbitrum-goerli/usdc.e/configuration.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC.e", - "baseTokenAddress": "0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892", - "baseTokenPriceFeed": "0x1692Bdd32F31b831caAc1b0c9fAF68613682813b", - "borrowMin": "100e6", - "pauseGuardian": "0x6C2fD6738e43ce0cc4A6f23a37e3a733516794A0", - "storeFrontPriceFactor": 0.5, - "targetReserves": "5000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0e15", - "baseBorrowSpeed": "0e15", - "baseMinForRewards": "10000e6" - }, - "assets": { - "LINK": { - "address": "0xbb7303602be1b9149b097aafb094ffce1860e532", - "priceFeed": "0xd28Ba6CA3bB72bF371b80a2a0a33cBcf9073C954", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "5000000e18" - }, - "WETH": { - "address": "0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3", - "priceFeed": "0x62CAe0FA2da220f43a51F86Db2EDb36DcA9A5A08", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "5000e18" - }, - "WBTC": { - "address": "0x22d5e2dE578677791f6c90e0110Ec629be9d5Fb5", - "priceFeed": "0x6550bc2301936011c1334555e62A87705A81C12C", - "decimals": "8", - "borrowCF": 0.7, - "liquidateCF": 0.75, - "liquidationFactor": 0.93, - "supplyCap": "300e8" - } - } -} diff --git a/deployments/arbitrum-goerli/usdc.e/deploy.ts b/deployments/arbitrum-goerli/usdc.e/deploy.ts deleted file mode 100644 index 4d6e0cf67..000000000 --- a/deployments/arbitrum-goerli/usdc.e/deploy.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet } from '../../../src/deploy'; - -const SECONDS_PER_DAY = 24 * 60 * 60; - -const GOERLI_TIMELOCK = '0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399'; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const trace = deploymentManager.tracer() - const ethers = deploymentManager.hre.ethers; - - // pull in existing assets - const USDC = await deploymentManager.existing('USDC', '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892', 'arbitrum-goerli'); - const LINK = await deploymentManager.existing('LINK', '0xbb7303602be1b9149b097aafb094ffce1860e532', 'arbitrum-goerli'); - const WETH = await deploymentManager.existing('WETH', '0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3', 'arbitrum-goerli'); - const WBTC = await deploymentManager.existing('WBTC', '0x22d5e2dE578677791f6c90e0110Ec629be9d5Fb5', 'arbitrum-goerli'); - - // Deploy ArbitrumBridgeReceiver - const bridgeReceiver = await deploymentManager.deploy( - 'bridgeReceiver', - 'bridges/arbitrum/ArbitrumBridgeReceiver.sol', - [] - ); - - // Deploy Local Timelock - const localTimelock = await deploymentManager.deploy( - 'timelock', - 'vendor/Timelock.sol', - [ - bridgeReceiver.address, // admin - 10 * 60, // delay - 14 * SECONDS_PER_DAY, // grace period - 10 * 60, // minimum delay - 30 * SECONDS_PER_DAY // maximum delay - ] - ); - - // Initialize ArbitrumBridgeReceiver - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - GOERLI_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - const { comet } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy( - 'bulker', - 'bulkers/BaseBulker.sol', - [ - await comet.governor(), // admin - WETH.address // weth - ] - ); - - return { - ...deployed, - bridgeReceiver, - bulker - }; -} \ No newline at end of file diff --git a/deployments/arbitrum-goerli/usdc.e/migrations/1679518383_configurate_and_ens.ts b/deployments/arbitrum-goerli/usdc.e/migrations/1679518383_configurate_and_ens.ts deleted file mode 100644 index 7a05a2023..000000000 --- a/deployments/arbitrum-goerli/usdc.e/migrations/1679518383_configurate_and_ens.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Contract } from 'ethers'; -import { expect } from 'chai'; -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { applyL1ToL2Alias, estimateL2Transaction, estimateTokenBridge } from '../../../../scenario/utils/arbitrumUtils'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; - -const arbitrumCOMPAddress = '0xf03370d2aCf26Dde26389B66498B7c293038F5aF'; - -export default migration('1679518383_configurate_and_ens', { - prepare: async (_deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - timelock: l2Timelock, - comet, - cometAdmin, - configurator, - rewards, - } = await deploymentManager.getContracts(); - - const { - arbitrumInbox, - arbitrumL1GatewayRouter, - timelock, - governor, - USDC, - COMP, - } = await govDeploymentManager.getContracts(); - - const USDCAmountToBridge = exp(10, 6); - const COMPAmountToBridge = exp(2_500, 18); - const usdcGatewayAddress = await arbitrumL1GatewayRouter.getGateway(USDC.address); - const compGatewayAddress = await arbitrumL1GatewayRouter.getGateway(COMP.address); - const refundAddress = l2Timelock.address; - - const compGasParams = await estimateTokenBridge( - { - token: COMP.address, - from: timelock.address, - to: rewards.address, - amount: COMPAmountToBridge - }, - govDeploymentManager, - deploymentManager - ); - - const usdcGasParams = await estimateTokenBridge( - { - token: USDC.address, - from: timelock.address, - to: comet.address, - amount: USDCAmountToBridge - }, - govDeploymentManager, - deploymentManager - ); - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, arbitrumCOMPAddress] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address, rewards.address], - [0, 0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)' - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata] - ] - ); - - const createRetryableTicketGasParams = await estimateL2Transaction( - { - from: applyL1ToL2Alias(timelock.address), - to: bridgeReceiver.address, - data: l2ProposalData - }, - deploymentManager - ); - - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - // XXX - const updatedMarkets = { - ...officialMarkets, - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address, - } - ], - }; - - const mainnetActions = [ - // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Arbitrum. - { - contract: arbitrumInbox, - signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', - args: [ - bridgeReceiver.address, // address to, - 0, // uint256 l2CallValue, - createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, - refundAddress, // address excessFeeRefundAddress, - refundAddress, // address callValueRefundAddress, - createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, - createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, - l2ProposalData, // bytes calldata data - ], - value: createRetryableTicketGasParams.deposit - }, - // 2. Approve the USDC gateway to take Timelock's USDC for bridging - { - contract: USDC, - signature: 'approve(address,uint256)', - args: [usdcGatewayAddress, USDCAmountToBridge] - }, - // 3. Bridge USDC from mainnet to Arbitrum Comet - { - contract: arbitrumL1GatewayRouter, - signature: 'outboundTransferCustomRefund(address,address,address,uint256,uint256,uint256,bytes)', - args: [ - USDC.address, // address _token, - refundAddress, // address _refundTo - comet.address, // address _to, - USDCAmountToBridge, // uint256 _amount, - usdcGasParams.gasLimit, // uint256 _maxGas, - usdcGasParams.maxFeePerGas, // uint256 _gasPriceBid, - utils.defaultAbiCoder.encode( - ['uint256', 'bytes'], - [usdcGasParams.maxSubmissionCost, '0x'] - ) // bytes calldata _data - ], - value: usdcGasParams.deposit - }, - // 4. Approve the COMP gateway to take Timelock's COMP for bridging - { - contract: COMP, - signature: 'approve(address,uint256)', - args: [compGatewayAddress, COMPAmountToBridge] - }, - // 5. Bridge COMP from mainnet to Arbitrum rewards - { - contract: arbitrumL1GatewayRouter, - signature: 'outboundTransferCustomRefund(address,address,address,uint256,uint256,uint256,bytes)', - args: [ - COMP.address, // address _token, - refundAddress, // address _refundTo, - rewards.address, // address _to, - COMPAmountToBridge, // uint256 _amount, - compGasParams.gasLimit, // uint256 _maxGas, - compGasParams.maxFeePerGas, // uint256 _gasPriceBid, - utils.defaultAbiCoder.encode( - ['uint256', 'bytes'], - [compGasParams.maxSubmissionCost, '0x'] - ) // bytes calldata _data - ], - value: compGasParams.deposit - }, - // 6. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(updatedMarkets)] - ) - }, - ]; - - const description = 'XXX'; // XXX add description - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(mainnetActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // await deploymentManager.spider(); // Pull in Arbitrum COMP now that reward config has been set - - const { - comet, - rewards, - WBTC, - WETH, - LINK - } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - LINK: { - supplyCap: exp(5_000_000, 18) - }, - WBTC: { - supplyCap: exp(300, 8) - }, - WETH: { - supplyCap: exp(5_000, 18) - } - }); - - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(arbitrumCOMPAddress); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - - // 2. & 3. - expect(await comet.getReserves()).to.be.equal(exp(10, 6)); - - // 4. & 5. - const arbitrumCOMP = new Contract( - arbitrumCOMPAddress, - ['function balanceOf(address account) external view returns (uint256)'], - deploymentManager.hre.ethers.provider - ); - expect(await arbitrumCOMP.balanceOf(rewards.address)).to.be.equal(exp(2_500, 18)); - - // 6. - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', - }, - ], - - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address - }, - ], - - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839' - }, - ] - }); - } -}); diff --git a/deployments/arbitrum-goerli/usdc.e/relations.ts b/deployments/arbitrum-goerli/usdc.e/relations.ts deleted file mode 100644 index f266af2a2..000000000 --- a/deployments/arbitrum-goerli/usdc.e/relations.ts +++ /dev/null @@ -1,29 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/arbitrum/ArbitrumBridgeReceiver.sol:ArbitrumBridgeReceiver' - }, - ClonableBeaconProxy: { - artifact: 'contracts/ERC20.sol:ERC20' - }, - // USDC - '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892': { - artifact: 'contracts/ERC20.sol:ERC20', - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - // WETH - '0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3': { - artifact: 'contracts/ERC20.sol:ERC20', - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; diff --git a/deployments/arbitrum-goerli/usdc.e/roots.json b/deployments/arbitrum-goerli/usdc.e/roots.json deleted file mode 100644 index 1ef25d7d2..000000000 --- a/deployments/arbitrum-goerli/usdc.e/roots.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "comet": "0x1d573274E19174260c5aCE3f2251598959d24456", - "configurator": "0x1Ead344570F0f0a0cD86d95d8adDC7855C8723Fb", - "rewards": "0x8DA65F8E3Aa22A498211fc4204C498ae9050DAE4", - "bridgeReceiver": "0xAC9fC1a9532BC92a9f33eD4c6Ce4A7a54930F376", - "bulker": "0x987350Af5a17b6DdafeB95E6e329c178f44841d7" -} \ No newline at end of file diff --git a/deployments/arbitrum-goerli/usdc/configuration.json b/deployments/arbitrum-goerli/usdc/configuration.json deleted file mode 100644 index fda04e2ac..000000000 --- a/deployments/arbitrum-goerli/usdc/configuration.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenAddress": "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63", - "baseTokenPriceFeed": "0x1692Bdd32F31b831caAc1b0c9fAF68613682813b", - "borrowMin": "1e6", - "pauseGuardian": "0x6C2fD6738e43ce0cc4A6f23a37e3a733516794A0", - "storeFrontPriceFactor": 0.5, - "targetReserves": "5000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", - "baseBorrowSpeed": "0.000402083333333e15", - "baseMinForRewards": "10000e6" - }, - "assets": { - "LINK": { - "address": "0xbb7303602be1b9149b097aafb094ffce1860e532", - "priceFeed": "0xd28Ba6CA3bB72bF371b80a2a0a33cBcf9073C954", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "5000000e18" - }, - "WETH": { - "address": "0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3", - "priceFeed": "0x62CAe0FA2da220f43a51F86Db2EDb36DcA9A5A08", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "5000e18" - }, - "WBTC": { - "address": "0x22d5e2dE578677791f6c90e0110Ec629be9d5Fb5", - "priceFeed": "0x6550bc2301936011c1334555e62A87705A81C12C", - "decimals": "8", - "borrowCF": 0.7, - "liquidateCF": 0.75, - "liquidationFactor": 0.93, - "supplyCap": "300e8" - } - } -} diff --git a/deployments/arbitrum-goerli/usdc/deploy.ts b/deployments/arbitrum-goerli/usdc/deploy.ts deleted file mode 100644 index ce94cab24..000000000 --- a/deployments/arbitrum-goerli/usdc/deploy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet } from '../../../src/deploy'; - -const SECONDS_PER_DAY = 24 * 60 * 60; - -const GOERLI_TIMELOCK = '0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399'; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const trace = deploymentManager.tracer() - const ethers = deploymentManager.hre.ethers; - - // pull in existing assets - const USDC = await deploymentManager.existing('USDC', '0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63', 'arbitrum-goerli'); - const LINK = await deploymentManager.existing('LINK', '0xbb7303602be1b9149b097aafb094ffce1860e532', 'arbitrum-goerli'); - const WETH = await deploymentManager.existing('WETH', '0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3', 'arbitrum-goerli'); - const WBTC = await deploymentManager.existing('WBTC', '0x22d5e2dE578677791f6c90e0110Ec629be9d5Fb5', 'arbitrum-goerli'); - - // Import shared contracts from cUSDCv3 - const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'arbitrum-goerli', 'usdc.e'); - const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum-goerli', 'usdc.e'); - const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'arbitrum-goerli', 'usdc.e'); - const configurator = await deploymentManager.fromDep('configurator', 'arbitrum-goerli', 'usdc.e'); - const rewards = await deploymentManager.fromDep('rewards', 'arbitrum-goerli', 'usdc.e'); - const bulker = await deploymentManager.fromDep('bulker', 'arbitrum-goerli', 'usdc.e'); - const localTimelock = await deploymentManager.fromDep('timelock', 'arbitrum-goerli', 'usdc.e'); - const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'arbitrum-goerli', 'usdc.e'); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - - return { - ...deployed, - bridgeReceiver, - bulker, - rewards, - }; -} \ No newline at end of file diff --git a/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts b/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts deleted file mode 100644 index 4ba4b9935..000000000 --- a/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { Contract } from 'ethers'; -import { expect } from 'chai'; -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { applyL1ToL2Alias, estimateL2Transaction, estimateTokenBridge } from '../../../../scenario/utils/arbitrumUtils'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; - -const arbitrumCOMPAddress = '0xf03370d2aCf26Dde26389B66498B7c293038F5aF'; - -export default migration('1689112067_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum-goerli', 'usdc.e'); - const usdceComet = await deploymentManager.fromDep('usdceComet', 'arbitrum-goerli', 'usdc.e', 'comet'); - const { - bridgeReceiver, - timelock: l2Timelock, - comet, - cometAdmin, - configurator, - rewards, - } = await deploymentManager.getContracts(); - - const { - arbitrumInbox, - arbitrumL1GatewayRouter, - timelock, - governor, - USDC, - COMP, - CCTPTokenMessenger, - } = await govDeploymentManager.getContracts(); - - // CCTP destination domain for Arbitrum - const ArbitrumDestinationDomain = 3; - const USDCAmountToBridge = exp(10, 6); - const refundAddress = l2Timelock.address; - const configuration = await getConfigurationStruct(deploymentManager); - const setFactoryCalldata = await calldata( - configurator.populateTransaction.setFactory(comet.address, cometFactory.address) - ); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, arbitrumCOMPAddress] - ); - - const turnOffUSDCeCometSupplySpeedCalldata = utils.defaultAbiCoder.encode( - ['address', 'uint64'], - [usdceComet.address, 0] - ); - - const turnOffUSDCeCometBorrowSpeedCalldata = utils.defaultAbiCoder.encode( - ['address', 'uint64'], - [usdceComet.address, 0] - ); - - const deployAndUpgradeToUSDCeCometCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, usdceComet.address] - ); - - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, configurator.address, cometAdmin.address, rewards.address, configurator.address, configurator.address, cometAdmin.address], - [0, 0, 0, 0, 0, 0, 0], - [ - 'setFactory(address,address)', - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)', - 'setBaseTrackingSupplySpeed(address,uint64)', - 'setBaseTrackingBorrowSpeed(address,uint64)', - 'deployAndUpgradeTo(address,address)', - ], - [setFactoryCalldata, setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata, turnOffUSDCeCometSupplySpeedCalldata, turnOffUSDCeCometBorrowSpeedCalldata, deployAndUpgradeToUSDCeCometCalldata] - ] - ); - - const createRetryableTicketGasParams = await estimateL2Transaction( - { - from: applyL1ToL2Alias(timelock.address), - to: bridgeReceiver.address, - data: l2ProposalData - }, - deploymentManager - ); - - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const arbitrumChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString(); - const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address }; - const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); - - // Rename old USDC market into USDC.e - officialMarketsJSON[arbitrumChainId][0].baseSymbol = 'USDC.e'; - - if (officialMarketsJSON[arbitrumChainId]) { - officialMarketsJSON[arbitrumChainId].push(newMarketObject); - } else { - officialMarketsJSON[arbitrumChainId] = [newMarketObject]; - } - - const mainnetActions = [ - // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Arbitrum. - { - contract: arbitrumInbox, - signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', - args: [ - bridgeReceiver.address, // address to, - 0, // uint256 l2CallValue, - createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, - refundAddress, // address excessFeeRefundAddress, - refundAddress, // address callValueRefundAddress, - createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, - createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, - l2ProposalData, // bytes calldata data - ], - value: createRetryableTicketGasParams.deposit - }, - // 2. Approve USDC to CCTP - { - contract: USDC, - signature: 'approve(address,uint256)', - args: [CCTPTokenMessenger.address, USDCAmountToBridge] - }, - // 3. Burn USDC to Arbitrum via CCTP - { - contract: CCTPTokenMessenger, - signature: 'depositForBurn(uint256,uint32,bytes32,address)', - args: [USDCAmountToBridge, ArbitrumDestinationDomain, utils.hexZeroPad(comet.address, 32), USDC.address], - }, - // 4. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] - ) - } - ]; - - // TODO: Will update this description to be more accurate once the contract is deployed - const description = "# Configurate Arbitrum cUSDCv3 market for Native USDC native, and set ENS record for official markets"; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(mainnetActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // Pull in Arbitrum COMP now that reward config has been set - const usdceComet = await deploymentManager.fromDep('usdceComet', 'arbitrum-goerli', 'usdc.e', 'comet'); - const { - comet, - rewards, - } = await deploymentManager.getContracts(); - - const config = await rewards.rewardConfig(comet.address); - - // 1. Verify state changes - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - LINK: { - supplyCap: exp(5_000_000, 18) - }, - WETH: { - supplyCap: exp(5_000, 18) - }, - WBTC: { - supplyCap: exp(300, 8) - }, - baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), - baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), - }); - - expect(config.token).to.be.equal(arbitrumCOMPAddress); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - // Ensure proposal has set usdce market to 0 - expect(await usdceComet.baseTrackingSupplySpeed()).to.be.equal(0); - expect(await usdceComet.baseTrackingBorrowSpeed()).to.be.equal(0); - - // 2. & 3. Verify the seeded USDC reaches Comet reserve - expect(await comet.getReserves()).to.be.equal(exp(10, 6)); - - // 4. Verify the official markets are updated - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', - }, - ], - - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - - 421613: [ - { - baseSymbol: 'USDC.e', - cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456', - }, - { - baseSymbol: 'USDC', - cometAddress: comet.address - }, - ], - - 59140: [ - { - baseSymbol: 'USDC', - cometAddress: '0xa84b24A43ba1890A165f94Ad13d0196E5fD1023a' - } - ], - - 84531: [ - { - baseSymbol: 'USDC', - cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1' - }, - { - baseSymbol: 'WETH', - cometAddress: '0xED94f3052638620fE226a9661ead6a39C2a265bE' - } - ], - - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839' - }, - ] - }); - } -}); diff --git a/deployments/arbitrum-goerli/usdc/relations.ts b/deployments/arbitrum-goerli/usdc/relations.ts deleted file mode 100644 index 8ad1e0e2d..000000000 --- a/deployments/arbitrum-goerli/usdc/relations.ts +++ /dev/null @@ -1,29 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/arbitrum/ArbitrumBridgeReceiver.sol:ArbitrumBridgeReceiver' - }, - ClonableBeaconProxy: { - artifact: 'contracts/ERC20.sol:ERC20' - }, - // Native USDC - '0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63': { - artifact: 'contracts/ERC20.sol:ERC20', - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - // WETH - '0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3': { - artifact: 'contracts/ERC20.sol:ERC20', - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; diff --git a/deployments/arbitrum-goerli/usdc/roots.json b/deployments/arbitrum-goerli/usdc/roots.json deleted file mode 100644 index 6f3a59a8c..000000000 --- a/deployments/arbitrum-goerli/usdc/roots.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "comet": "0x0C94d3F9D7F211630EDecAF085718Ac80821A6cA", - "configurator": "0x1Ead344570F0f0a0cD86d95d8adDC7855C8723Fb", - "rewards": "0x8DA65F8E3Aa22A498211fc4204C498ae9050DAE4", - "bridgeReceiver": "0xAC9fC1a9532BC92a9f33eD4c6Ce4A7a54930F376", - "bulker": "0x987350Af5a17b6DdafeB95E6e329c178f44841d7", - "CCTPMessageTransmitter": "0x109bc137cb64eab7c0b1dddd1edf341467dc2d35" -} \ No newline at end of file diff --git a/deployments/arbitrum/usdc.e/migrations/1735299634_update_comet_to_support_more_collaterals.ts b/deployments/arbitrum/usdc.e/migrations/1735299634_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..762cdcf21 --- /dev/null +++ b/deployments/arbitrum/usdc.e/migrations/1735299634_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,170 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +let newCometExtAddress: string; + +export default migration('1735299634_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + timelock: l2Timelock, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo WETH Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/arbitrum/usdc/migrations/1735299626_update_comet_to_support_more_collaterals.ts b/deployments/arbitrum/usdc/migrations/1735299626_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..01514180e --- /dev/null +++ b/deployments/arbitrum/usdc/migrations/1735299626_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,268 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +let newCometExtAddress: string; + +const USDCE_COMET = '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA'; +const USDT_COMET = '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07'; +const WETH_COMET = '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486'; + +export default migration('1735299626_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + timelock: l2Timelock, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const setFactoryCalldataUSDCE = await calldata( + configurator.populateTransaction.setFactory(USDCE_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDCE = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDCE_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDCE = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDCE_COMET] + ); + + const setFactoryCalldataUSDT = await calldata( + configurator.populateTransaction.setFactory(USDT_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDT = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDT_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDT = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDT_COMET] + ); + + const setFactoryCalldataWETH = await calldata( + configurator.populateTransaction.setFactory(WETH_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataWETH = await calldata( + configurator.populateTransaction.setExtensionDelegate(WETH_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataWETH = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, WETH_COMET] + ); + + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + ], + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0 + ], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [ + setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata, + setFactoryCalldataUSDCE, setExtensionDelegateCalldataUSDCE, deployAndUpgradeToCalldataUSDCE, + setFactoryCalldataUSDT, setExtensionDelegateCalldataUSDT, deployAndUpgradeToCalldataUSDT, + setFactoryCalldataWETH, setExtensionDelegateCalldataWETH, deployAndUpgradeToCalldataWETH, + ], + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Sends the proposal to the L2 + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = '# Update USDC, USDT, WETH and USDC.e Comets on Arbitrum to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update 4 Comets to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update Compound III USDT, USDC, WETH and USDC.e markets on Arbitrum. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version for all 4 comets: cUSDTv3, cUSDCv3, cWETHv3 and cUSDCev3.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDCE = new Contract( + USDCE_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDCE = await cometNewUSDCE.assetList(); + + expect(assetListAddressUSDCE).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDT = new Contract( + USDT_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDT = await cometNewUSDT.assetList(); + + expect(assetListAddressUSDT).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewWETH = new Contract( + WETH_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressWETH = await cometNewWETH.assetList(); + + expect(assetListAddressWETH).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/arbitrum/usdc/relations.ts b/deployments/arbitrum/usdc/relations.ts index 462bfc1a4..9a8535766 100644 --- a/deployments/arbitrum/usdc/relations.ts +++ b/deployments/arbitrum/usdc/relations.ts @@ -59,4 +59,12 @@ export default { } } }, + ERC1967Proxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; \ No newline at end of file diff --git a/deployments/arbitrum/usdt/migrations/1735299656_update_comet_to_support_more_collaterals.ts b/deployments/arbitrum/usdt/migrations/1735299656_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..7e008e321 --- /dev/null +++ b/deployments/arbitrum/usdt/migrations/1735299656_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,170 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +let newCometExtAddress: string; + +export default migration('1735299656_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + timelock: l2Timelock, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo WETH Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/arbitrum/weth/migrations/1735299663_update_comet_to_support_more_collaterals.ts b/deployments/arbitrum/weth/migrations/1735299663_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..7641ccc30 --- /dev/null +++ b/deployments/arbitrum/weth/migrations/1735299663_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,170 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +let newCometExtAddress: string; + +export default migration('1735299663_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + timelock: l2Timelock, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo WETH Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/base-goerli/usdc/configuration.json b/deployments/base-goerli/usdc/configuration.json deleted file mode 100644 index c782ac14d..000000000 --- a/deployments/base-goerli/usdc/configuration.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenAddress": "0x31D3A7711a74b4Ec970F50c3eaf1ee47ba803A95", - "baseTokenPriceFeed": "0xb85765935B4d9Ab6f841c9a00690Da5F34368bc0", - "borrowMin": "1e6", - "pauseGuardian": "0xBA5e81fD6811E2699b478d1Bcde62a585bC9b6f7", - "storeFrontPriceFactor": 0.5, - "targetReserves": "1000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", - "baseBorrowSpeed": "0.000402083333333e15", - "baseMinForRewards": "10000e6" - }, - "assets": { - "cbETH": { - "address": "0x7c6b91D9Be155A6Db01f749217d76fF02A7227F2", - "priceFeed": "0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2", - "decimals": "18", - "borrowCF": 0.75, - "liquidateCF": 0.8, - "liquidationFactor": 0.93, - "supplyCap": "800e18" - }, - "WETH": { - "address": "0x4200000000000000000000000000000000000006", - "priceFeed": "0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "1000e18" - } - } -} diff --git a/deployments/base-goerli/usdc/deploy.ts b/deployments/base-goerli/usdc/deploy.ts deleted file mode 100644 index d9b505101..000000000 --- a/deployments/base-goerli/usdc/deploy.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet } from '../../../src/deploy'; - -const SECONDS_PER_DAY = 24 * 60 * 60; - -const GOERLI_TIMELOCK = '0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399'; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const deployed = await deployContracts(deploymentManager, deploySpec); - return deployed; -} - -async function deployContracts( - deploymentManager: DeploymentManager, - deploySpec: DeploySpec -): Promise { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - // Pull in existing assets - const WETH = await deploymentManager.existing( - 'WETH', - '0x4200000000000000000000000000000000000006', - 'base-goerli' - ); - - const l2CrossDomainMessenger = await deploymentManager.existing( - 'l2CrossDomainMessenger', - ['0xC0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007', '0x4200000000000000000000000000000000000007'], - 'base-goerli' - ); - - const l2StandardBridge = await deploymentManager.existing( - 'l2StandardBridge', - ['0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010', '0x4200000000000000000000000000000000000010'], - 'base-goerli' - ); - - // Deploy OptimismBridgeReceiver - const bridgeReceiver = await deploymentManager.deploy( - 'bridgeReceiver', - 'bridges/optimism/OptimismBridgeReceiver.sol', - [l2CrossDomainMessenger.address] - ); - - // Deploy Local Timelock - const localTimelock = await deploymentManager.deploy( - 'timelock', - 'vendor/Timelock.sol', - [ - bridgeReceiver.address, // admin - 10 * 60, // delay - 14 * SECONDS_PER_DAY, // grace period - 10 * 60, // minimum delay - 30 * SECONDS_PER_DAY // maximum delay - ] - ); - - // Initialize OptimismBridgeReceiver - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - GOERLI_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - const { comet } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy( - 'bulker', - 'bulkers/BaseBulker.sol', - [ - await comet.governor(), // admin - WETH.address // weth - ] - ); - - // Deploy fauceteer - const fauceteer = await deploymentManager.deploy('fauceteer', 'test/Fauceteer.sol', []); - - return { - ...deployed, - bridgeReceiver, - l2CrossDomainMessenger, - l2StandardBridge, - bulker, - fauceteer - }; -} diff --git a/deployments/base-goerli/usdc/migrations/1679592519_configurate_and_ens.ts b/deployments/base-goerli/usdc/migrations/1679592519_configurate_and_ens.ts deleted file mode 100644 index 2143b2825..000000000 --- a/deployments/base-goerli/usdc/migrations/1679592519_configurate_and_ens.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { expect } from 'chai'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; -const baseCOMPAddress = '0xA29b548056c3fD0f68BAd9d4829EC4E66f22f796'; - -export default migration('1679592519_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - comet, - cometAdmin, - configurator, - rewards, - } = await deploymentManager.getContracts(); - - const { - baseL1CrossDomainMessenger, - baseL1StandardBridge, - governor, - COMP: goerliCOMP, - } = await govDeploymentManager.getContracts(); - - // ENS Setup - // See also: https://docs.ens.domains/contract-api-reference/name-processing - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const baseGoerliChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString(); - const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address }; - const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); - if (officialMarketsJSON[baseGoerliChainId]) { - officialMarketsJSON[baseGoerliChainId].push(newMarketObject); - } else { - officialMarketsJSON[baseGoerliChainId] = [newMarketObject]; - } - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, baseCOMPAddress] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address, rewards.address], - [0, 0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)' - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata] - ] - ); - - const COMPAmountToBridge = exp(10_000, 18); - - // Note: We aren't bridging USDC over to Base Goerli because they don't use a bridged version of USDC there, - const goerliActions = [ - // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Base-Goerli. - { - contract: baseL1CrossDomainMessenger, - signature: 'sendMessage(address,bytes,uint32)', - args: [bridgeReceiver.address, l2ProposalData, 2_500_000] - }, - - // 2. Approve Goerli's L1StandardBridge to take Timelock's COMP (for bridging) - { - contract: goerliCOMP, - signature: 'approve(address,uint256)', - args: [baseL1StandardBridge.address, COMPAmountToBridge] - }, - // 3. Bridge COMP from Goerli to Base-Goerli Comet using L1StandardBridge - { - contract: baseL1StandardBridge, - // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) - signature: 'depositERC20To(address,address,address,uint256,uint32,bytes)', - args: [goerliCOMP.address, baseCOMPAddress, rewards.address, COMPAmountToBridge, 200_000, '0x'] - }, - - // 4. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] - ) - }, - ]; - - const description = "# Configurate Base-Goerli cUSDCv3 market, set reward config, bridge over USDC and COMP, and update ENS text record."; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(goerliActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // We spider here to pull in Optimism COMP now that reward config has been set - - const { - comet, - rewards, - COMP, - } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - pauseGuardian: '0xBA5e81fD6811E2699b478d1Bcde62a585bC9b6f7', - baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), - baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), - baseBorrowMin: exp(1, 6), - WETH: { - supplyCap: exp(1000, 18) - }, - cbETH: { - supplyCap: exp(800, 18) - } - }) - - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(COMP.address); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - - // 2. & 3. - expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(10_000, 18)); - - // 4. - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', - }, - ], - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839', - }, - ], - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456' - } - ], - 84531: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address, - }, - ], - }); - } -}); \ No newline at end of file diff --git a/deployments/base-goerli/usdc/relations.ts b/deployments/base-goerli/usdc/relations.ts deleted file mode 100644 index 5f5760eec..000000000 --- a/deployments/base-goerli/usdc/relations.ts +++ /dev/null @@ -1,24 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver' - }, - - l2CrossDomainMessenger: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - - l2StandardBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; \ No newline at end of file diff --git a/deployments/base-goerli/usdc/roots.json b/deployments/base-goerli/usdc/roots.json deleted file mode 100644 index ffe3f14d7..000000000 --- a/deployments/base-goerli/usdc/roots.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "comet": "0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1", - "configurator": "0xB1C86B6f4BA3c997dAC601671418F6B026aaA5b2", - "rewards": "0x0818165C053D325985d87F4b8646b3062C72C385", - "bridgeReceiver": "0xdf983449591838C8660cAd7cE08C65b030A43bbE", - "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", - "l2StandardBridge": "0x4200000000000000000000000000000000000010", - "bulker": "0x684108D64Ac3BdE77c617bDEbDBC9afaE6562676", - "fauceteer": "0x54fcBea987d18E027a827eE25e1943Cf0874Eba8" -} \ No newline at end of file diff --git a/deployments/base-goerli/weth/configuration.json b/deployments/base-goerli/weth/configuration.json deleted file mode 100644 index b0060d2f0..000000000 --- a/deployments/base-goerli/weth/configuration.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "Compound WETH", - "symbol": "cWETHv3", - "baseToken": "WETH", - "baseTokenAddress": "0x4200000000000000000000000000000000000006", - "borrowMin": "0.001e18", - "pauseGuardian": "0xBA5e81fD6811E2699b478d1Bcde62a585bC9b6f7", - "storeFrontPriceFactor": 0.5, - "targetReserves": "500e18", - "rates": { - "supplyKink": 0.9, - "supplySlopeLow": 0.0283824, - "supplySlopeHigh": 0.6066567706, - "supplyBase": 0, - "borrowKink": 0.9, - "borrowSlopeLow": 0.05171500002, - "borrowSlopeHigh": 0.5171500339, - "borrowBase": 0.009945209674 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", - "baseBorrowSpeed": "0.000402083333333e15", - "baseMinForRewards": "1e18" - }, - "assets": { - "cbETH": { - "address": "0x7c6b91D9Be155A6Db01f749217d76fF02A7227F2", - "priceFeed": "0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2", - "decimals": "18", - "borrowCF": 0.90, - "liquidateCF": 0.93, - "liquidationFactor": 0.95, - "supplyCap": "1000e18" - } - } -} - diff --git a/deployments/base-goerli/weth/deploy.ts b/deployments/base-goerli/weth/deploy.ts deleted file mode 100644 index 8b4065cbc..000000000 --- a/deployments/base-goerli/weth/deploy.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet, exp } from '../../../src/deploy'; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const deployed = await deployContracts(deploymentManager, deploySpec); - return deployed; -} - -async function deployContracts( - deploymentManager: DeploymentManager, - deploySpec: DeploySpec -): Promise { - const trace = deploymentManager.tracer(); - - // Deploy constant price feed for WETH - const wethConstantPriceFeed = await deploymentManager.deploy( - 'WETH:priceFeed', - 'pricefeeds/ConstantPriceFeed.sol', - [ - 8, // decimals - exp(1, 8) // constantPrice - ] - ); - - // Deploy scaling price feed for cbETH - // XXX There is no cbETH / ETH pricefeed on testnet. Remember to change this for mainnet. - // const cbETHScalingPriceFeed = await deploymentManager.deploy( - // 'cbETH:priceFeed', - // 'ScalingPriceFeed.sol', - // [ - // '0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2', // cbETH / ETH price feed - // 8 // decimals - // ] - // ); - const cbETHConstantPriceFeed = await deploymentManager.deploy( - 'cbETH:priceFeed', - 'pricefeeds/ConstantPriceFeed.sol', - [ - 8, // decimals - exp(1, 8) // constantPrice - ] - ); - - // Import shared contracts from cUSDCv3 - const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'base-goerli', 'usdc'); - const cometFactory = await deploymentManager.fromDep('cometFactory', 'base-goerli', 'usdc'); - const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'base-goerli', 'usdc'); - const configurator = await deploymentManager.fromDep('configurator', 'base-goerli', 'usdc'); - const rewards = await deploymentManager.fromDep('rewards', 'base-goerli', 'usdc'); - const bulker = await deploymentManager.fromDep('bulker', 'base-goerli', 'usdc'); - const fauceteer = await deploymentManager.fromDep('fauceteer', 'base-goerli', 'usdc'); - const l2CrossDomainMessenger = await deploymentManager.fromDep('l2CrossDomainMessenger', 'base-goerli', 'usdc'); - const l2StandardBridge = await deploymentManager.fromDep('l2StandardBridge', 'base-goerli', 'usdc'); - const localTimelock = await deploymentManager.fromDep('timelock', 'base-goerli', 'usdc'); - const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'base-goerli', 'usdc'); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - - // XXX We will need to deploy a new bulker only if need to support wstETH - - return { - ...deployed, - bridgeReceiver, - l2CrossDomainMessenger, - l2StandardBridge, - bulker, - fauceteer - }; -} diff --git a/deployments/base-goerli/weth/migrations/1685486850_configurate_and_ens.ts b/deployments/base-goerli/weth/migrations/1685486850_configurate_and_ens.ts deleted file mode 100644 index fc00b94ed..000000000 --- a/deployments/base-goerli/weth/migrations/1685486850_configurate_and_ens.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { expect } from 'chai'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; -const baseCOMPAddress = '0xA29b548056c3fD0f68BAd9d4829EC4E66f22f796'; -const amountETHToWrap = exp(0.1, 18); - -export default migration('1685486850_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const cometFactory = await deploymentManager.fromDep('cometFactory', 'base-goerli', 'usdc'); - const { - bridgeReceiver, - timelock: localTimelock, - comet, - cometAdmin, - configurator, - rewards, - WETH - } = await deploymentManager.getContracts(); - - const { - baseL1CrossDomainMessenger, - baseL1StandardBridge, - governor, - COMP: goerliCOMP, - } = await govDeploymentManager.getContracts(); - - // ENS Setup - // See also: https://docs.ens.domains/contract-api-reference/name-processing - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const baseGoerliChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString(); - const newMarketObject = { baseSymbol: 'WETH', cometAddress: comet.address }; - const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); - if (officialMarketsJSON[baseGoerliChainId]) { - officialMarketsJSON[baseGoerliChainId].push(newMarketObject); - } else { - officialMarketsJSON[baseGoerliChainId] = [newMarketObject]; - } - - const configuration = await getConfigurationStruct(deploymentManager); - const setFactoryCalldata = await calldata( - configurator.populateTransaction.setFactory(comet.address, cometFactory.address) - ); - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, baseCOMPAddress] - ); - // Note: There is no way to directly bridge WETH, so we have to bridge ETH to the Timelock, wrap it, then transfer it to Comet - const transferWETHCalldata = utils.defaultAbiCoder.encode( - ['address', 'uint256'], - [comet.address, amountETHToWrap] - ); - - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, configurator.address, cometAdmin.address, rewards.address, WETH.address, WETH.address], - [0, 0, 0, 0, amountETHToWrap, 0], - [ - 'setFactory(address,address)', - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)', - 'deposit()', - 'transfer(address,uint256)' - ], - [setFactoryCalldata, setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata, '0x', transferWETHCalldata] - ] - ); - - const COMPAmountToBridge = exp(10_000, 18); - - const goerliActions = [ - - // 1. Set Comet configuration + deployAndUpgradeTo new Comet, set reward config on Base-Goerli, wrap ETH to WETH and transfer to Comet as reserves. - { - contract: baseL1CrossDomainMessenger, - signature: 'sendMessage(address,bytes,uint32)', - args: [bridgeReceiver.address, l2ProposalData, 3_500_000] - }, - - // 2. Bridge ETH to the L2 timelock - { - contract: baseL1StandardBridge, - value: amountETHToWrap, - signature: 'depositETHTo(address,uint32,bytes)', - args: [localTimelock.address, 200_000, '0x'] - }, - - // 3. Approve Goerli's L1StandardBridge to take Timelock's COMP (for bridging) - { - contract: goerliCOMP, - signature: 'approve(address,uint256)', - args: [baseL1StandardBridge.address, COMPAmountToBridge] - }, - // 4. Bridge COMP from Goerli to Base-Goerli Comet using L1StandardBridge - { - contract: baseL1StandardBridge, - // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) - signature: 'depositERC20To(address,address,address,uint256,uint32,bytes)', - args: [goerliCOMP.address, baseCOMPAddress, rewards.address, COMPAmountToBridge, 200_000, '0x'] - }, - - // 5. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] - ) - }, - ]; - - const description = "# Configurate Base-Goerli cWETHv3 market, set reward config, bridge over WETH and COMP, and update ENS text record."; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(goerliActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // We spider here to pull in Optimism COMP now that reward config has been set - - const { - comet, - rewards, - COMP, - WETH - } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - pauseGuardian: '0xBA5e81fD6811E2699b478d1Bcde62a585bC9b6f7', - baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), - baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), - cbETH: { - supplyCap: exp(1000, 18) - } - }) - - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(COMP.address); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - - // 1. & 2. - expect(await comet.getReserves()).to.be.equal(amountETHToWrap); - expect(await WETH.balanceOf(comet.address)).to.be.equal(amountETHToWrap); - - // 3. & 4. - expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(20_000, 18)); - - // 5. - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', - }, - ], - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839', - }, - ], - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456' - } - ], - 84531: [ - { - baseSymbol: 'USDC', - cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1' - }, - { - baseSymbol: 'WETH', - cometAddress: comet.address, - }, - ], - }); - } -}); \ No newline at end of file diff --git a/deployments/base-goerli/weth/relations.ts b/deployments/base-goerli/weth/relations.ts deleted file mode 100644 index 5f5760eec..000000000 --- a/deployments/base-goerli/weth/relations.ts +++ /dev/null @@ -1,24 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver' - }, - - l2CrossDomainMessenger: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - - l2StandardBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; \ No newline at end of file diff --git a/deployments/base-goerli/weth/roots.json b/deployments/base-goerli/weth/roots.json deleted file mode 100644 index b804a9c21..000000000 --- a/deployments/base-goerli/weth/roots.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "comet": "0xED94f3052638620fE226a9661ead6a39C2a265bE", - "configurator": "0xB1C86B6f4BA3c997dAC601671418F6B026aaA5b2", - "rewards": "0x0818165C053D325985d87F4b8646b3062C72C385", - "bridgeReceiver": "0xdf983449591838C8660cAd7cE08C65b030A43bbE", - "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", - "l2StandardBridge": "0x4200000000000000000000000000000000000010", - "bulker": "0x684108D64Ac3BdE77c617bDEbDBC9afaE6562676", - "fauceteer": "0x54fcBea987d18E027a827eE25e1943Cf0874Eba8" -} \ No newline at end of file diff --git a/deployments/base/aero/migrations/1735299703_update_comet_to_support_more_collaterals.ts b/deployments/base/aero/migrations/1735299703_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..a3371890a --- /dev/null +++ b/deployments/base/aero/migrations/1735299703_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,244 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +const USDC_COMET = '0xb125E6687d4313864e53df431d5425969c15Eb2F'; +const WETH_COMET = '0x46e6b214b524310239732D51387075E0e70970bf'; +const USDBC_COMET = '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf'; + +export default migration('1735299703_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const setFactoryCalldataUSDC = await calldata( + configurator.populateTransaction.setFactory(USDC_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDC = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDC_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDC = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDC_COMET] + ); + + const setFactoryCalldataWETH = await calldata( + configurator.populateTransaction.setFactory(WETH_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataWETH = await calldata( + configurator.populateTransaction.setExtensionDelegate(WETH_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataWETH = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, WETH_COMET] + ); + + const setFactoryCalldataUSDBC = await calldata( + configurator.populateTransaction.setFactory(USDBC_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDBC = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDBC_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDBC = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDBC_COMET] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + ], + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0 + ], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [ + setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata, + setFactoryCalldataUSDC, setExtensionDelegateCalldataUSDC, deployAndUpgradeToCalldataUSDC, + setFactoryCalldataWETH, setExtensionDelegateCalldataWETH, deployAndUpgradeToCalldataWETH, + setFactoryCalldataUSDBC, setExtensionDelegateCalldataUSDBC, deployAndUpgradeToCalldataUSDBC, + ], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Update AERO, USDC, WETH and USDbC Comets on Base to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update 4 Comets to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update a Compound III AERO, USDC, WETH and USDbC markets on Base. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version for all 4 comets: cAEROv3, cUSDCv3, cWETHv3 and cUSDbCv3.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDC = new Contract( + USDC_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDC = await cometNewUSDC.assetList(); + + expect(assetListAddressUSDC).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewWETH = new Contract( + WETH_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressWETH = await cometNewWETH.assetList(); + + expect(assetListAddressWETH).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDBC = new Contract( + USDBC_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDBC = await cometNewUSDBC.assetList(); + + expect(assetListAddressUSDBC).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/base/usdbc/migrations/1735299714_update_comet_to_support_more_collaterals.ts b/deployments/base/usdbc/migrations/1735299714_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..a78ef3c7d --- /dev/null +++ b/deployments/base/usdbc/migrations/1735299714_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299714_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/base/usdc/migrations/1735299718_update_comet_to_support_more_collaterals.ts b/deployments/base/usdc/migrations/1735299718_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..5d1501a3d --- /dev/null +++ b/deployments/base/usdc/migrations/1735299718_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299718_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/base/weth/migrations/1735299723_update_comet_to_support_more_collaterals.ts b/deployments/base/weth/migrations/1735299723_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..a9be12cdd --- /dev/null +++ b/deployments/base/weth/migrations/1735299723_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299723_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/goerli/usdc/configuration.json b/deployments/goerli/usdc/configuration.json deleted file mode 100644 index 6f632ad4c..000000000 --- a/deployments/goerli/usdc/configuration.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenPriceFeed": "0xAb5c49580294Aff77670F839ea425f5b78ab3Ae7", - "borrowMin": "1000e6", - "storeFrontPriceFactor": 0.5, - "targetReserves": "5000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000011574074074074073e15", - "baseBorrowSpeed": "0.0011458333333333333e15", - "baseMinForRewards": "10000e6" - }, - "rewardToken": "COMP", - "assets": { - "COMP": { - "priceFeed": "0x54a06047087927D9B0fb21c1cf0ebd792764dDB8", - "decimals": "18", - "borrowCF": 0.65, - "liquidateCF": 0.7, - "liquidationFactor": 0.92, - "supplyCap": "500000e18" - }, - "WBTC": { - "priceFeed": "0xA39434A63A52E749F02807ae27335515BA4b07F7", - "decimals": "8", - "borrowCF": 0.7, - "liquidateCF": 0.75, - "liquidationFactor": 0.93, - "supplyCap": "35000e8" - }, - "WETH": { - "priceFeed": "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", - "decimals": "18", - "borrowCF": 0.82, - "liquidateCF": 0.85, - "liquidationFactor": 0.93, - "supplyCap": "1000000e18" - } - } -} \ No newline at end of file diff --git a/deployments/goerli/usdc/deploy.ts b/deployments/goerli/usdc/deploy.ts deleted file mode 100644 index b0dd2d7d9..000000000 --- a/deployments/goerli/usdc/deploy.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, cloneGov, deployComet, exp, sameAddress, wait } from '../../../src/deploy'; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const deployed = await deployContracts(deploymentManager, deploySpec); - await mintTokens(deploymentManager); - return deployed; -} - -async function deployContracts(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const trace = deploymentManager.tracer() - const ethers = deploymentManager.hre.ethers; - const signer = await deploymentManager.getSigner(); - - // Declare existing assets as aliases - const COMP = await deploymentManager.existing('COMP', '0x3587b2F7E0E2D6166d6C14230e7Fe160252B0ba4'); - const USDC = await deploymentManager.existing('USDC', '0x07865c6E87B9F70255377e024ace6630C1Eaa37F'); - const WBTC = await deploymentManager.existing('WBTC', '0xAAD4992D949f9214458594dF92B44165Fb84dC19'); - const WETH = await deploymentManager.existing('WETH', '0x42a71137C09AE83D8d05974960fd607d40033499'); - - // Goerli -> Mumbai bridge contract - const fxRoot = await deploymentManager.existing('fxRoot', '0x3d1d3e34f7fb6d26245e6640e1c50710efff15ba', 'goerli'); - - // Deploy governance contracts - const { fauceteer, timelock } = await cloneGov(deploymentManager); - - // Deploy all Comet-related contracts - const deployed = await deployComet(deploymentManager, deploySpec); - const { rewards } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy( - 'bulker', - 'Bulker.sol', - [timelock.address, WETH.address] - ); - - await deploymentManager.idempotent( - async () => (await COMP.balanceOf(rewards.address)).eq(0), - async () => { - trace(`Sending some COMP to CometRewards`); - const amount = exp(1_000_000, 18); - trace(await wait(COMP.connect(signer).transfer(rewards.address, amount))); - trace(`COMP.balanceOf(${rewards.address}): ${await COMP.balanceOf(rewards.address)}`); - trace(`COMP.balanceOf(${signer.address}): ${await COMP.balanceOf(signer.address)}`); - } - ); - - return { ...deployed, fauceteer, bulker, fxRoot }; -} - -async function mintTokens(deploymentManager: DeploymentManager) { - const trace = deploymentManager.tracer(); - const signer = await deploymentManager.getSigner(); - const contracts = await deploymentManager.contracts(); - const fauceteer = contracts.get('fauceteer'); - - trace(`Attempting to mint as ${signer.address}...`); - - const WETH = contracts.get('WETH'); - await deploymentManager.idempotent( - async () => (await WETH.balanceOf(signer.address)).lt(exp(0.01, 18)), - async () => { - trace(`Minting 0.01 WETH for signer (this is a precious resource!)`); - trace(await wait(WETH.connect(signer).deposit({ value: exp(0.01, 18) }))); - trace(`WETH.balanceOf(${signer.address}): ${await WETH.balanceOf(signer.address)}`); - } - ); - - const WBTC = contracts.get('WBTC'); - await deploymentManager.idempotent( - async () => (await WBTC.balanceOf(fauceteer.address)).eq(0), - async () => { - trace(`Minting 20 WBTC to fauceteer`); - const amount = exp(20, await WBTC.decimals()); - trace(await wait(WBTC.connect(signer).mint(fauceteer.address, amount))); - trace(`WBTC.balanceOf(${fauceteer.address}): ${await WBTC.balanceOf(fauceteer.address)}`); - } - ); -} \ No newline at end of file diff --git a/deployments/goerli/usdc/migrations/1663870047_add_link.ts b/deployments/goerli/usdc/migrations/1663870047_add_link.ts deleted file mode 100644 index b4d1460c3..000000000 --- a/deployments/goerli/usdc/migrations/1663870047_add_link.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; -import { exp, proposal, wait } from '../../../../src/deploy'; - -import { expect } from 'chai'; - -const clone = { - link: '0x514910771af9ca656af840dff83e8264ecf986ca', -}; - -const LINK_PRICE_FEED = '0x48731cF7e84dc94C5f84577882c14Be11a5B7456'; -const PROPOSED_LINK_ASSET_INFO = { - priceFeed: LINK_PRICE_FEED, - decimals: 18, - borrowCollateralFactor: exp(0.75, 18), - liquidateCollateralFactor: exp(0.8, 18), - liquidationFactor: exp(0.92, 18), - supplyCap: exp(50_000_000, 18), -}; - -export default migration('1663870047_add_link', { - async prepare(deploymentManager: DeploymentManager) { - return {}; - }, - - async enact(deploymentManager: DeploymentManager) { - const trace = deploymentManager.tracer(); - const signer = await deploymentManager.getSigner(); - - const { - governor, - comet, - configurator, - cometAdmin, - fauceteer, - } = await deploymentManager.getContracts(); - - // Clone LINK and send half of total supply to the fauceteer - const LINK = await deploymentManager.clone('LINK', clone.link, []); - trace(`Sending half of all LINK to fauceteer`); - const amount = (await LINK.balanceOf(signer.address)).div(2); - trace(await wait(LINK.connect(signer).transfer(fauceteer.address, amount))); - trace(`LINK.balanceOf(${fauceteer.address}): ${await LINK.balanceOf(fauceteer.address)}`); - - const linkAssetConfig = { - asset: LINK.address, - ...PROPOSED_LINK_ASSET_INFO - }; - - const actions = [ - // 1. Add LINK in Configurator - { - contract: configurator, - signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', - args: [comet.address, linkAssetConfig], - }, - - // 2. Deploy and upgrade to a new version of Comet - { - contract: cometAdmin, - signature: 'deployAndUpgradeTo(address,address)', - args: [configurator.address, comet.address], - }, - ]; - const description = "# Add LINK to Goerli"; - const txn = await deploymentManager.retry( - async () => governor.propose(...await proposal(actions, description)) - ); - trace(txn); - - const event = (await txn.wait()).events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager) { - const { - comet, - configurator, - fauceteer, - LINK - } = await deploymentManager.getContracts(); - const linkAssetIndex = 3; - const linkAssetConfig = { - asset: LINK.address, - ...PROPOSED_LINK_ASSET_INFO - }; - - // 1. Compare proposed asset config with Comet asset info - const cometLinkAssetInfo = await comet.getAssetInfoByAddress(LINK.address); - expect(linkAssetIndex).to.be.equal(cometLinkAssetInfo.offset); - expect(linkAssetConfig.asset).to.be.equal(cometLinkAssetInfo.asset); - expect(linkAssetConfig.priceFeed).to.be.equal(cometLinkAssetInfo.priceFeed); - expect(exp(1, linkAssetConfig.decimals)).to.be.equal(cometLinkAssetInfo.scale); - expect(linkAssetConfig.borrowCollateralFactor).to.be.equal(cometLinkAssetInfo.borrowCollateralFactor); - expect(linkAssetConfig.liquidateCollateralFactor).to.be.equal(cometLinkAssetInfo.liquidateCollateralFactor); - expect(linkAssetConfig.liquidationFactor).to.be.equal(cometLinkAssetInfo.liquidationFactor); - expect(linkAssetConfig.supplyCap).to.be.equal(cometLinkAssetInfo.supplyCap); - - // 2. Compare proposed asset config with Configurator asset config - const configuratorLinkAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[linkAssetIndex]; - expect(linkAssetConfig.asset).to.be.equal(configuratorLinkAssetConfig.asset); - expect(linkAssetConfig.priceFeed).to.be.equal(configuratorLinkAssetConfig.priceFeed); - expect(linkAssetConfig.decimals).to.be.equal(configuratorLinkAssetConfig.decimals); - expect(linkAssetConfig.borrowCollateralFactor).to.be.equal(configuratorLinkAssetConfig.borrowCollateralFactor); - expect(linkAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorLinkAssetConfig.liquidateCollateralFactor); - expect(linkAssetConfig.liquidationFactor).to.be.equal(configuratorLinkAssetConfig.liquidationFactor); - expect(linkAssetConfig.supplyCap).to.be.equal(configuratorLinkAssetConfig.supplyCap); - - // 3. Expect that the Fauceteer has received half of the LINK total supply - expect(await LINK.balanceOf(fauceteer.address)).to.be.equal(exp(500_000_000, 18)); // Half of 1bn total supply - }, -}); diff --git a/deployments/goerli/usdc/migrations/1665028496_absorb_transfer_event_and_auto_collateral.ts b/deployments/goerli/usdc/migrations/1665028496_absorb_transfer_event_and_auto_collateral.ts deleted file mode 100644 index c58d98206..000000000 --- a/deployments/goerli/usdc/migrations/1665028496_absorb_transfer_event_and_auto_collateral.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; -import { calldata, exp, proposal } from '../../../../src/deploy'; - -import { expect } from 'chai'; - -export default migration('1665028496_absorb_transfer_event_and_auto_collateral', { - async prepare(deploymentManager: DeploymentManager) { - const cometFactory = await deploymentManager.deploy('cometFactory', 'CometFactory.sol', [], true); - return { newFactoryAddress: cometFactory.address }; - }, - - async enact(deploymentManager: DeploymentManager, { newFactoryAddress }) { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - const { - governor, - comet, - configurator, - cometAdmin, - COMP, - } = await deploymentManager.getContracts(); - - const actions = [ - // 1. Set comet factory to newly deployed factory - { - contract: configurator, - signature: 'setFactory(address,address)', - args: [comet.address, newFactoryAddress], - }, - - // 2. Deploy and upgrade to a new version of Comet - { - contract: cometAdmin, - signature: 'deployAndUpgradeTo(address,address)', - args: [configurator.address, comet.address], - }, - ]; - const description = "# Liquidation Event Handling And Collateral Reserves\n"; - const txn = await deploymentManager.retry( - async () => governor.propose(...await proposal(actions, description)) - ); - trace(txn); - - const event = (await txn.wait()).events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager) { - // 1. & 2. - // added a scenario to check for new Transfer event - } -}); diff --git a/deployments/goerli/usdc/migrations/1681942579_upgrade_governor_bravo.ts b/deployments/goerli/usdc/migrations/1681942579_upgrade_governor_bravo.ts deleted file mode 100644 index c23f01ca3..000000000 --- a/deployments/goerli/usdc/migrations/1681942579_upgrade_governor_bravo.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { expect } from 'chai'; -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { proposal } from '../../../../src/deploy'; - -const clone = { - governorImpl: '0xeF3B6E9e13706A8F01fe98fdCf66335dc5CfdEED' -}; - -const OLD_GOVERNOR_IMPL_ADDRESS = '0x9d26789c7b2492E6015B26dc75C79AeA71a7211c'; - -export default migration('1681942579_upgrade_governor_bravo', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - const { governor } = await deploymentManager.getContracts(); - - // Deploy new governor implementation (cloned from mainnet) - const newGovernorImpl = await deploymentManager.clone( - 'newGovernorImpl', - clone.governorImpl, - [], - 'mainnet' - ); - - const actions = [ - // 1. Set implementation of the governor to the new governor implementation - { - target: governor.address, - signature: '_setImplementation(address)', - calldata: ethers.utils.defaultAbiCoder.encode(['address'], [newGovernorImpl.address]) - } - ]; - const description = - '# Update governor implementation\n\n## Explanation\n\nUpdates the governor implementation to allow for sending ETH from the Timelock. \n'; - const txn = await deploymentManager.retry(async () => - governor.propose(...(await proposal(actions, description))) - ); - trace(txn); - - const event = (await txn.wait()).events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager) { - await deploymentManager.spider(); // We spider here to pull in the updated governor impl address - const { 'governor:implementation': governorImpl } = await deploymentManager.getContracts(); - - // 1. - expect(governorImpl.address).to.not.be.eq(OLD_GOVERNOR_IMPL_ADDRESS); - } -}); diff --git a/deployments/goerli/usdc/relations.ts b/deployments/goerli/usdc/relations.ts deleted file mode 100644 index 6652288ff..000000000 --- a/deployments/goerli/usdc/relations.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - fxRoot: { - relations: { - stateSender: { - field: async fxRoot => fxRoot.stateSender() - } - } - }, - arbitrumInbox: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - }, - relations: { - arbitrumBridge: { - field: async inbox => inbox.bridge() - } - } - }, - arbitrumL1GatewayRouter: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - baseL1CrossDomainMessenger: { - delegates: { - // Not great, but this address shouldn't change and is very difficult to grab on-chain (private methods) - field: async () => '0xa042e16781484716c1Ef448c919af7BCd9607467' - } - }, - baseL1StandardBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - }, - }, - opL1CrossDomainMessenger: { - delegates: { - field: async () => '0xDa2332D0a7608919Cd331B1304Cd179129a90495', - }, - }, - opL1StandardBridge: { - delegates: { - field: { - slot: - '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', - }, - }, - }, - lineaMessageService: { - artifact: 'contracts/bridges/linea/IMessageService.sol:IMessageService', - // delegates: { - // field: { - // slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - // } - // } - }, - lineaL1TokenBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - lineaL1usdcBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - scrollMessenger: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - scrollL1TokenBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; diff --git a/deployments/goerli/usdc/roots.json b/deployments/goerli/usdc/roots.json deleted file mode 100644 index 3add9436d..000000000 --- a/deployments/goerli/usdc/roots.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "timelock": "0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399", - "fauceteer": "0x75442Ac771a7243433e033F3F8EaB2631e22938f", - "comet": "0x3EE77595A8459e93C2888b13aDB354017B198188", - "configurator": "0xB28495db3eC65A0e3558F040BC4f98A0d588Ae60", - "rewards": "0xef9e070044d62C38D2e316146dDe92AD02CF2c2c", - "bulker": "0x69dD076105977c55dC2835951d287f82D54606b4", - "fxRoot": "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA", - "arbitrumInbox": "0x6BEbC4925716945D46F0Ec336D5C2564F419682C", - "arbitrumL1GatewayRouter": "0x4c7708168395aEa569453Fc36862D2ffcDaC588c", - "baseL1CrossDomainMessenger": "0x8e5693140eA606bcEB98761d9beB1BC87383706D", - "baseL1StandardBridge": "0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a", - "lineaMessageService": "0x70BaD09280FD342D02fe64119779BC1f0791BAC2", - "lineaL1TokenBridge": "0xaA012D038E6440535Ec66eDf2DA592F4F8398133", - "lineaL1usdcBridge": "0x9c556D2cCfb6157E4A6305aa9963EdD6ca5047cB", - "CCTPTokenMessenger": "0xd0c3da58f55358142b8d3e06c1c30c5c6114efe8", - "CCTPMessageTransmitter": "0x26413e8157cd32011e726065a5462e97dd4d03d9", - "opL1CrossDomainMessenger": "0x5086d1eEF304eb5284A0f6720f79403b4e9bE294", - "opL1StandardBridge": "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8", - "scrollMessenger": "0x5260e38080BFe97e6C4925d9209eCc5f964373b6", - "scrollL1TokenBridge": "0xe5E30E7c24e4dFcb281A682562E53154C15D3332" -} diff --git a/deployments/goerli/weth/configuration.json b/deployments/goerli/weth/configuration.json deleted file mode 100644 index bd5113a3f..000000000 --- a/deployments/goerli/weth/configuration.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "Compound WETH", - "symbol": "cWETHv3", - "baseToken": "WETH", - "baseTokenAddress": "0x42a71137C09AE83D8d05974960fd607d40033499", - "borrowMin": "0.1e18", - "governor": "0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399", - "pauseGuardian": "0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399", - "storeFrontPriceFactor": 0.5, - "targetReserves": "5000e18", - "rates": { - "supplyKink": 0.9, - "supplySlopeLow": 0.01690681444, - "supplySlopeHigh": 0.6066567706, - "supplyBase": 0, - "borrowKink": 0.9, - "borrowSlopeLow": 0.05171500002, - "borrowSlopeHigh": 0.5171500339, - "borrowBase": 0.009945209674 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "447916666666e0", - "baseBorrowSpeed": "0e15", - "baseMinForRewards": "1000e18" - }, - "assets": { - "cbETH": { - "decimals": "18", - "borrowCF": 0.90, - "liquidateCF": 0.93, - "liquidationFactor": 0.95, - "supplyCap": "9_000e18" - }, - "wstETH": { - "address": "0x4942BBAf745f235e525BAff49D31450810EDed5b", - "decimals": "18", - "borrowCF": 0.90, - "liquidateCF": 0.93, - "liquidationFactor": 0.95, - "supplyCap": "80_000e18" - } - } -} \ No newline at end of file diff --git a/deployments/goerli/weth/deploy.ts b/deployments/goerli/weth/deploy.ts deleted file mode 100644 index a08b9c01a..000000000 --- a/deployments/goerli/weth/deploy.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { debug, DeploySpec, deployComet, exp, sameAddress, wait } from '../../../src/deploy'; - -const clone = { - cbETHImpl: '0x31724cA0C982A31fbb5C57f4217AB585271fc9a5', - cbETHProxy: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', -}; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const deployed = await deployContracts(deploymentManager, deploySpec); - await mintTokens(deploymentManager); - return deployed; -} - -async function deployContracts(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const ethers = deploymentManager.hre.ethers; - const signer = await deploymentManager.getSigner(); - - // Declare existing assets as aliases - const WETH = await deploymentManager.existing('WETH', '0x42a71137C09AE83D8d05974960fd607d40033499', 'goerli'); - const wstETH = await deploymentManager.existing('wstETH', '0x4942BBAf745f235e525BAff49D31450810EDed5b', 'goerli'); - - // Import shared contracts from cUSDCv3 - const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'goerli', 'usdc'); - const cometFactory = await deploymentManager.fromDep('cometFactory', 'goerli', 'usdc'); - const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'goerli', 'usdc'); - const configurator = await deploymentManager.fromDep('configurator', 'goerli', 'usdc'); - const rewards = await deploymentManager.fromDep('rewards', 'goerli', 'usdc'); - const fauceteer = await deploymentManager.fromDep('fauceteer', 'goerli', 'usdc'); - const fxRoot = await deploymentManager.fromDep('fxRoot', 'goerli', 'usdc'); - - // Clone cbETH - const cbETHProxyAdmin = await deploymentManager.deploy('cbETH:admin', 'vendor/proxy/transparent/ProxyAdmin.sol', []); - const cbETHImpl = await deploymentManager.clone('cbETH:implementation', clone.cbETHImpl, []); - const cbETHProxy = await deploymentManager.clone('cbETH', clone.cbETHProxy, [cbETHImpl.address]); - const cbETHProxyAdminSlot = '0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b'; - const cbETH = cbETHImpl.attach(cbETHProxy.address); - await deploymentManager.idempotent( - async () => !sameAddress(await ethers.provider.getStorageAt(cbETHProxy.address, cbETHProxyAdminSlot), cbETHProxyAdmin.address), - async () => { - debug(`Changing admin of cbETH proxy to ${cbETHProxyAdmin.address}`); - await wait(cbETHProxy.connect(signer).changeAdmin(cbETHProxyAdmin.address)); - - debug(`Initializing cbETH`); - await wait(cbETH.connect(signer).initialize( - 'Coinbase Wrapped Staked ETH', // name - 'cbETH', // symbol - '', // currency - 18, // decimals - signer.address, // Master Minter - signer.address, // Pauser - signer.address, // Blacklister - signer.address // Owner - )); - } - ); - - // Deploy stETH / ETH SimplePriceFeed - const stETHtoETHPriceFeed = await deploymentManager.deploy( - 'stETHToETH:simplePriceFeed', - 'test/SimplePriceFeed.sol', - [ - exp(0.98882408, 18), // Latest answer on mainnet at block 16170924 - 18 - ] - ); - - // Deploy cbETH / ETH SimplePriceFeed - const cbETHtoETHPriceFeed = await deploymentManager.deploy( - 'cbETHToETH:simplePriceFeed', - 'test/SimplePriceFeed.sol', - [ - exp(0.97, 18), - 18 - ] - ); - - // Deploy WstETHPriceFeed - const wstETHPriceFeed = await deploymentManager.deploy( - 'wstETH:priceFeed', - 'WstETHPriceFeed.sol', - [ - stETHtoETHPriceFeed.address, // stETH / ETH price feed - wstETH.address, // wstETH - 8 // decimals - ] - ); - - // Deploy constant price feed for WETH - const wethConstantPriceFeed = await deploymentManager.deploy( - 'WETH:priceFeed', - 'ConstantPriceFeed.sol', - [ - 8, // decimals - exp(1, 8) // constantPrice - ] - ); - - // Deploy scaling price feed for cbETH - const cbETHScalingPriceFeed = await deploymentManager.deploy( - 'cbETH:priceFeed', - 'ScalingPriceFeed.sol', - [ - cbETHtoETHPriceFeed.address, // cbETH / ETH price feed - 8 // decimals - ] - ); - - // Deploy all Comet-related contracts - const deployed = await deployComet(deploymentManager, deploySpec); - const { comet } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy( - 'bulker', - 'bulkers/MainnetBulker.sol', - [ - await comet.governor(), // admin_ - WETH.address, // weth_ - wstETH.address // wsteth_ - ] - ); - - return { ...deployed, bulker, fauceteer, fxRoot }; -} - -async function mintTokens(deploymentManager: DeploymentManager) { - const signer = await deploymentManager.getSigner(); - const contracts = await deploymentManager.contracts(); - const fauceteer = contracts.get('fauceteer')!; - - debug(`Attempting to mint as ${signer.address}...`); - - // If we haven't spidered new contracts (which we could before minting, but its slow), - // then the proxy contract won't have the impl functions yet, so just do it explicitly - const cbETHProxy = contracts.get('cbETH')!, cbETHImpl = contracts.get('cbETH:implementation')!; - const cbETH = cbETHImpl.attach(cbETHProxy.address); - await deploymentManager.idempotent( - async () => (await cbETH.balanceOf(fauceteer.address)).eq(0), - async () => { - debug(`Minting 1M cbETH to fauceteer`); - const amount = exp(1_000_000, await cbETH.decimals()); - await wait(cbETH.connect(signer).configureMinter(signer.address, amount)); - await wait(cbETH.connect(signer).mint(fauceteer.address, amount)); - debug(`cbETH.balanceOf(${fauceteer.address}): ${await cbETH.balanceOf(fauceteer.address)}`); - } - ); -} diff --git a/deployments/goerli/weth/migrations/1672860425_initialize_market.ts b/deployments/goerli/weth/migrations/1672860425_initialize_market.ts deleted file mode 100644 index 03aa5a7b8..000000000 --- a/deployments/goerli/weth/migrations/1672860425_initialize_market.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; -import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; - -import { expect } from 'chai'; - -const COMPAddress = '0x3587b2F7E0E2D6166d6C14230e7Fe160252B0ba4'; - -export default migration('1672860425_initialize_market', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - - // Import shared contracts from cUSDCv3 - const cometFactory = await deploymentManager.fromDep('cometFactory', 'goerli', 'usdc'); - - const { - governor, - comet, - configurator, - cometAdmin, - rewards, - } = await deploymentManager.getContracts(); - - const configuration = await getConfigurationStruct(deploymentManager); - - const actions = [ - // 1. Set the factory in the Configurator - { - contract: configurator, - signature: 'setFactory(address,address)', - args: [comet.address, cometFactory.address], - }, - - // 2. Set the configuration in the Configurator - { - contract: configurator, - signature: 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - args: [comet.address, configuration], - }, - - // 3. Deploy and upgrade to a new version of Comet - { - contract: cometAdmin, - signature: "deployAndUpgradeTo(address,address)", - args: [configurator.address, comet.address], - }, - - // 4. Set the rewards configuration to COMP - { - contract: rewards, - signature: "setRewardConfig(address,address)", - args: [comet.address, COMPAddress], - }, - ]; - const description = "# Initialize cWETHv3 on Goerli" - const txn = await deploymentManager.retry( - async () => trace((await governor.propose(...await proposal(actions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - - async verify(deploymentManager: DeploymentManager) { - const { - comet, - rewards, - wstETH, - cbETH, - } = await deploymentManager.getContracts(); - // 2. & 3. - expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(38.7 / 86400, 15, 18)); // ~ 38.7 COMP / day cut from v2 - expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(0); - - const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); - expect(wstETHInfo.supplyCap).to.be.equal(exp(80_000, 18)); // ~ $100M / $1225 - - const cbETHInfo = await comet.getAssetInfoByAddress(cbETH.address); - expect(cbETHInfo.supplyCap).to.be.equal(exp(9_000, 18)); // ~ $10M / $1091 - - // 4. - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(COMPAddress); - expect(config.rescaleFactor).to.be.equal(1000000000000n); - expect(config.shouldUpscale).to.be.equal(true); - }, -}); \ No newline at end of file diff --git a/deployments/goerli/weth/migrations/1675148548_set_up_ens_subdomain_and_text_record.ts b/deployments/goerli/weth/migrations/1675148548_set_up_ens_subdomain_and_text_record.ts deleted file mode 100644 index eeebc7936..000000000 --- a/deployments/goerli/weth/migrations/1675148548_set_up_ens_subdomain_and_text_record.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { expect } from 'chai'; -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { proposal } from '../../../../src/deploy'; - -interface Vars {} - -const ENSName = 'compound-community-licenses.eth'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = ENSSubdomainLabel + '.' + ENSName; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; -const textRecordKey = 'v3-official-markets'; -// JSON string of official markets -const officialMarketsJSON = JSON.stringify({ - 1: [ - { - baseSymbol: 'USDC', - cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3' - }, - { - baseSymbol: 'WETH', - cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94' - } - ] -}); - -export default migration('1675148548_set_up_ens_subdomain_and_text_record', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, vars: Vars) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - const { timelock, governor } = await deploymentManager.getContracts(); - - // Namehash explanation: https://docs.ens.domains/contract-api-reference/name-processing - const nameHash = ethers.utils.namehash(ENSName); - const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(ENSSubdomainLabel)); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - - const actions = [ - // 1. Set up 'v3-additional-grants.compound-community-licenses.eth' ENS subdomain with the Timelock as the owner - { - target: ENSRegistryAddress, - signature: 'setSubnodeRecord(bytes32,bytes32,address,address,uint64)', - // setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'bytes32', 'address', 'address', 'uint64'], - [nameHash, labelHash, timelock.address, ENSResolverAddress, 0] - ) - }, - - // 2. Set the official markets text record on the subdomain - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, textRecordKey, officialMarketsJSON] - ) - } - ]; - const description = - '# Set up an ENS subdomain and text record for official Comet deployments\n'; - const txn = await deploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(actions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager) { - const ethers = deploymentManager.hre.ethers; - - const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); - const ENSRegistry = await deploymentManager.existing('ENSRegistry', ENSRegistryAddress); - - const { timelock } = await deploymentManager.getContracts(); - - const nameHash = ethers.utils.namehash(ENSName); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - - // 1. - expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); - expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); - expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); - expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); - - // 2. - expect(await ENSResolver.text(subdomainHash, textRecordKey)).to.be.equal(officialMarketsJSON); - expect(await ENSResolver.text(nameHash, textRecordKey)).to.be.equal(''); - } -}); diff --git a/deployments/goerli/weth/relations.ts b/deployments/goerli/weth/relations.ts deleted file mode 100644 index 73cec97f3..000000000 --- a/deployments/goerli/weth/relations.ts +++ /dev/null @@ -1,20 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - 'fxRoot': { - relations: { - stateSender: { - field: async (fxRoot) => fxRoot.stateSender() - } - } - }, - 'wstETH': { - artifact: 'contracts/bulkers/IWstETH.sol', - relations: { - stETH: { - field: async (wstETH) => wstETH.stETH() - } - } - }, -}; diff --git a/deployments/goerli/weth/roots.json b/deployments/goerli/weth/roots.json deleted file mode 100644 index 9f4b7c1e7..000000000 --- a/deployments/goerli/weth/roots.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "comet": "0x9A539EEc489AAA03D588212a164d0abdB5F08F5F", - "configurator": "0xB28495db3eC65A0e3558F040BC4f98A0d588Ae60", - "rewards": "0xef9e070044d62C38D2e316146dDe92AD02CF2c2c", - "bulker": "0x69dD076105977c55dC2835951d287f82D54606b4", - "fauceteer": "0x75442Ac771a7243433e033F3F8EaB2631e22938f", - "fxRoot": "0x3d1d3e34f7fb6d26245e6640e1c50710efff15ba" -} \ No newline at end of file diff --git a/deployments/linea-goerli/usdc/configuration.json b/deployments/linea-goerli/usdc/configuration.json deleted file mode 100644 index 0ce449a25..000000000 --- a/deployments/linea-goerli/usdc/configuration.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenAddress": "0xf56dc6695cF1f5c364eDEbC7Dc7077ac9B586068", - "baseTokenPriceFeed": "0xcb81fffa91cd5f3436318d25057c1d5df8e23f95", - "borrowMin": "0.001e6", - "storeFrontPriceFactor": 0.5, - "targetReserves": "1000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", - "baseBorrowSpeed": "0.000402083333333e15", - "baseMinForRewards": "1000e6" - }, - "assets": { - "WETH": { - "address": "0x2C1b868d6596a18e32E61B901E4060C872647b6C", - "priceFeed": "0x45A61d74eaa5F78a6e1919Ca74Db2615743190Ad", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "10000000e18" - }, - "WBTC": { - "address": "0xdbcd5bafbaa8c1b326f14ec0c8b125db57a5cc4c", - "priceFeed": "0x625e78891611d5a6227ff78548c373b56b0c8ea0", - "decimals": "18", - "borrowCF": 0.7, - "liquidateCF": 0.75, - "liquidationFactor": 0.93, - "supplyCap": "30000e18" - } - } -} diff --git a/deployments/linea-goerli/usdc/deploy.ts b/deployments/linea-goerli/usdc/deploy.ts deleted file mode 100644 index 6ae1e930c..000000000 --- a/deployments/linea-goerli/usdc/deploy.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet, exp, wait } from '../../../src/deploy'; - -const secondsPerDay = 24 * 60 * 60; - -const GOERLI_TIMELOCK = '0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399'; - -export default async function deploy( - deploymentManager: DeploymentManager, - deploySpec: DeploySpec -): Promise { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - // Pull in existing assets - const WETH = await deploymentManager.existing( - 'WETH', - '0x2C1b868d6596a18e32E61B901E4060C872647b6C', - 'linea-goerli' - ); - - const l2MessageService = await deploymentManager.existing( - 'l2MessageService', - '0xC499a572640B64eA1C8c194c43Bc3E19940719dC', - 'linea-goerli' - ); - - const l2TokenBridge = await deploymentManager.existing( - 'l2TokenBridge', - '0xB191E3d98074f92584E5205B99c3F17fB2068927', - 'linea-goerli' - ); - - const l2usdcBridge = await deploymentManager.existing( - 'l2usdcBridge', - '0x2aeD4D02fD76EeC1580cCDbA158b16F4A0Ad2B60', - 'linea-goerli' - ); - - // Deploy LineaBridgeReceiver - const bridgeReceiver = await deploymentManager.deploy( - 'bridgeReceiver', - 'bridges/linea/LineaBridgeReceiver.sol', - [l2MessageService.address] - ); - - // Deploy Local Timelock - const localTimelock = await deploymentManager.deploy('timelock', 'vendor/Timelock.sol', [ - bridgeReceiver.address, // admin - 10 * 60, // delay - 14 * secondsPerDay, // grace period - 10 * 60, // minimum delay - 30 * secondsPerDay // maximum delay - ]); - - // Initialize BridgeReceiver - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - GOERLI_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - const { comet } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy('bulker', 'bulkers/BaseBulker.sol', [ - await comet.governor(), // admin - WETH.address // weth - ]); - - // Deploy fauceteer - const fauceteer = await deploymentManager.deploy('fauceteer', 'test/Fauceteer.sol', []); - - return { - ...deployed, - bridgeReceiver, - l2MessageService, - l2TokenBridge, - bulker, - fauceteer, - l2usdcBridge - }; -} diff --git a/deployments/linea-goerli/usdc/migrations/1686918613_configurate_and_ens.ts b/deployments/linea-goerli/usdc/migrations/1686918613_configurate_and_ens.ts deleted file mode 100644 index e7dbc724b..000000000 --- a/deployments/linea-goerli/usdc/migrations/1686918613_configurate_and_ens.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { expect } from 'chai'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; -const lineaCOMPAddress = '0xab3134fa5edfb3dc64aa790e8bb6448117d18fe9'; - -export default migration('1686918613_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - comet, - cometAdmin, - configurator, - rewards - } = await deploymentManager.getContracts(); - - const { - lineaMessageService, - lineaL1TokenBridge, - lineaL1usdcBridge, - governor, - COMP, - USDC - } = await govDeploymentManager.getContracts(); - - // ENS Setup - // See also: https://docs.ens.domains/contract-api-reference/name-processing - const ENSResolver = await govDeploymentManager.existing( - 'ENSResolver', - ENSResolverAddress, - 'goerli' - ); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const lineaGoerliChainId = ( - await deploymentManager.hre.ethers.provider.getNetwork() - ).chainId.toString(); - const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address }; - const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); - if (officialMarketsJSON[lineaGoerliChainId]) { - officialMarketsJSON[lineaGoerliChainId].push(newMarketObject); - } else { - officialMarketsJSON[lineaGoerliChainId] = [newMarketObject]; - } - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, lineaCOMPAddress] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address, rewards.address], - [0, 0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)' - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata] - ] - ); - - const COMPAmountToBridge = exp(1_000, 18); - const USDCAmountToBridge = exp(100_000, 6); - - const goerliActions = [ - // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Linea-Goerli. - { - contract: lineaMessageService, - signature: 'sendMessage(address,uint256,bytes)', - args: [bridgeReceiver.address, 0, l2ProposalData] - }, - - // 2. Approve Goerli's L1StandardBridge to take Timelock's COMP (for bridging) - { - contract: COMP, - signature: 'approve(address,uint256)', - args: [lineaL1TokenBridge.address, COMPAmountToBridge] - }, - // 3. Bridge COMP from Goerli to Linea-Goerli Comet using L1StandardBridge - { - contract: lineaL1TokenBridge, - signature: 'bridgeToken(address,uint256,address)', - args: [COMP.address, COMPAmountToBridge, rewards.address] - }, - // 4. Approve Goerli's L1usdcBridge to take Timelock's USDC (for bridging) - { - contract: USDC, - signature: 'approve(address,uint256)', - args: [lineaL1usdcBridge.address, USDCAmountToBridge] - }, - // 5. Bridge USDC from Goerli to Linea-Goerli Comet using L1usdcBridge - { - contract: lineaL1usdcBridge, - signature: 'depositTo(uint256,address)', - args: [USDCAmountToBridge, rewards.address] - }, - // 6. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] - ) - } - ]; - - const description = - '# Configurate Linea-Goerli cUSDCv3 market, set reward config, bridge over USDC and COMP, and update ENS text record.'; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(goerliActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify( - deploymentManager: DeploymentManager, - govDeploymentManager: DeploymentManager, - preMigrationBlockNumber: number - ) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // We spider here to pull in Linea COMP now that reward config has been set - - const { comet, rewards, COMP, USDC } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), - baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), - baseMinForRewards: exp(1000, 6), - numAssets: 2, - WETH: { - borrowCollateralFactor: exp(0.775, 18), - liquidationFactor: exp(0.95, 18), - supplyCap: exp(1000, 18) - }, - WBTC: { - offset: 1, - asset: '0xdbcd5bafbaa8c1b326f14ec0c8b125db57a5cc4c', - priceFeed: '0x625e78891611D5A6227Ff78548C373b56B0C8ea0', - scale: exp(1, 18), - borrowCollateralFactor: exp(0.7, 18), - liquidateCollateralFactor: exp(0.75, 18), - liquidationFactor: exp(0.93, 18), - supplyCap: exp(300, 18) - }, - }); - - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(COMP.address); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - - // 2. & 3. - expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(1_000, 18)); - - // 4 & 5, - expect(await USDC.balanceOf(rewards.address)).to.be.equal(exp(100_000, 6)); - - // 6. - const ENSResolver = await govDeploymentManager.existing( - 'ENSResolver', - ENSResolverAddress, - 'goerli' - ); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188' - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F' - } - ], - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839' - } - ], - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456' - } - ], - 84531: [ - { - baseSymbol: 'USDC', - cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1' - }, - { - baseSymbol: 'WETH', - cometAddress: '0xED94f3052638620fE226a9661ead6a39C2a265bE' - } - ], - 59140: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address - } - ] - }); - } -}); diff --git a/deployments/linea-goerli/usdc/migrations/1687878801_increase_supply_caps.ts b/deployments/linea-goerli/usdc/migrations/1687878801_increase_supply_caps.ts deleted file mode 100644 index bf848322e..000000000 --- a/deployments/linea-goerli/usdc/migrations/1687878801_increase_supply_caps.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { - diffState, - getCometConfig, -} from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { - calldata, - exp, - getConfigurationStruct, - proposal, -} from '../../../../src/deploy'; -import { expect } from 'chai'; - -export default migration('1687878801_increase_supply_caps', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async ( - deploymentManager: DeploymentManager, - govDeploymentManager: DeploymentManager, - vars: Vars - ) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - comet, - cometAdmin, - configurator, - } = await deploymentManager.getContracts(); - - const { - lineaMessageService, - governor, - } = await govDeploymentManager.getContracts(); - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration( - comet.address, - configuration - ) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address], - [0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata], - ] - ); - - const goerliActions = [ - // 1. Set Comet configuration - { - contract: lineaMessageService, - signature: 'sendMessage(address,uint256,bytes)', - args: [bridgeReceiver.address, 0, l2ProposalData], - }, - ]; - - const description = '# Increase supply caps for Linea-Goerli cUSDCv3 market'; - const txn = await govDeploymentManager.retry(async () => - trace( - await governor.propose(...(await proposal(goerliActions, description))) - ) - ); - - const event = txn.events.find((event) => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify( - deploymentManager: DeploymentManager, - govDeploymentManager: DeploymentManager, - preMigrationBlockNumber: number - ) { - - const { comet } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - baseBorrowMin: exp(0.001, 6), - WETH: { - supplyCap: exp(10_000_000, 18) - }, - WBTC: { - supplyCap: exp(30_000, 18) - }, - }); - } -}); diff --git a/deployments/linea-goerli/usdc/relations.ts b/deployments/linea-goerli/usdc/relations.ts deleted file mode 100644 index f60791c96..000000000 --- a/deployments/linea-goerli/usdc/relations.ts +++ /dev/null @@ -1,37 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/linea/LineaBridgeReceiver.sol:LineaBridgeReceiver' - }, - // COMP - '0xab3134fa5edfb3dc64aa790e8bb6448117d18fe9': { - artifact: 'contracts/ERC20.sol:ERC20', - }, - // WBTC - '0xdbcd5bafbaa8c1b326f14ec0c8b125db57a5cc4c': { - artifact: 'contracts/ERC20.sol:ERC20', - }, - l2MessageService: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - l2TokenBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - l2usdcBridge: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - } -}; diff --git a/deployments/linea-goerli/usdc/roots.json b/deployments/linea-goerli/usdc/roots.json deleted file mode 100644 index 703086f1f..000000000 --- a/deployments/linea-goerli/usdc/roots.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "comet": "0xa84b24A43ba1890A165f94Ad13d0196E5fD1023a", - "configurator": "0x079f68Fa440A0F04d741a5Aba7DA8fE9DfB0AA8B", - "rewards": "0x44411C605eb7e009cad03f3847cfbbFCF8895130", - "bridgeReceiver": "0x06F066A9C0633507EAc640D89442C77748C3a2a8", - "l2MessageService": "0xC499a572640B64eA1C8c194c43Bc3E19940719dC", - "l2TokenBridge": "0xB191E3d98074f92584E5205B99c3F17fB2068927", - "bulker": "0xad6729C101691A63F7d1e4CcbaD04bC8c6818a22", - "fauceteer": "0x953d0A78eeC15E76266792fE163dC5316F5c2aca" -} \ No newline at end of file diff --git a/deployments/mainnet/usdc/migrations/1735299739_update_comet_to_support_more_collaterals.ts b/deployments/mainnet/usdc/migrations/1735299739_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..12c037d1f --- /dev/null +++ b/deployments/mainnet/usdc/migrations/1735299739_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,197 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; + +let newCometExtAddress: string; + +const USDS_COMET = '0x5D409e56D886231aDAf00c8775665AD0f9897b56'; +const USDT_COMET = '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840'; + +export default migration('1735299739_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, _, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const mainnetActions = [ + // 1. Set the factory in the Configurator for the USDC comet + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactoryExtendedAssetList], + }, + // 2. Set the factory in the Configurator for the USDS comet + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [USDS_COMET, cometFactoryExtendedAssetList], + }, + // 3. Set the factory in the Configurator for the USDT comet + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [USDT_COMET, cometFactoryExtendedAssetList], + }, + // 4. Set new CometExt as the extension delegate for the USDC comet + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [comet.address, newCometExt], + }, + // 5. Set new CometExt as the extension delegate for the USDS comet + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [USDS_COMET, newCometExt], + }, + // 6. Set new CometExt as the extension delegate for the USDT comet + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [USDT_COMET, newCometExt], + }, + // 7. Deploy and upgrade to a new version of Comet for the USDC comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + // 8. Deploy and upgrade to a new version of Comet for the USDS comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, USDS_COMET], + }, + // 9. Deploy and upgrade to a new version of Comet for the USDT comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, USDT_COMET], + }, + ]; + + const { timelock } = await deploymentManager.getContracts(); + // impersonate the timelock + await deploymentManager.hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [timelock.address], + }); + + const description = '# Update USDC, USDT and USDS Comets on Mainnet to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update 3 Comets to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update a Compound III USDC, USDT and USDS markets on Ethereum. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory for cUSDCv3 to the newly deployed factory.\n\nThe second action sets the extension delegate for cUSDCv3 to the newly deployed contract.\n\nThe third action deploys and upgrades cUSDCv3 to a new version.\n\nThe fourth action sets the factory for cUSDTv3 to the newly deployed factory.\n\nThe fifth action sets the extension delegate for cUSDTv3 to the newly deployed contract.\n\nThe sixth action deploys and upgrades cUSDTv3 to a new version.\n\nThe seventh action sets the factory for cUSDSv3 to the newly deployed factory.\n\nThe eighth action sets the extension delegate for cUSDSv3 to the newly deployed contract.\n\nThe ninth action deploys and upgrades cUSDSv3 to a new version.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNewUSDC = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDC = await cometNewUSDC.assetList(); + + expect(assetListAddressUSDC).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDS = new Contract( + USDS_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDS = await cometNewUSDS.assetList(); + + expect(assetListAddressUSDS).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDT = new Contract( + USDT_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDT = await cometNewUSDT.assetList(); + + expect(assetListAddressUSDT).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mainnet/usds/migrations/1735299744_update_comet_to_support_more_collaterals.ts b/deployments/mainnet/usds/migrations/1735299744_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..ed41f83a3 --- /dev/null +++ b/deployments/mainnet/usds/migrations/1735299744_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299744_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, _, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const mainnetActions = [ + // 1. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactoryExtendedAssetList], + }, + // 2. Set new CometExt as the extension delegate + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [comet.address, newCometExt], + }, + // 3. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mainnet/usdt/migrations/1735299748_update_comet_to_support_more_collaterals.ts b/deployments/mainnet/usdt/migrations/1735299748_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..defc79f86 --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1735299748_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299748_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, _, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const mainnetActions = [ + // 1. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactoryExtendedAssetList], + }, + // 2. Set new CometExt as the extension delegate + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [comet.address, newCometExt], + }, + // 3. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mainnet/usdt/relations.ts b/deployments/mainnet/usdt/relations.ts index 8036ed523..3bc07940d 100644 --- a/deployments/mainnet/usdt/relations.ts +++ b/deployments/mainnet/usdt/relations.ts @@ -28,4 +28,12 @@ export default { } } }, + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; \ No newline at end of file diff --git a/deployments/mainnet/weth/migrations/1735299752_update_comet_to_support_more_collaterals.ts b/deployments/mainnet/weth/migrations/1735299752_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..f6544a716 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1735299752_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,157 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; + +let newCometExtAddress: string; +const WSTETH_COMET = '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3'; + +export default migration('1735299752_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, _, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const mainnetActions = [ + // 1. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactoryExtendedAssetList], + }, + // 2. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [WSTETH_COMET, cometFactoryExtendedAssetList], + }, + // 3. Set new CometExt as the extension delegate + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [comet.address, newCometExt], + }, + // 4. Set new CometExt as the extension delegate + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [WSTETH_COMET, newCometExt], + }, + // 5. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + // 6. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, WSTETH_COMET], + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewWSTETH = new Contract( + WSTETH_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressWSTETH = await cometNewWSTETH.assetList(); + + expect(assetListAddressWSTETH).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mainnet/wsteth/migrations/1735299760_update_comet_to_support_more_collaterals.ts b/deployments/mainnet/wsteth/migrations/1735299760_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..4ee265202 --- /dev/null +++ b/deployments/mainnet/wsteth/migrations/1735299760_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299760_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, _, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const mainnetActions = [ + // 1. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactoryExtendedAssetList], + }, + // 2. Set new CometExt as the extension delegate + { + contract: configurator, + signature: 'setExtensionDelegate(address,address)', + args: [comet.address, newCometExt], + }, + // 3. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mantle/usde/migrations/1735299778_update_comet_to_support_more_collaterals.ts b/deployments/mantle/usde/migrations/1735299778_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..f87f16df8 --- /dev/null +++ b/deployments/mantle/usde/migrations/1735299778_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299778_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + + const { + mantleL1CrossDomainMessenger, + governor + } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Mantle. + { + contract: mantleL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 2_500_000], + }, + ]; + + const description = '# Update USDe Comet on Mantle to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update Mantle cUSDeV3 Comet to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update Compound III USDe markets on Mantle. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/mumbai/usdc/configuration.json b/deployments/mumbai/usdc/configuration.json deleted file mode 100644 index cb9f2656f..000000000 --- a/deployments/mumbai/usdc/configuration.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenPriceFeed": "0x572dDec9087154dC5dfBB1546Bb62713147e0Ab0", - "borrowMin": "100e6", - "storeFrontPriceFactor": 0.5, - "targetReserves": "5000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000011574074074074073e15", - "baseBorrowSpeed": "0.0011458333333333333e15", - "baseMinForRewards": "10000e6" - }, - "assets": { - "DAI": { - "priceFeed": "0x0FCAa9c899EC5A91eBc3D5Dd869De833b06fB046", - "decimals": "18", - "borrowCF": 0.79, - "liquidateCF": 0.85, - "liquidationFactor": 0.93, - "supplyCap": "1_000_000e18" - }, - "WETH": { - "priceFeed": "0x0715A7794a1dc8e42615F059dD6e406A6594651A", - "decimals": "18", - "borrowCF": 0.82, - "liquidateCF": 0.85, - "liquidationFactor": 0.93, - "supplyCap": "50_000e18" - }, - "WBTC": { - "priceFeed": "0x007A22900a3B98143368Bd5906f8E17e9867581b", - "decimals": "8", - "borrowCF": 0.7, - "liquidateCF": 0.75, - "liquidationFactor": 0.93, - "supplyCap": "20_000e8" - }, - "WMATIC": { - "priceFeed": "0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada", - "decimals": "18", - "borrowCF": 0.82, - "liquidateCF": 0.85, - "liquidationFactor": 0.93, - "supplyCap": "500_000e18" - } - } -} \ No newline at end of file diff --git a/deployments/mumbai/usdc/deploy.ts b/deployments/mumbai/usdc/deploy.ts deleted file mode 100644 index 45440653e..000000000 --- a/deployments/mumbai/usdc/deploy.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet, exp, sameAddress, wait } from '../../../src/deploy'; - -const clone = { - usdcImpl: '0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF', - usdcProxy: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - weth: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', - wbtc: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', - wmatic: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', - dai: '0x6b175474e89094c44da98b954eedeac495271d0f' -}; - -const FX_CHILD = "0xCf73231F28B7331BBe3124B907840A94851f9f11"; -const GOERLI_TIMELOCK = "0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399"; - -const secondsPerDay = 24 * 60 * 60; - -export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const deployed = await deployContracts(deploymentManager, deploySpec); - await mintTokens(deploymentManager); - return deployed; -} - -async function deployContracts(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { - const trace = deploymentManager.tracer() - const ethers = deploymentManager.hre.ethers; - const signer = await deploymentManager.getSigner(); - - const fxChild = await deploymentManager.existing('fxChild', FX_CHILD, 'mumbai'); - - // Deploy PolygonBridgeReceiver - const bridgeReceiver = await deploymentManager.deploy( - 'bridgeReceiver', - 'bridges/polygon/PolygonBridgeReceiver.sol', - [fxChild?.address] // fxChild - ); - - // Deploy Local Timelock - const localTimelock = await deploymentManager.deploy( - 'timelock', - 'vendor/Timelock.sol', - [ - bridgeReceiver.address, // admin - 10 * 60, // delay - 14 * secondsPerDay, // grace period - 10 * 60, // minimum delay - 30 * secondsPerDay // maxiumum delay - ] - ); - - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - GOERLI_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); - - // USDC - const usdcProxyAdmin = await deploymentManager.deploy('USDC:admin', 'vendor/proxy/transparent/ProxyAdmin.sol', []); - const usdcImpl = await deploymentManager.clone('USDC:implementation', clone.usdcImpl, []); - const usdcProxy = await deploymentManager.clone('USDC', clone.usdcProxy, [usdcImpl.address]); - const usdcProxyAdminSlot = '0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b'; - const USDC = usdcImpl.attach(usdcProxy.address); - - await deploymentManager.idempotent( - async () => !sameAddress(await ethers.provider.getStorageAt(usdcProxy.address, usdcProxyAdminSlot), usdcProxyAdmin.address), - async () => { - trace(`Changing admin of USDC proxy to ${usdcProxyAdmin.address}`); - trace(await wait(usdcProxy.connect(signer).changeAdmin(usdcProxyAdmin.address))); - - trace(`Initializing USDC`); - trace(await wait(USDC.connect(signer).initialize( - 'USD Coin', // name - 'USDC', // symbol - 'USD', // currency - 6, // decimals - signer.address, // Master Minter - signer.address, // Pauser - signer.address, // Blacklister - signer.address // Owner - ))); - } - ); - - const WBTC = await deploymentManager.clone('WBTC', clone.wbtc, []); - const WETH = await deploymentManager.clone( - 'WETH', - clone.weth, - [signer.address], - 'polygon' // NOTE: cloned from Polygon, not mainnet - ); - const WMATIC = await deploymentManager.clone( - 'WMATIC', - clone.wmatic, - [], - 'polygon' // NOTE: cloned from Polygon, not mainnet - ); - const DAI = await deploymentManager.clone('DAI', clone.dai, - [80001] // chain id - ); - - // Deploy Comet - const deployed = await deployComet( - deploymentManager, - deploySpec, - { - governor: localTimelock.address, - pauseGuardian: localTimelock.address - } - ); - - // Deploy Bulker - const bulker = await deploymentManager.deploy('bulker', 'bulkers/BaseBulker.sol', [ - localTimelock.address, - WMATIC.address - ]); - - // Deploy fauceteer - const fauceteer = await deploymentManager.deploy('fauceteer', 'test/Fauceteer.sol', []); - - return { - bridgeReceiver, - bulker, - fauceteer, - fxChild, - ...deployed - }; -} - -async function mintTokens(deploymentManager: DeploymentManager) { - const trace = deploymentManager.tracer(); - const signer = await deploymentManager.getSigner(); - const fauceteer = await deploymentManager.getContractOrThrow('fauceteer'); - - trace(`Attempting to mint as ${signer.address}...`); - - const WMATIC = await deploymentManager.getContractOrThrow('WMATIC'); - await deploymentManager.idempotent( - async () => (await WMATIC.balanceOf(signer.address)).lt(exp(0.01, 18)), - async () => { - trace(`Minting 0.01 WMATIC for signer (this is a precious resource!)`); - trace(await wait(WMATIC.connect(signer).deposit({ value: exp(0.01, 18) }))); - trace(`WMATIC.balanceOf(${signer.address}): ${await WMATIC.balanceOf(signer.address)}`); - } - ); - - const WETH = await deploymentManager.getContractOrThrow('WETH'); - await deploymentManager.idempotent( - async () => (await WETH.balanceOf(fauceteer.address)).eq(0), - async () => { - trace(`Minting 10_000 WETH to fauceteer`); - const amount = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [exp(10_000, await WETH.decimals())] - ); - trace(await wait(WETH.connect(signer).deposit(fauceteer.address, amount))); - trace(`WETH.balanceOf(${fauceteer.address}): ${await WETH.balanceOf(fauceteer.address)}`); - } - ); - - // If we haven't spidered new contracts (which we could before minting, but its slow), - // then the proxy contract won't have the impl functions yet, so just do it explicitly - const usdcProxy = await deploymentManager.getContractOrThrow('USDC'); - const usdcImpl = await deploymentManager.getContractOrThrow('USDC:implementation'); - const USDC = usdcImpl.attach(usdcProxy.address); - await deploymentManager.idempotent( - async () => (await USDC.balanceOf(fauceteer.address)).eq(0), - async () => { - trace(`Minting 100M USDC to fauceteer`); - const amount = exp(100_000_000, await USDC.decimals()); - trace(await wait(USDC.connect(signer).configureMinter(signer.address, amount))); - trace(await wait(USDC.connect(signer).mint(fauceteer.address, amount))); - trace(`USDC.balanceOf(${fauceteer.address}): ${await USDC.balanceOf(fauceteer.address)}`); - } - ); - - const WBTC = await deploymentManager.getContractOrThrow('WBTC'); - await deploymentManager.idempotent( - async () => (await WBTC.balanceOf(fauceteer.address)).eq(0), - async () => { - trace(`Minting 20 WBTC to fauceteer`); - const amount = exp(20, await WBTC.decimals()); - trace(await wait(WBTC.connect(signer).mint(fauceteer.address, amount))); - trace(`WBTC.balanceOf(${fauceteer.address}): ${await WBTC.balanceOf(fauceteer.address)}`); - } - ); - - const DAI = await deploymentManager.getContractOrThrow('DAI'); - await deploymentManager.idempotent( - async () => (await DAI.balanceOf(fauceteer.address)).eq(0), - async () => { - trace(`Minting 100M DAI to fauceteer`); - const amount = exp(100_000_000, await DAI.decimals()); - trace(await wait(DAI.connect(signer).mint(fauceteer.address, amount))); - trace(`DAI.balanceOf(${fauceteer.address}): ${await DAI.balanceOf(fauceteer.address)}`); - } - ); -} \ No newline at end of file diff --git a/deployments/mumbai/usdc/migrations/1676659582_configurate_and_ens.ts b/deployments/mumbai/usdc/migrations/1676659582_configurate_and_ens.ts deleted file mode 100644 index 4d64e0f11..000000000 --- a/deployments/mumbai/usdc/migrations/1676659582_configurate_and_ens.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Contract } from 'ethers'; -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { expect } from 'chai'; -import {ERC20__factory} from '../../../../build/types'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; - -const ERC20PredicateAddress = '0xdD6596F2029e6233DEFfaCa316e6A95217d4Dc34'; -const RootChainManagerAddress = '0xBbD7cBFA79faee899Eaf900F13C9065bF03B1A74'; - -export default migration('1676659582_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - comet, - cometAdmin, - configurator, - rewards, - WBTC, - WETH, - WMATIC - } = await deploymentManager.getContracts(); - - const { - fxRoot, - timelock, - governor, - USDC, - COMP, - } = await govDeploymentManager.getContracts(); - - // ENS Setup - // See also: https://docs.ens.domains/contract-api-reference/name-processing - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const nameHash = ethers.utils.namehash(ENSName); - const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(ENSSubdomainLabel)); - const subdomainHash = ethers.utils.namehash(ENSSubdomain), ttl = 0; - const officialMarkets = { - 5: [ - { - baseSymbol: 'USDC', - cometAddress: (await govDeploymentManager.fromDep('cUSDCv3', 'goerli', 'usdc', 'comet')).address, - }, - { - baseSymbol: 'WETH', - cometAddress: (await govDeploymentManager.fromDep('cWETHv3', 'goerli', 'weth', 'comet')).address - } - ], - - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address, - } - ], - }; - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address], - [0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)' - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata] - ] - ); - - const RootChainManager = await deploymentManager.existing( - 'RootChainManager', - RootChainManagerAddress, - 'goerli' - ); - const USDCAmountToBridge = exp(500, 6); - const COMPAmountToBridge = exp(10_000, 18); - const depositUSDCData = utils.defaultAbiCoder.encode(['uint256'], [USDCAmountToBridge]); - const depositForUSDCCalldata = utils.defaultAbiCoder.encode( - ['address', 'address', 'bytes'], - [comet.address, USDC.address, depositUSDCData] - ); - const depositCOMPData = utils.defaultAbiCoder.encode(['uint256'], [COMPAmountToBridge]); - const depositForCOMPCCalldata = utils.defaultAbiCoder.encode( - ['address', 'address', 'bytes'], - [rewards.address, COMP.address, depositCOMPData] - ); - - const mainnetActions = [ - // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Mumbai. - { - contract: fxRoot, - signature: 'sendMessageToChild(address,bytes)', - args: [bridgeReceiver.address, l2ProposalData] - }, - // 2. Approve Mumbai's ERC20Predicate to take Timelock's USDC (for bridging) - { - contract: USDC, - signature: 'approve(address,uint256)', - args: [ERC20PredicateAddress, USDCAmountToBridge] - }, - // 3. Bridge USDC from mainnet to Mumbai Comet using RootChainManager - { - target: RootChainManager.address, - signature: 'depositFor(address,address,bytes)', - calldata: depositForUSDCCalldata - }, - // Note: Cannot test this flow for COMP because Polygon team has changed the way to map new tokens in January 2023 - // to go through the FxPortal instead of the RootChainManager - // // 4. Approve Mumbai's ERC20Predicate to take Timelock's COMP (for bridging) - // { - // contract: COMP, - // signature: 'approve(address,uint256)', - // args: [ERC20PredicateAddress, COMPAmountToBridge] - // }, - // // 5. Bridge COMP from mainnet to Mumbai CometRewards using RootChainManager - // { - // target: RootChainManager.address, - // signature: 'depositFor(address,address,bytes)', - // calldata: depositForCOMPCCalldata - // }, - - // Note: No need to do this because we have already set up the subdomain on Goerli - // 6. Set up ENS license subdomain with the Timelock as the owner - // { - // target: ENSRegistryAddress, - // signature: 'setSubnodeRecord(bytes32,bytes32,address,address,uint64)', - // calldata: ethers.utils.defaultAbiCoder.encode( - // ['bytes32', 'bytes32', 'address', 'address', 'uint64'], - // [nameHash, labelHash, timelock.address, ENSResolverAddress, ttl] - // ) - // }, - - // 7. Establish the new list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarkets)] - ) - }, - ]; - - const description = "Configurate Mumbai cUSDCv3 market, bridge over USDC and COMP, and update ENS text record."; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(mainnetActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) { - const ethers = deploymentManager.hre.ethers; - - const { - comet, - rewards, - DAI, - WBTC, - WETH, - WMATIC - } = await deploymentManager.getContracts(); - - const { - timelock, - } = await govDeploymentManager.getContracts(); - - // 1. - const daiInfo = await comet.getAssetInfoByAddress(DAI.address); - const wbtcInfo = await comet.getAssetInfoByAddress(WBTC.address); - const wethInfo = await comet.getAssetInfoByAddress(WETH.address); - const wmaticInfo = await comet.getAssetInfoByAddress(WMATIC.address); - expect(await daiInfo.supplyCap).to.be.eq(exp(1_000_000, 18)); - expect(await wbtcInfo.supplyCap).to.be.eq(exp(20_000, 8)); - expect(await wethInfo.supplyCap).to.be.eq(exp(50_000, 18)); - expect(await wmaticInfo.supplyCap).to.be.eq(exp(500_000, 18)); - - // 2. & 3. - // Note: Cannot verify because the USDC we are bridging over is different from the one in Comet - // expect(await comet.getReserves()).to.be.equal(exp(400_000, 6)); - const bridgedUSDC = new Contract( - '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23', - ERC20__factory.createInterface(), - deploymentManager.hre.ethers.provider - ); - expect(await bridgedUSDC.balanceOf(comet.address)).to.be.equal(exp(500, 6)); - - // 4. & 5. - // const bridgedCOMP = new Contract( - // '0x46DaBF9092B40A3fE6c0bC3331Cd928B600754fE', - // ERC20__factory.createInterface(), - // deploymentManager.hre.ethers.provider - // ); - // expect(await bridgedCOMP.balanceOf(rewards.address)).to.be.equal(exp(10_000, 18)); - - // 6. & 7. - const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); - const ENSRegistry = await govDeploymentManager.existing('ENSRegistry', ENSRegistryAddress, 'goerli'); - const nameHash = ethers.utils.namehash(ENSName); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); - expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); - expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); - expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', - }, - ], - - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address, - }, - ] - }); - } -}); \ No newline at end of file diff --git a/deployments/mumbai/usdc/relations.ts b/deployments/mumbai/usdc/relations.ts deleted file mode 100644 index 9e27b0b01..000000000 --- a/deployments/mumbai/usdc/relations.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - 'governor': { - artifact: 'contracts/bridges/polygon/PolygonBridgeReceiver.sol:PolygonBridgeReceiver', - } -}; \ No newline at end of file diff --git a/deployments/mumbai/usdc/roots.json b/deployments/mumbai/usdc/roots.json deleted file mode 100644 index 3947f3f3a..000000000 --- a/deployments/mumbai/usdc/roots.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bridgeReceiver": "0xe195d2cBf7f20E40Cf701a9fA3F01fE89bA5a1da", - "fauceteer": "0x1Cea3a83BA17692cEa8DB37D72446f014480F3bE", - "fxChild": "0xCf73231F28B7331BBe3124B907840A94851f9f11", - "comet": "0xF09F0369aB0a875254fB565E52226c88f10Bc839", - "configurator": "0x64550801B8bf3BF4D8792d46D8903F82e2EC95A9", - "rewards": "0x0785f2AC0dCBEDEE4b8D62c25A34098E9A0dF4bB", - "bulker": "0x990D086E52B132f5b4f769829612F756a4a20bb8" -} \ No newline at end of file diff --git a/deployments/optimism/usdc/migrations/1735299799_update_comet_to_support_more_collaterals.ts b/deployments/optimism/usdc/migrations/1735299799_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..441d954e0 --- /dev/null +++ b/deployments/optimism/usdc/migrations/1735299799_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,209 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +const USDT_COMET = '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214'; +const WETH_COMET = '0xE36A30D249f7761327fd973001A32010b521b6Fd'; + +export default migration('1735299799_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const setFactoryCalldataUSDT = await calldata( + configurator.populateTransaction.setFactory(USDT_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDT = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDT_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDT = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDT_COMET] + ); + + const setFactoryCalldataWETH = await calldata( + configurator.populateTransaction.setFactory(WETH_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataWETH = await calldata( + configurator.populateTransaction.setExtensionDelegate(WETH_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataWETH = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, WETH_COMET] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address, + ], + [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0 + ], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [ + setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata, + setFactoryCalldataUSDT, setExtensionDelegateCalldataUSDT, deployAndUpgradeToCalldataUSDT, + setFactoryCalldataWETH, setExtensionDelegateCalldataWETH, deployAndUpgradeToCalldataWETH, + ], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Update USDC, USDT and WETH Comets on Optimism to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update 4 Comets to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update Compound III USDT, USDC and WETH markets on Optimism. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version for all 4 comets: cUSDTv3, cUSDCv3 and cWETHv3.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewUSDC = new Contract( + USDT_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressUSDC = await cometNewUSDC.assetList(); + + expect(assetListAddressUSDC).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + + const cometNewWETH = new Contract( + WETH_COMET, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddressWETH = await cometNewWETH.assetList(); + + expect(assetListAddressWETH).to.not.be.equal(ethers.constants.AddressZero); + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/optimism/usdc/relations.ts b/deployments/optimism/usdc/relations.ts index 7ef784489..179b7dff3 100644 --- a/deployments/optimism/usdc/relations.ts +++ b/deployments/optimism/usdc/relations.ts @@ -33,4 +33,13 @@ export default { } } }, + + ERC1967Proxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; diff --git a/deployments/optimism/usdt/migrations/1735299805_update_comet_to_support_more_collaterals.ts b/deployments/optimism/usdt/migrations/1735299805_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..aaeaf80ad --- /dev/null +++ b/deployments/optimism/usdt/migrations/1735299805_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,143 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299805_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/optimism/weth/migrations/1735299811_update_comet_to_support_more_collaterals.ts b/deployments/optimism/weth/migrations/1735299811_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..9ffa7939a --- /dev/null +++ b/deployments/optimism/weth/migrations/1735299811_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,143 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299811_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/polygon/usdc/migrations/1735299827_update_comet_to_support_more_collaterals.ts b/deployments/polygon/usdc/migrations/1735299827_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..5248476ef --- /dev/null +++ b/deployments/polygon/usdc/migrations/1735299827_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,167 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +const USDT_COMET = '0xaeB318360f27748Acb200CE616E389A6C9409a07'; + +export default migration('1735299827_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, fxRoot } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + + const setFactoryCalldataUSDT = await calldata( + configurator.populateTransaction.setFactory(USDT_COMET, cometFactoryExtendedAssetList) + ); + const setExtensionDelegateCalldataUSDT = await calldata( + configurator.populateTransaction.setExtensionDelegate(USDT_COMET, newCometExt) + ); + const deployAndUpgradeToCalldataUSDT = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, USDT_COMET] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, configurator.address, cometAdmin.address, + configurator.address, configurator.address, cometAdmin.address + ], + [ + 0, 0, 0, + 0, 0, 0 + ], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [ + setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata, + setFactoryCalldataUSDT, setExtensionDelegateCalldataUSDT, deployAndUpgradeToCalldataUSDT + ], + ] + ); + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Polygon. + { + contract: fxRoot, + signature: 'sendMessageToChild(address,bytes)', + args: [bridgeReceiver.address, l2ProposalData], + }, + ]; + + const description = '# Update USDC and USDT Comets on Polygon to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update 2 Comets to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update Compound III USDT and USDC markets on Polygon. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version for all 4 comets: cUSDTv3 and cUSDCv3.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/polygon/usdt/migrations/1735299832_update_comet_to_support_more_collaterals.ts b/deployments/polygon/usdt/migrations/1735299832_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..55e772a1c --- /dev/null +++ b/deployments/polygon/usdt/migrations/1735299832_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,143 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299832_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, fxRoot } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Polygon. + { + contract: fxRoot, + signature: 'sendMessageToChild(address,bytes)', + args: [bridgeReceiver.address, l2ProposalData], + }, + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/deployments/relations.ts b/deployments/relations.ts index f0ea7c6ca..1655c90c6 100644 --- a/deployments/relations.ts +++ b/deployments/relations.ts @@ -31,7 +31,14 @@ const relationConfigMap: RelationConfigMap = { }) ); }, - alias: async (token) => token.symbol(), + alias: async (token) => { + try { + return token.symbol(); + } + catch (e) { + throw new Error(`Failed to get symbol for token ${token.address}`); + } + }, }, assetPriceFeeds: { field: async (comet) => { diff --git a/deployments/scroll-goerli/usdc/configuration.json b/deployments/scroll-goerli/usdc/configuration.json deleted file mode 100644 index 8d370324f..000000000 --- a/deployments/scroll-goerli/usdc/configuration.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "Compound USDC", - "symbol": "cUSDCv3", - "baseToken": "USDC", - "baseTokenAddress": "0x67aE69Fd63b4fc8809ADc224A9b82Be976039509", - "baseTokenPriceFeed": "0xbbb06bDcf221Ae565f458b990DE83Cbb3D27C2aA", - "borrowMin": "0.001e6", - "storeFrontPriceFactor": 0.5, - "targetReserves": "1000000e6", - "rates": { - "supplyKink": 0.8, - "supplySlopeLow": 0.0325, - "supplySlopeHigh": 0.4, - "supplyBase": 0, - "borrowKink": 0.8, - "borrowSlopeLow": 0.035, - "borrowSlopeHigh": 0.25, - "borrowBase": 0.015 - }, - "tracking": { - "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", - "baseBorrowSpeed": "0.000402083333333e15", - "baseMinForRewards": "1000e6" - }, - "assets": { - "WETH": { - "address": "0xa1EA0B2354F5A344110af2b6AD68e75545009a03", - "priceFeed": "0x2287BAF672879935cB944a2C050971515E73da65", - "decimals": "18", - "borrowCF": 0.775, - "liquidateCF": 0.825, - "liquidationFactor": 0.95, - "supplyCap": "10000000e18" - } - } -} diff --git a/deployments/scroll-goerli/usdc/deploy.ts b/deployments/scroll-goerli/usdc/deploy.ts deleted file mode 100644 index 93fb99f9b..000000000 --- a/deployments/scroll-goerli/usdc/deploy.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; -import { DeploySpec, deployComet, exp, wait } from '../../../src/deploy'; - -const secondsPerDay = 24 * 60 * 60; - -const GOERLI_TIMELOCK = '0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399'; // L1 contract - -export default async function deploy( - deploymentManager: DeploymentManager, - deploySpec: DeploySpec -): Promise { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - - // Pull in existing assets - const WETH = await deploymentManager.existing( - 'WETH', - '0xa1EA0B2354F5A344110af2b6AD68e75545009a03', - 'scroll-goerli' - ); - - const l2Messenger = await deploymentManager.existing( - 'l2Messenger', - '0xb75d7e84517e1504C151B270255B087Fd746D34C', - 'scroll-goerli' - ); - - const l2ERC20Gateway = await deploymentManager.existing( - 'l2ERC20Gateway', - '0xB878F37BB278bf0e4974856fFe86f5e6F66BD725', - 'scroll-goerli' - ); - - const l2ETHGateway = await deploymentManager.existing( - 'l2ETHGateway', - '0x32139B5C8838E94fFcD83E60dff95Daa7F0bA14c', - 'scroll-goerli' - ); - - const l2WETHGateway = await deploymentManager.existing( - 'l2WETHGateway', - '0xBb88bF582F2BBa46702621dae5CB9271057bC85b', - 'scroll-goerli' - ); - - // Deploy ScrollBridgeReceiver - const bridgeReceiver = await deploymentManager.deploy( - 'bridgeReceiver', - 'bridges/scroll/ScrollBridgeReceiver.sol', - [l2Messenger.address] - ); - - // Deploy Local Timelock - const localTimelock = await deploymentManager.deploy('timelock', 'vendor/Timelock.sol', [ - bridgeReceiver.address, // admin - 10 * 60, // delay - 14 * secondsPerDay, // grace period - 10 * 60, // minimum delay - 30 * secondsPerDay // maximum delay - ]); - - // Initialize BridgeReceiver - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - GOERLI_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); - - // Deploy Comet - const deployed = await deployComet(deploymentManager, deploySpec); - const { comet } = deployed; - - // Deploy Bulker - const bulker = await deploymentManager.deploy('bulker', 'bulkers/BaseBulker.sol', [ - await comet.governor(), // admin - WETH.address // weth - ]); - - // Deploy fauceteer - const fauceteer = await deploymentManager.deploy('fauceteer', 'test/Fauceteer.sol', []); - - return { - ...deployed, - bridgeReceiver, - l2Messenger, - l2ERC20Gateway, - l2ETHGateway, - l2WETHGateway, - bulker, - fauceteer, - }; -} diff --git a/deployments/scroll-goerli/usdc/migrations/1694594095_configurate_and_ens.ts b/deployments/scroll-goerli/usdc/migrations/1694594095_configurate_and_ens.ts deleted file mode 100644 index 33e2fdfb3..000000000 --- a/deployments/scroll-goerli/usdc/migrations/1694594095_configurate_and_ens.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; -import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; -import { migration } from '../../../../plugins/deployment_manager/Migration'; -import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; -import { expect } from 'chai'; - -const ENSName = 'compound-community-licenses.eth'; -const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; -const ENSSubdomainLabel = 'v3-additional-grants'; -const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; -const ENSTextRecordKey = 'v3-official-markets'; -const scrollCOMPAddress = '0xE90a006650cda1F8390f95f45132B36bA9038bdF'; -const scrollL1StandardERC20GatewayAddress = "0xeF37207c1A1efF6D6a9d7BfF3cF4270e406d319b" - -export default migration('1694594095_configurate_and_ens', { - prepare: async (deploymentManager: DeploymentManager) => { - return {}; - }, - - enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { - const trace = deploymentManager.tracer(); - const ethers = deploymentManager.hre.ethers; - const { utils } = ethers; - - const { - bridgeReceiver, - comet, - cometAdmin, - configurator, - rewards - } = await deploymentManager.getContracts(); - - const { - scrollMessenger, - scrollL1TokenBridge, - governor, - COMP, - } = await govDeploymentManager.getContracts(); - - // ENS Setup - // See also: https://docs.ens.domains/contract-api-reference/name-processing - const ENSResolver = await govDeploymentManager.existing( - 'ENSResolver', - ENSResolverAddress, - 'goerli' - ); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const scrollGoerliChainId = ( - await deploymentManager.hre.ethers.provider.getNetwork() - ).chainId.toString(); - const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address }; - const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); - if (officialMarketsJSON[scrollGoerliChainId]) { - officialMarketsJSON[scrollGoerliChainId].push(newMarketObject); - } else { - officialMarketsJSON[scrollGoerliChainId] = [newMarketObject]; - } - - const configuration = await getConfigurationStruct(deploymentManager); - - const setConfigurationCalldata = await calldata( - configurator.populateTransaction.setConfiguration(comet.address, configuration) - ); - const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [configurator.address, comet.address] - ); - const setRewardConfigCalldata = utils.defaultAbiCoder.encode( - ['address', 'address'], - [comet.address, scrollCOMPAddress] - ); - const l2ProposalData = utils.defaultAbiCoder.encode( - ['address[]', 'uint256[]', 'string[]', 'bytes[]'], - [ - [configurator.address, cometAdmin.address, rewards.address], - [0, 0, 0], - [ - 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', - 'deployAndUpgradeTo(address,address)', - 'setRewardConfig(address,address)' - ], - [setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata] - ] - ); - - const COMPAmountToBridge = exp(10_000, 18); - - const goerliActions = [ - // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Scroll Alpha. - { - contract: scrollMessenger, - signature: 'sendMessage(address,uint256,bytes,uint256)', - args: [bridgeReceiver.address, 0, l2ProposalData, 600_000] - }, - - // 2. Approve Goerli's StandardERC20Gateway to take Timelock's COMP (for bridging) - { - contract: COMP, - signature: 'approve(address,uint256)', - args: [scrollL1StandardERC20GatewayAddress, COMPAmountToBridge] - }, - // 3. Bridge COMP from Goerli to Scroll Alpha Comet using L1GatewayRouter - { - contract: scrollL1TokenBridge, - signature: 'depositERC20(address,address,uint256,uint256)', - args: [COMP.address, rewards.address, COMPAmountToBridge, 300_000] - }, - // 4. Update the list of official markets - { - target: ENSResolverAddress, - signature: 'setText(bytes32,string,string)', - calldata: ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'string', 'string'], - [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] - ) - } - ]; - - const description = - '# Configurate Scroll Alpha cUSDCv3 market, set reward config, bridge over USDC and COMP, and update ENS text record.'; - const txn = await govDeploymentManager.retry(async () => - trace(await governor.propose(...(await proposal(goerliActions, description)))) - ); - - const event = txn.events.find(event => event.event === 'ProposalCreated'); - const [proposalId] = event.args; - - trace(`Created proposal ${proposalId}.`); - }, - - async enacted(deploymentManager: DeploymentManager): Promise { - return true; - }, - - async verify( - deploymentManager: DeploymentManager, - govDeploymentManager: DeploymentManager, - preMigrationBlockNumber: number - ) { - const ethers = deploymentManager.hre.ethers; - await deploymentManager.spider(); // We spider here to pull in Scroll COMP now that reward config has been set - - const { comet, rewards, COMP, USDC } = await deploymentManager.getContracts(); - - // 1. - const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - expect(stateChanges).to.deep.equal({ - baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), - baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), - baseMinForRewards: exp(1000, 6), - WETH: { - borrowCollateralFactor: exp(0.775, 18), - liquidationFactor: exp(0.95, 18), - supplyCap: exp(1000, 18) - }, - }); - - const config = await rewards.rewardConfig(comet.address); - expect(config.token).to.be.equal(COMP.address); - expect(config.rescaleFactor).to.be.equal(exp(1, 12)); - expect(config.shouldUpscale).to.be.equal(true); - - // 2. & 3. - expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(1_000, 18)); - - // 4 - const ENSResolver = await govDeploymentManager.existing( - 'ENSResolver', - ENSResolverAddress, - 'goerli' - ); - const subdomainHash = ethers.utils.namehash(ENSSubdomain); - const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); - const officialMarkets = JSON.parse(officialMarketsJSON); - expect(officialMarkets).to.deep.equal({ - 5: [ - { - baseSymbol: 'USDC', - cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188' - }, - { - baseSymbol: 'WETH', - cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F' - } - ], - 80001: [ - { - baseSymbol: 'USDC', - cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839' - } - ], - 420: [ - { - baseSymbol: 'USDC', - cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' - } - ], - 421613: [ - { - baseSymbol: 'USDC', - cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456' - } - ], - 84531: [ - { - baseSymbol: 'USDC', - cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1' - }, - { - baseSymbol: 'WETH', - cometAddress: '0xED94f3052638620fE226a9661ead6a39C2a265bE' - } - ], - 59140: [ - { - baseSymbol: 'USDC', - cometAddress: "0xa84b24A43ba1890A165f94Ad13d0196E5fD1023a" - } - ], - 534353: [ - { - baseSymbol: 'USDC', - cometAddress: comet.address - } - ] - }); - } - -}); diff --git a/deployments/scroll-goerli/usdc/relations.ts b/deployments/scroll-goerli/usdc/relations.ts deleted file mode 100644 index 767191e98..000000000 --- a/deployments/scroll-goerli/usdc/relations.ts +++ /dev/null @@ -1,42 +0,0 @@ -import baseRelationConfig from '../../relations'; - -export default { - ...baseRelationConfig, - governor: { - artifact: 'contracts/bridges/scroll/ScrollBridgeReceiver.sol:ScrollBridgeReceiver' - }, - l2Messenger: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - l2ERC20Gateway: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - l2ETHGateway: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - l2WETHGateway: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, - '0x477df03cb8c83ec241df01302a9d13102676eee4': { - artifact: 'contracts/Configurator.sol:Configurator', - }, - '0x3effaacd82fa5a76d539b7d9cee0250f972f115f': { - artifact: 'contracts/CometFactory.sol:CometFactory', - } -}; diff --git a/deployments/scroll-goerli/usdc/roots.json b/deployments/scroll-goerli/usdc/roots.json deleted file mode 100644 index cf9aee3fd..000000000 --- a/deployments/scroll-goerli/usdc/roots.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "comet": "0x149B7023781d1D37689d447A565a1bf5854a8e3d", - "configurator": "0x6f5010E26581DC625802005EC82b30067460610a", - "rewards": "0xB2dbE5c648D8B4329499af8929Ee1d81b577ed5E", - "bridgeReceiver": "0x1ad7e375CF875bf9145fFa511d9D3c3A0d9DD0f8", - "l2Messenger": "0xb75d7e84517e1504C151B270255B087Fd746D34C", - "l2ERC20Gateway": "0xB878F37BB278bf0e4974856fFe86f5e6F66BD725", - "l2ETHGateway": "0x32139B5C8838E94fFcD83E60dff95Daa7F0bA14c", - "l2WETHGateway": "0xBb88bF582F2BBa46702621dae5CB9271057bC85b", - "bulker": "0x055FFa809c8817A1a0967B40027e48856C9D7945", - "fauceteer": "0x4a499858975e824f4ee2B95633B6B975Ab6d029E" -} \ No newline at end of file diff --git a/deployments/scroll/usdc/migrations/1735299855_update_comet_to_support_more_collaterals.ts b/deployments/scroll/usdc/migrations/1735299855_update_comet_to_support_more_collaterals.ts new file mode 100644 index 000000000..f86064978 --- /dev/null +++ b/deployments/scroll/usdc/migrations/1735299855_update_comet_to_support_more_collaterals.ts @@ -0,0 +1,143 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal, exp } from '../../../../src/deploy'; +import { ethers } from 'ethers'; +import { Contract } from 'ethers'; +import { utils } from 'ethers'; + +let newCometExtAddress: string; + +export default migration('1735299855_update_comet_to_support_more_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _assetListFactory = await deploymentManager.deploy( + 'assetListFactory', + 'AssetListFactory.sol', + [] + ); + + const cometFactoryExtendedAssetList = await deploymentManager.deploy( + 'cometFactoryExtendedAssetList', + 'CometFactoryExtendedAssetList.sol', + [] + ); + const { + comet + } = await deploymentManager.getContracts(); + + const extensionDelegate = new Contract( + await comet.extensionDelegate(), + [ + 'function name() external view returns (string)', + 'function symbol() external view returns (string)', + ], + deploymentManager.hre.ethers.provider + ); + const name = await extensionDelegate.name(); + const symbol = await extensionDelegate.symbol(); + + const _newCometExt = await deploymentManager.deploy( + 'CometExtAssetList', + 'CometExtAssetList.sol', + [ + { + name32: ethers.utils.formatBytes32String(name), + symbol32: ethers.utils.formatBytes32String(symbol) + }, + _assetListFactory.address + ] + ); + return { + cometFactoryExtendedAssetList: cometFactoryExtendedAssetList.address, + newCometExt: _newCometExt.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + cometFactoryExtendedAssetList, + newCometExt, + }) { + + const trace = deploymentManager.tracer(); + const { + comet, + cometAdmin, + configurator, + bridgeReceiver, + } = await deploymentManager.getContracts(); + const { governor, scrollMessenger } = await govDeploymentManager.getContracts(); + + newCometExtAddress = newCometExt; + + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactoryExtendedAssetList) + ); + + const setExtensionDelegateCalldata = await calldata( + configurator.populateTransaction.setExtensionDelegate(comet.address, newCometExt) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0], + [ + 'setFactory(address,address)', + 'setExtensionDelegate(address,address)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setExtensionDelegateCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + { + contract: scrollMessenger, + signature: 'sendMessage(address,uint256,bytes,uint256)', + args: [bridgeReceiver.address, 0, l2ProposalData, 1_000_000], + value: exp(0.2, 18) + }, + ]; + + const description = '# Update USDC Comet on Scroll to support more collaterals\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to update Scroll cUSDCv3 Comet to a new version, which supports up to 24 collaterals. This proposal takes the governance steps recommended and necessary to update Compound III USDC markets on Scroll. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario).\n\nDetailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/904) and [forum discussion](https://www.comp.xyz/t/increase-amount-of-collaterals-in-comet/5465).\n\n\n## Proposal Actions\n\nThe first action sets the factory to the newly deployed factory, extension delegate to the newly deployed contract and deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet } = await deploymentManager.getContracts(); + + const cometNew = new Contract( + comet.address, + [ + 'function assetList() external view returns (address)', + ], + deploymentManager.hre.ethers.provider + ); + + const assetListAddress = await cometNew.assetList(); + + expect(assetListAddress).to.not.be.equal(ethers.constants.AddressZero); + + expect(await comet.extensionDelegate()).to.be.equal(newCometExtAddress); + }, +}); diff --git a/hardhat.config.ts b/hardhat.config.ts index f7a92ce4b..a50fbd87a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,11 +18,8 @@ import './tasks/scenario/task.ts'; // Relation Config import relationConfigMap from './deployments/relations'; -import goerliRelationConfigMap from './deployments/goerli/usdc/relations'; -import goerliWethRelationConfigMap from './deployments/goerli/weth/relations'; import sepoliaUsdcRelationConfigMap from './deployments/sepolia/usdc/relations'; import sepoliaWethRelationConfigMap from './deployments/sepolia/weth/relations'; -import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import mainnetUsdtRelationConfigMap from './deployments/mainnet/usdt/relations'; @@ -33,21 +30,15 @@ import polygonUsdtRelationConfigMap from './deployments/polygon/usdt/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; import arbitrumNativeUsdcRelationConfigMap from './deployments/arbitrum/usdc/relations'; import arbitrumWETHRelationConfigMap from './deployments/arbitrum/weth/relations'; -import arbitrumBridgedUsdcGoerliRelationConfigMap from './deployments/arbitrum-goerli/usdc.e/relations'; -import arbitrumGoerliNativeUsdcRelationConfigMap from './deployments/arbitrum-goerli/usdc/relations'; import arbitrumUsdtRelationConfigMap from './deployments/arbitrum/usdt/relations'; import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations'; import baseWethRelationConfigMap from './deployments/base/weth/relations'; import baseUsdcRelationConfigMap from './deployments/base/usdc/relations'; import baseAeroRelationConfigMap from './deployments/base/aero/relations'; -import baseGoerliRelationConfigMap from './deployments/base-goerli/usdc/relations'; -import baseGoerliWethRelationConfigMap from './deployments/base-goerli/weth/relations'; -import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relations'; import optimismRelationConfigMap from './deployments/optimism/usdc/relations'; import optimismUsdtRelationConfigMap from './deployments/optimism/usdt/relations'; import optimismWethRelationConfigMap from './deployments/optimism/weth/relations'; import mantleRelationConfigMap from './deployments/mantle/usde/relations'; -import scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => { @@ -63,10 +54,9 @@ const { POLYGONSCAN_KEY, ARBISCAN_KEY, BASESCAN_KEY, - LINEASCAN_KEY, OPTIMISMSCAN_KEY, MANTLESCAN_KEY, - INFURA_KEY, + SCROLLSCAN_KEY, ANKR_KEY, MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect', REPORT_GAS = 'false', @@ -100,6 +90,7 @@ export function requireEnv(varName, msg?: string): string { 'LINEASCAN_KEY', 'OPTIMISMSCAN_KEY', 'MANTLESCAN_KEY', + 'SCROLLSCAN_KEY' ].map((v) => requireEnv(v)); // Networks @@ -112,15 +103,20 @@ interface NetworkConfig { } const networkConfigs: NetworkConfig[] = [ - { network: 'mainnet', chainId: 1 }, - { network: 'ropsten', chainId: 3 }, - { network: 'rinkeby', chainId: 4 }, - { network: 'goerli', chainId: 5 }, - { network: 'sepolia', chainId: 11155111 }, + { + network: 'mainnet', + chainId: 1, + url: `https://rpc.ankr.com/eth/${ANKR_KEY}`, + }, + { + network: 'sepolia', + chainId: 11155111, + url: `https://rpc.ankr.com/eth_sepolia/${ANKR_KEY}`, + }, { network: 'polygon', chainId: 137, - url: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`, + url: `https://rpc.ankr.com/polygon/${ANKR_KEY}`, }, { network: 'optimism', @@ -131,7 +127,7 @@ const networkConfigs: NetworkConfig[] = [ network: 'mantle', chainId: 5000, // link for scenarios - url: `https://mantle-mainnet.infura.io/v3/${INFURA_KEY}`, + url: `https://rpc.ankr.com/mantle/${ANKR_KEY}`, // link for deployment // url: `https://rpc.mantle.xyz`, }, @@ -143,7 +139,7 @@ const networkConfigs: NetworkConfig[] = [ { network: 'arbitrum', chainId: 42161, - url: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`, + url: `https://rpc.ankr.com/arbitrum/${ANKR_KEY}`, }, { network: 'avalanche', @@ -155,31 +151,6 @@ const networkConfigs: NetworkConfig[] = [ chainId: 43113, url: 'https://api.avax-test.network/ext/bc/C/rpc', }, - { - network: 'mumbai', - chainId: 80001, - url: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`, - }, - { - network: 'arbitrum-goerli', - chainId: 421613, - url: `https://arbitrum-goerli.infura.io/v3/${INFURA_KEY}`, - }, - { - network: 'base-goerli', - chainId: 84531, - url: `https://goerli.base.org/`, - }, - { - network: 'linea-goerli', - chainId: 59140, - url: `https://linea-goerli.infura.io/v3/${INFURA_KEY}`, - }, - { - network: 'scroll-goerli', - chainId: 534353, - url: 'https://alpha-rpc.scroll.io/l2', - }, { network: 'scroll', chainId: 534352, @@ -188,7 +159,7 @@ const networkConfigs: NetworkConfig[] = [ ]; function getDefaultProviderURL(network: string) { - return `https://${network}.infura.io/v3/${INFURA_KEY}`; + return `https://rpc.ankr.com/${network}/${ANKR_KEY}`; } function setupDefaultNetworkProviders(hardhatConfig: HardhatUserConfig) { @@ -255,34 +226,24 @@ const config: HardhatUserConfig = { apiKey: { // Ethereum mainnet: ETHERSCAN_KEY, - ropsten: ETHERSCAN_KEY, - rinkeby: ETHERSCAN_KEY, - goerli: ETHERSCAN_KEY, sepolia: ETHERSCAN_KEY, // Avalanche avalanche: SNOWTRACE_KEY, avalancheFujiTestnet: SNOWTRACE_KEY, // Polygon polygon: POLYGONSCAN_KEY, - polygonMumbai: POLYGONSCAN_KEY, // Arbitrum arbitrumOne: ARBISCAN_KEY, arbitrumTestnet: ARBISCAN_KEY, arbitrum: ARBISCAN_KEY, - 'arbitrum-goerli': ARBISCAN_KEY, // Base base: BASESCAN_KEY, - 'base-goerli': BASESCAN_KEY, - // Linea - 'linea-goerli': LINEASCAN_KEY, // optimism: OPTIMISMSCAN_KEY, optimisticEthereum: OPTIMISMSCAN_KEY, // Mantle mantle: MANTLESCAN_KEY, - // Scroll Testnet - 'scroll-goerli': ETHERSCAN_KEY, // Scroll - 'scroll': ETHERSCAN_KEY, + 'scroll': SCROLLSCAN_KEY, }, customChains: [ { @@ -294,15 +255,6 @@ const config: HardhatUserConfig = { browserURL: 'https://arbiscan.io/' } }, - { - // Hardhat's Etherscan plugin calls the network `arbitrumGoerli`, so we need to add an entry for our own network name - network: 'arbitrum-goerli', - chainId: 421613, - urls: { - apiURL: 'https://api-goerli.arbiscan.io/api', - browserURL: 'https://goerli.arbiscan.io/' - } - }, { // Hardhat's Etherscan plugin doesn't have support Base, so we need to add an entry for our own network name network: 'base', @@ -312,31 +264,6 @@ const config: HardhatUserConfig = { browserURL: 'https://basescan.org/' } }, - { - // Hardhat's Etherscan plugin calls the network `baseGoerli`, so we need to add an entry for our own network name - network: 'base-goerli', - chainId: 84531, - urls: { - apiURL: 'https://api-goerli.basescan.org/api', - browserURL: 'https://goerli.basescan.org/' - } - }, - { - network: 'linea-goerli', - chainId: 59140, - urls: { - apiURL: 'https://api-goerli.lineascan.build/api', - browserURL: 'https://goerli.lineascan.build/' - } - }, - { - network: 'scroll-goerli', - chainId: 534353, - urls: { - apiURL: 'https://alpha-blockscout.scroll.io/api', - browserURL: 'https://alpha-blockscout.scroll.io/' - } - }, { network: 'scroll', chainId: 534352, @@ -369,17 +296,10 @@ const config: HardhatUserConfig = { deploymentManager: { relationConfigMap, networks: { - goerli: { - usdc: goerliRelationConfigMap, - weth: goerliWethRelationConfigMap - }, sepolia: { usdc: sepoliaUsdcRelationConfigMap, weth: sepoliaWethRelationConfigMap }, - mumbai: { - usdc: mumbaiRelationConfigMap - }, mainnet: { usdc: mainnetRelationConfigMap, weth: mainnetWethRelationConfigMap, @@ -397,23 +317,12 @@ const config: HardhatUserConfig = { usdt: arbitrumUsdtRelationConfigMap, weth: arbitrumWETHRelationConfigMap }, - 'arbitrum-goerli': { - 'usdc.e': arbitrumBridgedUsdcGoerliRelationConfigMap, - usdc: arbitrumGoerliNativeUsdcRelationConfigMap - }, 'base': { usdbc: baseUsdbcRelationConfigMap, weth: baseWethRelationConfigMap, usdc: baseUsdcRelationConfigMap, aero: baseAeroRelationConfigMap }, - 'base-goerli': { - usdc: baseGoerliRelationConfigMap, - weth: baseGoerliWethRelationConfigMap - }, - 'linea-goerli': { - usdc: lineaGoerliRelationConfigMap - }, optimism: { usdc: optimismRelationConfigMap, usdt: optimismUsdtRelationConfigMap, @@ -422,9 +331,6 @@ const config: HardhatUserConfig = { 'mantle': { 'usde': mantleRelationConfigMap }, - 'scroll-goerli': { - usdc: scrollGoerliRelationConfigMap - }, 'scroll': { usdc: scrollRelationConfigMap } @@ -469,16 +375,6 @@ const config: HardhatUserConfig = { network: 'fuji', deployment: 'usdc' }, - { - name: 'goerli', - network: 'goerli', - deployment: 'usdc' - }, - { - name: 'goerli-weth', - network: 'goerli', - deployment: 'weth', - }, { name: 'sepolia-usdc', network: 'sepolia', @@ -489,12 +385,6 @@ const config: HardhatUserConfig = { network: 'sepolia', deployment: 'weth' }, - { - name: 'mumbai', - network: 'mumbai', - deployment: 'usdc', - auxiliaryBase: 'goerli' - }, { name: 'polygon', network: 'polygon', @@ -531,18 +421,6 @@ const config: HardhatUserConfig = { deployment: 'weth', auxiliaryBase: 'mainnet' }, - { - name: 'arbitrum-goerli-usdc.e', - network: 'arbitrum-goerli', - deployment: 'usdc.e', - auxiliaryBase: 'goerli' - }, - { - name: 'arbitrum-goerli-usdc', - network: 'arbitrum-goerli', - deployment: 'usdc', - auxiliaryBase: 'goerli' - }, { name: 'base-usdbc', network: 'base', @@ -567,24 +445,6 @@ const config: HardhatUserConfig = { deployment: 'aero', auxiliaryBase: 'mainnet' }, - { - name: 'base-goerli', - network: 'base-goerli', - deployment: 'usdc', - auxiliaryBase: 'goerli' - }, - { - name: 'base-goerli-weth', - network: 'base-goerli', - deployment: 'weth', - auxiliaryBase: 'goerli' - }, - { - name: 'linea-goerli', - network: 'linea-goerli', - deployment: 'usdc', - auxiliaryBase: 'goerli' - }, { name: 'optimism-usdc', network: 'optimism', @@ -609,12 +469,6 @@ const config: HardhatUserConfig = { deployment: 'usde', auxiliaryBase: 'mainnet' }, - { - name: 'scroll-goerli', - network: 'scroll-goerli', - deployment: 'usdc', - auxiliaryBase: 'goerli' - }, { name: 'scroll-usdc', network: 'scroll', diff --git a/plugins/import/etherscan.ts b/plugins/import/etherscan.ts index 332c7fe2c..c16016a4d 100644 --- a/plugins/import/etherscan.ts +++ b/plugins/import/etherscan.ts @@ -10,21 +10,15 @@ export function getEtherscanApiUrl(network: string): string { let host = { rinkeby: 'api-rinkeby.etherscan.io', ropsten: 'api-ropsten.etherscan.io', - goerli: 'api-goerli.etherscan.io', sepolia: 'api-sepolia.etherscan.io', mainnet: 'api.etherscan.io', fuji: 'api-testnet.snowtrace.io', avalanche: 'api.snowtrace.io', - mumbai: 'api-mumbai.polygonscan.com', polygon: 'api.polygonscan.com', arbitrum: 'api.arbiscan.io', - 'arbitrum-goerli': 'api-goerli.arbiscan.io', base: 'api.basescan.org', - 'base-goerli': 'api-goerli.basescan.org', - 'linea-goerli': 'api-goerli.lineascan.build', optimism: 'api-optimistic.etherscan.io', mantle: 'api.mantlescan.xyz', - 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'api.scrollscan.com' }[network]; @@ -39,21 +33,15 @@ export function getEtherscanUrl(network: string): string { let host = { rinkeby: 'rinkeby.etherscan.io', ropsten: 'ropsten.etherscan.io', - goerli: 'goerli.etherscan.io', sepolia: 'sepolia.etherscan.io', mainnet: 'etherscan.io', fuji: 'testnet.snowtrace.io', avalanche: 'snowtrace.io', - mumbai: 'mumbai.polygonscan.com', polygon: 'polygonscan.com', arbitrum: 'arbiscan.io', - 'arbitrum-goerli': 'goerli.arbiscan.io', base: 'basescan.org', - 'base-goerli': 'goerli.basescan.org', - 'linea-goerli': 'goerli.lineascan.build', optimism: 'optimistic.etherscan.io', mantle: 'mantlescan.xyz', - 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'scrollscan.com' }[network]; @@ -68,22 +56,16 @@ export function getEtherscanApiKey(network: string): string { let apiKey = { rinkeby: process.env.ETHERSCAN_KEY, ropsten: process.env.ETHERSCAN_KEY, - goerli: process.env.ETHERSCAN_KEY, sepolia: process.env.ETHERSCAN_KEY, mainnet: process.env.ETHERSCAN_KEY, fuji: process.env.SNOWTRACE_KEY, avalanche: process.env.SNOWTRACE_KEY, - mumbai: process.env.POLYGONSCAN_KEY, polygon: process.env.POLYGONSCAN_KEY, arbitrum: process.env.ARBISCAN_KEY, - 'arbitrum-goerli': process.env.ARBISCAN_KEY, base: process.env.BASESCAN_KEY, - 'base-goerli': process.env.BASESCAN_KEY, - 'linea-goerli': process.env.LINEASCAN_KEY, optimism: process.env.OPTIMISMSCAN_KEY, mantle: process.env.MANTLESCAN_KEY, - 'scroll-goerli': process.env.ETHERSCAN_KEY, - scroll: process.env.ETHERSCAN_KEY + scroll: process.env.SCROLLSCAN_KEY }[network]; if (!apiKey) { diff --git a/plugins/scenario/Runner.ts b/plugins/scenario/Runner.ts index da19c4ebc..e7df109cf 100644 --- a/plugins/scenario/Runner.ts +++ b/plugins/scenario/Runner.ts @@ -153,6 +153,37 @@ export class Runner { } } + +async function retry(fn: () => Promise, retries: number = 10, timeLimit?: number, wait: number = 250) { + try { + return await asyncCallWithTimeout(fn(), timeLimit); + } catch (e) { + if (retries === 0) throw e; + if(e.reason !== 'could not detect network') + throw e; + + console.warn(`Retrying in ${wait}ms...`); + + await new Promise(ok => setTimeout(ok, wait)); + return retry(fn, retries - 1, timeLimit, wait >= 10000 ? 10000 : wait * 2); + } +} +async function asyncCallWithTimeout(asyncPromise: Promise, timeLimit: number = 5000_000) { + let timeoutHandle: string | number | NodeJS.Timeout; + + const timeoutPromise = new Promise((_resolve, reject) => { + timeoutHandle = setTimeout( + () => reject(new Error('Async call timeout limit reached')), + timeLimit + ); + }); + + return Promise.race([asyncPromise, timeoutPromise]).then(result => { + clearTimeout(timeoutHandle); + return result; + }); +} + export async function runScenarios(bases: ForkSpec[]) { const loader = await Loader.load(); const [runningScenarios, skippedScenarios] = loader.splitScenarios(); @@ -161,16 +192,20 @@ export async function runScenarios(bases: ForkSpec[]) { const results: Result[] = []; for (const base of bases) { - const world = new World(base), dm = world.deploymentManager; - const delta = await dm.runDeployScript({ allMissing: true }); - console.log(`[${base.name}] Deployed ${dm.counter} contracts, spent ${dm.spent} to initialize world 🗺`); - console.log(`[${base.name}]\n${dm.diffDelta(delta)}`); - - if (world.auxiliaryDeploymentManager) { - await world.auxiliaryDeploymentManager.spider(); - } - - const runner = new Runner(base, world); + let runner: Runner; + await retry(async () => { + const world = new World(base); + await world.initialize(base); + const dm = world.deploymentManager; + const delta = await dm.runDeployScript({ allMissing: true }); + console.log(`[${base.name}] Deployed ${dm.counter} contracts, spent ${dm.spent} to initialize world 🗺`); + console.log(`[${base.name}]\n${dm.diffDelta(delta)}`); + + if (world.auxiliaryDeploymentManager) { + await world.auxiliaryDeploymentManager.spider(); + } + runner = new Runner(base, world); + }); // NB: contexts are (still) a bit awkward // they prob dont even really need to get passed through here currently diff --git a/plugins/scenario/World.ts b/plugins/scenario/World.ts index ffc8bdb10..35c0b53c6 100644 --- a/plugins/scenario/World.ts +++ b/plugins/scenario/World.ts @@ -28,15 +28,17 @@ export class World { snapshotAuxiliaryDeploymentManager?: DeploymentManager; constructor(base: ForkSpec) { - // Q: should we really need to fork/snapshot the deployment manager? - const hre = hreForBase(base); this.base = base; + } + + async initialize(base: ForkSpec) { + const hre = await hreForBase(base); this.deploymentManager = new DeploymentManager(base.network, base.deployment, hre); + // Q: should we really need to fork/snapshot the deployment manager? this.snapshotDeploymentManager = this.deploymentManager; - if (this.base.auxiliaryBase) { const auxiliaryBase = hre.config.scenario.bases.find(b => b.name === this.base.auxiliaryBase); - this.auxiliaryDeploymentManager = new DeploymentManager(auxiliaryBase.network, auxiliaryBase.deployment, hreForBase(auxiliaryBase)); + this.auxiliaryDeploymentManager = new DeploymentManager(auxiliaryBase.network, auxiliaryBase.deployment, await hreForBase(auxiliaryBase)); this.snapshotAuxiliaryDeploymentManager = this.auxiliaryDeploymentManager; } } diff --git a/plugins/scenario/utils/hreForBase.ts b/plugins/scenario/utils/hreForBase.ts index 5609d9572..b608ac925 100644 --- a/plugins/scenario/utils/hreForBase.ts +++ b/plugins/scenario/utils/hreForBase.ts @@ -1,4 +1,4 @@ -import type { ethers } from 'ethers'; +import { ethers } from 'ethers'; import type { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { HardhatContext } from 'hardhat/internal/context'; @@ -36,7 +36,7 @@ declare module 'hardhat/internal/core/runtime-environment' { } } -export function nonForkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { +export async function nonForkedHreForBase(base: ForkSpec): Promise { const ctx: HardhatContext = HardhatContext.getHardhatContext(); const hardhatArguments = getEnvHardhatArguments( @@ -61,7 +61,17 @@ export function nonForkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { ); } -export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { +function getBlockRollback(base: ForkSpec){ + if(base.blockNumber) + return base.blockNumber; + else if(base.network === 'arbitrum'){ + return undefined; + } + else + return 280; +} + +export async function forkedHreForBase(base: ForkSpec): Promise { const ctx: HardhatContext = HardhatContext.getHardhatContext(); const hardhatArguments = getEnvHardhatArguments(HARDHAT_PARAM_DEFINITIONS, process.env); @@ -73,6 +83,12 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { const baseNetwork = networks[base.network] as HttpNetworkUserConfig; + const provider = new ethers.providers.JsonRpcProvider(baseNetwork.url); + + // noNetwork otherwise + if(!base.blockNumber && baseNetwork.url) + + base.blockNumber = await provider.getBlockNumber() - getBlockRollback(base); // arbitrary number of blocks to go back if (!baseNetwork) { throw new Error(`cannot find network config for network: ${base.network}`); } @@ -96,7 +112,7 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { defaultNetwork: 'hardhat', networks: { hardhat: forkedNetwork, - localhost + localhost: localhost }, }, }; @@ -111,7 +127,7 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment { ); } -export default function hreForBase(base: ForkSpec, fork = true): HardhatRuntimeEnvironment { +export default async function hreForBase(base: ForkSpec, fork = true): Promise { if (fork) { return forkedHreForBase(base); } else { diff --git a/scenario/AddMaticxCollateralScenario.ts b/scenario/AddMaticxCollateralScenario.ts index 783e58d9b..1619bd8e1 100644 --- a/scenario/AddMaticxCollateralScenario.ts +++ b/scenario/AddMaticxCollateralScenario.ts @@ -9,7 +9,7 @@ import { createCrossChainProposal, matchesDeployment } from './utils'; const MATICX_ADDRESS = '0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6'; const MATICX_PRICE_FEED_ADDRESS = '0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403'; const MATICX_WHALES = { - polygon: ['0x68B9220B8E617b7700aCAE1a5Ff43F3eb29257F3'], + polygon: ['0x80cA0d8C38d2e2BcbaB66aA1648Bd1C7160500FE'], }; // TODO: add ability to run ad hoc scenarios against a single migration, to avoid needing the scenario to do all this setup of @@ -33,13 +33,13 @@ scenario( const { albert } = actors; const dm = context.world.deploymentManager; const maticx = await dm.existing( - 'MATICX', + 'MaticX', MATICX_ADDRESS, context.world.base.network, 'contracts/ERC20.sol:ERC20' ); const maticxPricefeed = await dm.existing( - 'MATICX:priceFeed', + 'MaticX:priceFeed', MATICX_PRICE_FEED_ADDRESS, context.world.base.network ); @@ -49,6 +49,10 @@ scenario( dm, MATICX_WHALES.polygon[0] ); + await dm.hre.ethers.provider.send('hardhat_setBalance', [ + maticxWhaleSigner.address, + dm.hre.ethers.utils.hexStripZeros(dm.hre.ethers.utils.parseUnits('100', 'ether').toHexString()), + ]); await maticx .connect(maticxWhaleSigner) .transfer(albert.address, exp(9000, 18).toString()); @@ -106,6 +110,6 @@ scenario( expect(await albert.getCometCollateralBalance(maticx.address)).to.be.equal( supplyAmount ); - expect(await albert.getCometBaseBalance()).to.be.equal(-borrowAmount); + expect(await albert.getCometBaseBalance()).to.be.closeTo(-borrowAmount, 1n); } ); diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index c1ddf3209..74a19f022 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -3,6 +3,7 @@ import { constants, utils } from 'ethers'; import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; +import { getConfigForScenario } from './utils/scenarioHelper'; async function hasNativeAsCollateralOrBase(ctx: CometContext): Promise { const comet = await ctx.getComet(); @@ -22,15 +23,23 @@ async function hasNativeAsCollateralOrBase(ctx: CometContext): Promise scenario( 'Comet#bulker > (non-WETH base) all non-reward actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), - supplyCaps: { - $asset0: 5000, - $asset1: 5000, - }, - tokenBalances: { - albert: { $base: '== 0', $asset0: 5000, $asset1: 5000 }, - $comet: { $base: 5000 }, - }, + filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }]), + supplyCaps: async (ctx) => ( + { + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1, + } + ), + tokenBalances: async (ctx) => ( + { + albert: { + $base: '== 0', + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1 + }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, bulker }, context) => { const { albert, betty } = actors; @@ -44,9 +53,9 @@ scenario( const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; const collateralAsset = context.getAssetByAddress(collateralAssetAddress); const collateralScale = scaleBN.toBigInt(); - const toSupplyCollateral = 5000n * collateralScale; - const toBorrowBase = 1000n * baseScale; - const toTransferBase = 500n * baseScale; + const toSupplyCollateral = BigInt(asset0 === wrappedNativeToken ? getConfigForScenario(context).bulkerAsset1 : getConfigForScenario(context).bulkerAsset) * collateralScale; + const toBorrowBase = BigInt(getConfigForScenario(context).bulkerBorrowBase) * baseScale; + const toTransferBase = BigInt(getConfigForScenario(context).bulkerBorrowAsset) * baseScale; const toSupplyEth = exp(0.01, 18); const toWithdrawEth = exp(0.005, 18); @@ -107,15 +116,23 @@ scenario( scenario( 'Comet#bulker > (wstETH base) all non-reward actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), - supplyCaps: { - $asset0: 5000, - $asset1: 5000, - }, - tokenBalances: { - albert: { $base: '== 0', $asset0: 5000, $asset1: 5000 }, - $comet: { $base: 5000 }, - }, + filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }]), + supplyCaps: async (ctx) => ( + { + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1, + } + ), + tokenBalances: async (ctx) => ( + { + albert: { + $base: '== 0', + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1 + }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, bulker }, context) => { const { albert, betty } = actors; @@ -129,9 +146,9 @@ scenario( const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; const collateralAsset = context.getAssetByAddress(collateralAssetAddress); const collateralScale = scaleBN.toBigInt(); - const toSupplyCollateral = 5000n * collateralScale; - const toBorrowBase = 1000n * baseScale; - const toTransferBase = 500n * baseScale; + const toSupplyCollateral = BigInt(asset0 === wrappedNativeToken ? getConfigForScenario(context).bulkerAsset1 : getConfigForScenario(context).bulkerAsset) * collateralScale; + const toBorrowBase = BigInt(getConfigForScenario(context).bulkerBorrowBase) * baseScale; + const toTransferBase = BigInt(getConfigForScenario(context).bulkerBorrowAsset) * baseScale; const toSupplyEth = exp(0.01, 18); const toWithdrawEth = exp(0.005, 18); @@ -193,13 +210,20 @@ scenario( 'Comet#bulker > (WETH base) all non-reward actions in one txn', { filter: async (ctx) => await isBulkerSupported(ctx) && matchesDeployment(ctx, [{ deployment: 'weth' }]), - supplyCaps: { - $asset0: 3000, - }, - tokenBalances: { - albert: { $base: '== 0', $asset0: 3000 }, - $comet: { $base: 5000 }, - }, + supplyCaps: async (ctx) => ( + { + $asset0: getConfigForScenario(ctx).bulkerAsset, + } + ), + tokenBalances: async (ctx) => ( + { + albert: { + $base: '== 0', + $asset0: getConfigForScenario(ctx).bulkerAsset + }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, bulker }, context) => { const { albert, betty } = actors; @@ -209,9 +233,9 @@ scenario( const { asset: collateralAssetAddress, scale: scaleBN } = await comet.getAssetInfo(0); const collateralAsset = context.getAssetByAddress(collateralAssetAddress); const collateralScale = scaleBN.toBigInt(); - const toSupplyCollateral = 3000n * collateralScale; - const toBorrowBase = 1500n * baseScale; - const toTransferBase = 500n * baseScale; + const toSupplyCollateral = BigInt(getConfigForScenario(context).bulkerAsset) * collateralScale; + const toBorrowBase = BigInt(getConfigForScenario(context).bulkerBorrowBase) * baseScale; + const toTransferBase = BigInt(getConfigForScenario(context).bulkerBorrowAsset) * baseScale; const toSupplyEth = exp(0.01, 18); const toWithdrawEth = exp(0.005, 18); @@ -268,15 +292,23 @@ scenario( scenario( 'Comet#bulker > (non-WETH base) all actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'linea-goerli' }]), - supplyCaps: { - $asset0: 5000, - $asset1: 5000, - }, - tokenBalances: { - albert: { $base: '== 1000000', $asset0: 5000, $asset1: 5000 }, - $comet: { $base: 5000 }, - } + filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }]), + supplyCaps: async (ctx) => ( + { + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1, + } + ), + tokenBalances: async (ctx) => ( + { + albert: { + $base: `== ${getConfigForScenario(ctx).bulkerBase}`, + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1 + }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, rewards, bulker }, context, world) => { const { albert, betty } = actors; @@ -291,10 +323,10 @@ scenario( const collateralAsset = context.getAssetByAddress(collateralAssetAddress); const collateralScale = scaleBN.toBigInt(); const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); - const toSupplyBase = 1_000_000n * baseScale; - const toSupplyCollateral = 5000n * collateralScale; - const toBorrowBase = 1000n * baseScale; - const toTransferBase = 500n * baseScale; + const toSupplyBase = BigInt(getConfigForScenario(context).bulkerBase) * baseScale; + const toSupplyCollateral = BigInt(asset0 === wrappedNativeToken ? getConfigForScenario(context).bulkerAsset1 : getConfigForScenario(context).bulkerAsset) * collateralScale; + const toBorrowBase = BigInt(getConfigForScenario(context).bulkerBorrowBase) * baseScale; + const toTransferBase = BigInt(getConfigForScenario(context).bulkerBorrowAsset) * baseScale; const toSupplyEth = exp(0.01, 18); const toWithdrawEth = exp(0.005, 18); @@ -372,15 +404,23 @@ scenario( scenario( 'Comet#bulker > (wstETH base) all actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'linea-goerli' }]), - supplyCaps: { - $asset0: 5000, - $asset1: 5000, - }, - tokenBalances: { - albert: { $base: '== 1000000', $asset0: 5000, $asset1: 5000 }, - $comet: { $base: 5000 }, - } + filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }]), + supplyCaps: async (ctx) => ( + { + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1, + } + ), + tokenBalances: async (ctx) => ( + { + albert: { + $base: `== ${getConfigForScenario(ctx).bulkerBase}`, + $asset0: getConfigForScenario(ctx).bulkerAsset, + $asset1: getConfigForScenario(ctx).bulkerAsset1 + }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, rewards, bulker }, context, world) => { const { albert, betty } = actors; @@ -395,10 +435,10 @@ scenario( const collateralAsset = context.getAssetByAddress(collateralAssetAddress); const collateralScale = scaleBN.toBigInt(); const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); - const toSupplyBase = 1_000_000n * baseScale; - const toSupplyCollateral = 5000n * collateralScale; - const toBorrowBase = 1000n * baseScale; - const toTransferBase = 500n * baseScale; + const toSupplyBase = BigInt(getConfigForScenario(context).bulkerBase) * baseScale; + const toSupplyCollateral = BigInt(asset0 === wrappedNativeToken ? getConfigForScenario(context).bulkerAsset1 : getConfigForScenario(context).bulkerAsset) * collateralScale; + const toBorrowBase = BigInt(getConfigForScenario(context).bulkerBorrowBase) * baseScale; + const toTransferBase = BigInt(getConfigForScenario(context).bulkerBorrowAsset) * baseScale; const toSupplyEth = exp(0.01, 18); const toWithdrawEth = exp(0.005, 18); @@ -479,10 +519,12 @@ scenario( supplyCaps: { $asset0: 10, }, - tokenBalances: { - albert: { $base: '== 10', $asset0: 10 }, - $comet: { $base: 5000 }, - }, + tokenBalances: async (ctx) => ( + { + albert: { $base: '== 10', $asset0: 10 }, + $comet: { $base: getConfigForScenario(ctx).bulkerComet }, + } + ), }, async ({ comet, actors, rewards, bulker }, context, world) => { const { albert, betty } = actors; diff --git a/scenario/CrossChainGovernanceScenario.ts b/scenario/CrossChainGovernanceScenario.ts index cc25ab765..848b9663a 100644 --- a/scenario/CrossChainGovernanceScenario.ts +++ b/scenario/CrossChainGovernanceScenario.ts @@ -47,7 +47,7 @@ scenario( scenario( 'upgrade Polygon governance contracts and ensure they work properly', { - filter: async ctx => matchesDeployment(ctx, [{network: 'polygon'}, {network: 'mumbai'}]) + filter: async ctx => matchesDeployment(ctx, [{network: 'polygon'}]) }, async ({ comet, configurator, proxyAdmin, timelock: oldLocalTimelock, bridgeReceiver: oldBridgeReceiver }, context, world) => { const dm = world.deploymentManager; @@ -155,7 +155,7 @@ scenario( scenario( 'upgrade Arbitrum governance contracts and ensure they work properly', { - filter: async ctx => matchesDeployment(ctx, [{network: 'arbitrum'}, {network: 'arbitrum-goerli'}]) + filter: async ctx => matchesDeployment(ctx, [{network: 'arbitrum'}]) }, async ({ comet, configurator, proxyAdmin, timelock: oldLocalTimelock, bridgeReceiver: oldBridgeReceiver }, context, world) => { const dm = world.deploymentManager; @@ -262,7 +262,7 @@ scenario( } ); -scenario( +scenario.skip( 'upgrade Linea governance contracts and ensure they work properly', { filter: async ctx => matchesDeployment(ctx, [{ network: 'linea-goerli' }]) @@ -386,7 +386,7 @@ scenario( scenario( 'upgrade Scroll governance contracts and ensure they work properly', { - filter: async ctx => matchesDeployment(ctx, [{ network: 'scroll-goerli' }, {network: 'scroll'}]) + filter: async ctx => matchesDeployment(ctx, [{network: 'scroll'}]) }, async ( { diff --git a/scenario/InterestRateScenario.ts b/scenario/InterestRateScenario.ts index a5161ad33..72256e908 100644 --- a/scenario/InterestRateScenario.ts +++ b/scenario/InterestRateScenario.ts @@ -3,7 +3,6 @@ import { expect } from 'chai'; import { annualize, defactor, exp } from '../test/helpers'; import { BigNumber } from 'ethers'; import { FuzzType } from './constraints/Fuzzing'; -import { matchesDeployment } from './utils'; function calculateInterestRate( utilization: BigNumber, @@ -92,9 +91,6 @@ scenario( borrowPerYearInterestRateSlopeHigh: exp(0.3, 18), }, utilization: 0.5, - // XXX this scenario fails for Goerli cUSDCv3 because someone supplied 100bn USDC and it's no longer - // possible to increase the utilization up to the target amount - filter: async (ctx) => !matchesDeployment(ctx, [{network: 'goerli', deployment: 'usdc'}]) }, async ({ comet }) => { const utilization = await comet.getUtilization(); @@ -118,9 +114,6 @@ scenario( borrowPerYearInterestRateSlopeHigh: exp(0.3, 18), }, utilization: 0.85, - // XXX this scenario fails for Goerli cUSDCv3 because someone supplied 100bn USDC and it's no longer - // possible to increase the utilization up to the target amount - filter: async (ctx) => !matchesDeployment(ctx, [{network: 'goerli', deployment: 'usdc'}]) }, async ({ comet }) => { const utilization = await comet.getUtilization(); diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index ad6ab7069..e6fdae1a0 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -777,7 +777,7 @@ scenario( const assetAmounts = { mainnet: { usdc: ' == 5000', // COMP - weth: ' == 7000', // CB_ETH + weth: ' == 3400', // CB_ETH usdt: ' == 5000', // COMP usds: ' == 850', // WETH }, diff --git a/scenario/SupplyScenario.ts b/scenario/SupplyScenario.ts index a1b012390..e12d04a31 100644 --- a/scenario/SupplyScenario.ts +++ b/scenario/SupplyScenario.ts @@ -509,6 +509,7 @@ scenario( symbol === 'WETH' ? /Transaction reverted without a reason string/ : /.^/, symbol === 'wstETH' ? /0xc2139725/ : /.^/, symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/, + symbol === 'WPOL' ? /Transaction reverted without a reason string/ : /.^/, ] ); } @@ -595,6 +596,7 @@ scenario( symbol === 'WETH' ? /Transaction reverted without a reason string/ : /.^/, symbol === 'wstETH' ? /0x00b284f2/ : /.^/, symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/, + symbol === 'WPOL' ? /Transaction reverted without a reason string/ : /.^/, ] ); } diff --git a/scenario/TransferScenario.ts b/scenario/TransferScenario.ts index 796446453..636eae4cb 100644 --- a/scenario/TransferScenario.ts +++ b/scenario/TransferScenario.ts @@ -134,11 +134,13 @@ scenario( scenario( 'Comet#transfer > partial withdraw / borrow base to partial repay / supply', { - cometBalances: { - albert: { $base: 1000, $asset0: 5000 }, // in units of asset, not wei - betty: { $base: -1000 }, - charles: { $base: 1000 }, // to give the protocol enough base for others to borrow from - }, + cometBalances: async (ctx) => ( + { + albert: { $base: getConfigForScenario(ctx).transferBase, $asset0: getConfigForScenario(ctx).transferAsset1 }, // in units of asset, not wei + betty: { $base: -getConfigForScenario(ctx).transferBase }, + charles: { $base: getConfigForScenario(ctx).transferBase }, // to give the protocol enough base for others to borrow from + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; @@ -149,16 +151,25 @@ scenario( const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 100 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, 100n) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, 100n) + 2n); + expectApproximately( + await albert.getCometBaseBalance(), + BigInt(getConfigForScenario(context).transferBase) * scale, + getInterest(BigInt(getConfigForScenario(context).transferBase) * scale, borrowRate, 100n) + 2n + ); + + expectApproximately( + await betty.getCometBaseBalance(), + -BigInt(getConfigForScenario(context).transferBase) * scale, + getInterest(BigInt(getConfigForScenario(context).transferBase) * scale, borrowRate, 100n) + 2n + ); // Albert with positive balance transfers to Betty with negative balance - const toTransfer = 2500n * scale; + const toTransfer = BigInt(getConfigForScenario(context).transferBase) * 25n / 10n * scale; const txn = await albert.transferAsset({ dst: betty.address, asset: baseAsset.address, amount: toTransfer }); // Albert ends with negative balance and Betty with positive balance - expectApproximately(await albert.getCometBaseBalance(), -1500n * scale, getInterest(1500n * scale, borrowRate, 100n) + 4n); - expectApproximately(await betty.getCometBaseBalance(), 1500n * scale, getInterest(1500n * scale, borrowRate, 100n) + 4n); + expectApproximately(await albert.getCometBaseBalance(), -BigInt(getConfigForScenario(context).transferBase) * 15n / 10n * scale, getInterest(BigInt(getConfigForScenario(context).transferBase) * 15n / 10n * scale, borrowRate, 100n) + 4n); + expectApproximately(await betty.getCometBaseBalance(), BigInt(getConfigForScenario(context).transferBase) * 15n / 10n * scale, getInterest(BigInt(getConfigForScenario(context).transferBase) * 15n / 10n * scale, borrowRate, 100n) + 4n); return txn; // return txn to measure gas } @@ -273,10 +284,15 @@ scenario( 'Comet#transfer collateral reverts if undercollateralized', { // XXX we should probably have a price constraint? - cometBalances: { - albert: { $base: -1000, $asset0: '== 3000' }, // in units of asset, not wei - betty: { $asset0: 0 }, - }, + cometBalances: async (ctx) => ( + { + albert: { + $base: -getConfigForScenario(ctx).transferBase, + $asset0: `== ${getConfigForScenario(ctx).transferAsset}` + }, // in units of asset, not wei + betty: { $asset0: 0 }, + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; @@ -289,7 +305,7 @@ scenario( albert.transferAsset({ dst: betty.address, asset: collateralAsset.address, - amount: 3000n * scale, + amount: BigInt(getConfigForScenario(context).transferAsset) * scale, }), 'NotCollateralized()' ); @@ -300,10 +316,15 @@ scenario( 'Comet#transferFrom collateral reverts if undercollateralized', { // XXX we should probably have a price constraint? - cometBalances: { - albert: { $base: -1000, $asset0: '== 3000' }, // in units of asset, not wei - betty: { $asset0: 0 }, - }, + cometBalances: async (ctx) => ( + { + albert: { + $base: -getConfigForScenario(ctx).transferBase, + $asset0: `== ${getConfigForScenario(ctx).transferAsset}` + }, // in units of asset, not wei + betty: { $asset0: 0 }, + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; @@ -319,7 +340,7 @@ scenario( src: albert.address, dst: betty.address, asset: collateralAsset.address, - amount: 3000n * scale, + amount: BigInt(getConfigForScenario(context).transferAsset) * scale, }), 'NotCollateralized()' ); diff --git a/scenario/WithdrawReservesScenario.ts b/scenario/WithdrawReservesScenario.ts index 5517bb788..6e81fc910 100644 --- a/scenario/WithdrawReservesScenario.ts +++ b/scenario/WithdrawReservesScenario.ts @@ -59,7 +59,7 @@ scenario( await context.setNextBaseFeeToZero(); await expectRevertCustom( - admin.withdrawReserves(albert.address, 101n * scale, { gasPrice: 0 }), + admin.withdrawReserves(albert.address, 1001n * scale, { gasPrice: 0 }), 'InsufficientReserves()' ); } diff --git a/scenario/WithdrawScenario.ts b/scenario/WithdrawScenario.ts index d0bab38b9..8ec1848e2 100644 --- a/scenario/WithdrawScenario.ts +++ b/scenario/WithdrawScenario.ts @@ -2,6 +2,7 @@ import { CometContext, scenario } from './context/CometContext'; import { expect } from 'chai'; import { expectApproximately, expectRevertCustom, hasMinBorrowGreaterThanOne, isTriviallySourceable, isValidAssetIndex, MAX_ASSETS } from './utils'; import { ContractReceipt } from 'ethers'; +import { getConfigForScenario } from './utils/scenarioHelper'; async function testWithdrawCollateral(context: CometContext, assetNum: number): Promise { const comet = await context.getComet(); @@ -104,13 +105,17 @@ scenario( scenario( 'Comet#withdraw > borrow base', { - tokenBalances: { - albert: { $base: '== 0' }, - $comet: { $base: 1000 }, // in units of asset, not wei - }, - cometBalances: { - albert: { $asset0: 3000 } // in units of asset, not wei - }, + tokenBalances: async (ctx) => ( + { + albert: { $base: '== 0' }, + $comet: { $base: getConfigForScenario(ctx).withdrawBase }, // in units of asset, not wei + } + ), + cometBalances: async (ctx) => ( + { + albert: { $asset0: getConfigForScenario(ctx).withdrawAsset } // in units of asset, not wei + } + ), }, async ({ comet, actors }, context) => { const { albert } = actors; @@ -123,10 +128,10 @@ scenario( expect(await comet.balanceOf(albert.address)).to.be.equal(0n); // Albert borrows 1000 unit of base from Comet - const txn = await albert.withdrawAsset({ asset: baseAsset.address, amount: 1000n * scale }); + const txn = await albert.withdrawAsset({ asset: baseAsset.address, amount: BigInt(getConfigForScenario(context).withdrawBase) * scale }); - expect(await baseAsset.balanceOf(albert.address)).to.be.equal(1000n * scale); - expectApproximately(await albert.getCometBaseBalance(), -1000n * scale, precision); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(BigInt(getConfigForScenario(context).withdrawBase) * scale); + expectApproximately(await albert.getCometBaseBalance(), -BigInt(getConfigForScenario(context).withdrawBase) * scale, precision); return txn; // return txn to measure gas } @@ -163,12 +168,17 @@ scenario( scenario( 'Comet#withdrawFrom > borrow base', { - tokenBalances: { - $comet: { $base: 1000 }, // in units of asset, not wei - }, - cometBalances: { - albert: { $asset0: 3000 } // in units of asset, not wei - }, + tokenBalances: async (ctx) => ( + { + albert: { $base: '== 0' }, + $comet: { $base: getConfigForScenario(ctx).withdrawBase }, // in units of asset, not wei + } + ), + cometBalances: async (ctx) => ( + { + albert: { $asset0: getConfigForScenario(ctx).withdrawAsset } // in units of asset, not wei + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; @@ -183,10 +193,10 @@ scenario( await albert.allow(betty, true); // Betty borrows 1000 unit of base using Albert's account - const txn = await betty.withdrawAssetFrom({ src: albert.address, dst: betty.address, asset: baseAsset.address, amount: 1000n * scale }); + const txn = await betty.withdrawAssetFrom({ src: albert.address, dst: betty.address, asset: baseAsset.address, amount: BigInt(getConfigForScenario(context).withdrawBase) * scale }); - expect(await baseAsset.balanceOf(betty.address)).to.be.equal(1000n * scale); - expectApproximately(await albert.getCometBaseBalance(), -1000n * scale, precision); + expect(await baseAsset.balanceOf(betty.address)).to.be.equal(BigInt(getConfigForScenario(context).withdrawBase) * scale); + expectApproximately(await albert.getCometBaseBalance(), -BigInt(getConfigForScenario(context).withdrawBase) * scale, precision); return txn; // return txn to measure gas } diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index cd7dc79c3..2e4d968cf 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,15 +62,9 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 353 - if (proposal.id.eq(353)) { - console.log('Skipping proposal 353'); - continue; - } - - // temporary hack to skip proposal 353 - if (proposal.id.eq(353)) { - console.log('Skipping proposal 353'); + // temporary hack to skip proposal 385 + if (proposal.id.eq(385)) { + console.log('Skipping proposal 385'); continue; } diff --git a/scenario/context/CometContext.ts b/scenario/context/CometContext.ts index 9687cdd1c..e72b5a3dc 100644 --- a/scenario/context/CometContext.ts +++ b/scenario/context/CometContext.ts @@ -74,7 +74,7 @@ export class CometContext { } async getCompWhales(): Promise { - const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism', 'mantle'].includes(this.world.base.network); + const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism', 'scroll', 'mantle'].includes(this.world.base.network); return COMP_WHALES[useMainnetComp ? 'mainnet' : 'testnet']; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index aede8553a..4c3b0d69d 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -19,7 +19,7 @@ import { isBridgeProposal } from './isBridgeProposal'; export { mineBlocks, setNextBaseFeeToZero, setNextBlockTimestamp }; -export const MAX_ASSETS = 15; +export const MAX_ASSETS = 30; export const UINT256_MAX = 2n ** 256n - 1n; export interface ComparativeAmount { @@ -299,11 +299,16 @@ export function matchesDeployment(ctx: CometContext, deploymentCriteria: Deploym export async function isRewardSupported(ctx: CometContext): Promise { const rewards = await ctx.getRewards(); const comet = await ctx.getComet(); + const COMP = await ctx.getComp(); + if (rewards == null) return false; const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); if (rewardTokenAddress === constants.AddressZero) return false; + const totalSupply = await COMP.totalSupply(); + if (totalSupply.toBigInt() < exp(1, 18)) return false; + return true; } @@ -464,7 +469,7 @@ export async function executeOpenProposal( const proposal = await governor.proposals(id); await setNextBlockTimestamp(dm, Math.max(block.timestamp, proposal.eta.toNumber()) + 1); await setNextBaseFeeToZero(dm); - await governor.execute(id, { gasPrice: 0, gasLimit: 12000000 }); + await governor.execute(id, { gasPrice: 0, gasLimit: 24000000 }); } await redeployRenzoOracle(dm); await mockAllRedstoneOracles(dm); @@ -533,8 +538,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal // Create the chain-specific wrapper around the L2 proposal data switch (bridgeNetwork) { - case 'arbitrum': - case 'arbitrum-goerli': { + case 'arbitrum': { const inbox = await govDeploymentManager.getContractOrThrow('arbitrumInbox'); const refundAddress = constants.AddressZero; const createRetryableTicketCalldata = utils.defaultAbiCoder.encode( @@ -558,8 +562,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(createRetryableTicketCalldata); break; } - case 'base': - case 'base-goerli': { + case 'base': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'bytes', 'uint32'], [bridgeReceiver.address, l2ProposalData, 1_000_000] // XXX find a reliable way to estimate the gasLimit @@ -574,7 +577,6 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } - case 'mumbai': case 'polygon': { const sendMessageToChildCalldata = utils.defaultAbiCoder.encode( ['address', 'bytes'], @@ -588,20 +590,20 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageToChildCalldata); break; } - case 'linea-goerli': { - const sendMessageCalldata = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'bytes'], - [bridgeReceiver.address, 0, l2ProposalData] - ); - const lineaMessageService = await govDeploymentManager.getContractOrThrow( - 'lineaMessageService' - ); - targets.push(lineaMessageService.address); - values.push(0); - signatures.push('sendMessage(address,uint256,bytes)'); - calldata.push(sendMessageCalldata); - break; - } + // case 'linea-goerli': { + // const sendMessageCalldata = utils.defaultAbiCoder.encode( + // ['address', 'uint256', 'bytes'], + // [bridgeReceiver.address, 0, l2ProposalData] + // ); + // const lineaMessageService = await govDeploymentManager.getContractOrThrow( + // 'lineaMessageService' + // ); + // targets.push(lineaMessageService.address); + // values.push(0); + // signatures.push('sendMessage(address,uint256,bytes)'); + // calldata.push(sendMessageCalldata); + // break; + // } case 'optimism': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'bytes', 'uint32'], @@ -631,8 +633,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } - case 'scroll': - case 'scroll-goerli': { + case 'scroll': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'uint256', 'bytes', 'uint256'], [bridgeReceiver.address, 0, l2ProposalData, 1_000_000] // XXX find a reliable way to estimate the gasLimit diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 8bad488e8..97d87bcd0 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -8,8 +8,7 @@ export async function isBridgeProposal( ) { const bridgeNetwork = bridgeDeploymentManager.network; switch (bridgeNetwork) { - case 'arbitrum': - case 'arbitrum-goerli': { + case 'arbitrum': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); const inbox = await governanceDeploymentManager.getContractOrThrow('arbitrumInbox'); const l1GatewayRouter = await governanceDeploymentManager.getContractOrThrow( @@ -18,7 +17,6 @@ export async function isBridgeProposal( const { targets } = await governor.getActions(openProposal.id); return targets.includes(inbox.address) || targets.includes(l1GatewayRouter.address); } - case 'mumbai': case 'polygon': { const { governor, @@ -31,8 +29,7 @@ export async function isBridgeProposal( const { targets } = await governor.getActions(openProposal.id); return targets.some(t => bridgeAddresses.includes(t.toLowerCase())); } - case 'base': - case 'base-goerli': { + case 'base': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); const baseL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( 'baseL1CrossDomainMessenger' @@ -44,14 +41,14 @@ export async function isBridgeProposal( const bridgeContracts = [baseL1CrossDomainMessenger.address, baseL1StandardBridge.address]; return targets.some(t => bridgeContracts.includes(t)); } - case 'linea-goerli': { - const governor = await governanceDeploymentManager.getContractOrThrow('governor'); - const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( - 'lineaMessageService' - ); - const { targets } = await governor.getActions(openProposal.id); - return targets.includes(lineaMessageService.address); - } + // case 'linea': { + // const governor = await governanceDeploymentManager.getContractOrThrow('governor'); + // const lineaMessageService = await governanceDeploymentManager.getContractOrThrow( + // 'lineaMessageService' + // ); + // const { targets } = await governor.getActions(openProposal.id); + // return targets.includes(lineaMessageService.address); + // } case 'optimism': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( @@ -79,8 +76,7 @@ export async function isBridgeProposal( ]; return targets.some(t => bridgeContracts.includes(t)); } - case 'scroll': - case 'scroll-goerli': { + case 'scroll': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); const scrollMessenger = await governanceDeploymentManager.getContractOrThrow( 'scrollMessenger' diff --git a/scenario/utils/relayArbitrumMessage.ts b/scenario/utils/relayArbitrumMessage.ts index 442d45a27..ee434bb4b 100644 --- a/scenario/utils/relayArbitrumMessage.ts +++ b/scenario/utils/relayArbitrumMessage.ts @@ -147,10 +147,7 @@ export async function relayCCTPMint( // L1 contracts const L1MessageTransmitter = await governanceDeploymentManager.getContractOrThrow('CCTPMessageTransmitter'); // Arbitrum TokenMinter which is L2 contracts - const TokenMinter = - bridgeDeploymentManager.network === 'arbitrum' ? - await bridgeDeploymentManager.existing('TokenMinter', '0xE7Ed1fa7f45D05C508232aa32649D89b73b8bA48', 'arbitrum') : - await bridgeDeploymentManager.existing('TokenMinter', '0xE997d7d2F6E065a9A93Fa2175E878Fb9081F1f0A', 'arbitrum-goerli'); + const TokenMinter = await bridgeDeploymentManager.existing('TokenMinter', '0xE7Ed1fa7f45D05C508232aa32649D89b73b8bA48', 'arbitrum'); const depositForBurnEvents: Log[] = await governanceDeploymentManager.hre.ethers.provider.getLogs({ @@ -232,10 +229,7 @@ export async function relayCCTPMint( }); // Impersonate the Arbitrum TokenMinter and mint token to recipient - const ImpersonateLocalTokenMessenger = - bridgeDeploymentManager.network === 'arbitrum' ? '0x19330d10d9cc8751218eaf51e8885d058642e08a' : - bridgeDeploymentManager.network === 'arbitrum-goerli' ? '0x12dcfd3fe2e9eac2859fd1ed86d2ab8c5a2f9352' : - '0x0'; + const ImpersonateLocalTokenMessenger = bridgeDeploymentManager.network === 'arbitrum' ? '0x19330d10d9cc8751218eaf51e8885d058642e08a' : '0x0'; // Impersonate the Arbitrum TokenMinter and mint token to recipient for (let burnEvent of burnEvents) { const { recipient, amount, sourceDomain, burnToken } = burnEvent; diff --git a/scenario/utils/relayBaseMessage.ts b/scenario/utils/relayBaseMessage.ts index 397faa7d2..3625deb78 100644 --- a/scenario/utils/relayBaseMessage.ts +++ b/scenario/utils/relayBaseMessage.ts @@ -11,7 +11,7 @@ The Base relayer applies an offset to the message sender. applyL1ToL2Alias mimics the AddressAliasHelper.applyL1ToL2Alias fn that converts an L1 address to its offset, L2 equivalent. -https://goerli.basescan.org/address/0x4200000000000000000000000000000000000007#code +https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000007#code */ function applyL1ToL2Alias(address: string) { const offset = BigInt('0x1111000000000000000000000000000000001111'); diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index eb7db32e0..1329a0da3 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -15,7 +15,6 @@ export default async function relayMessage( const bridgeNetwork = bridgeDeploymentManager.network; switch (bridgeNetwork) { case 'base': - case 'base-goerli': await relayBaseMessage( governanceDeploymentManager, bridgeDeploymentManager, @@ -36,7 +35,6 @@ export default async function relayMessage( startingBlockNumber ); break; - case 'mumbai': case 'polygon': await relayPolygonMessage( governanceDeploymentManager, @@ -45,7 +43,6 @@ export default async function relayMessage( ); break; case 'arbitrum': - case 'arbitrum-goerli': await relayArbitrumMessage( governanceDeploymentManager, bridgeDeploymentManager, @@ -57,7 +54,7 @@ export default async function relayMessage( startingBlockNumber ); break; - case 'linea-goerli': + case 'linea': await relayLineaMessage( governanceDeploymentManager, bridgeDeploymentManager, @@ -65,7 +62,6 @@ export default async function relayMessage( ); break; case 'scroll': - case 'scroll-goerli': await relayScrollMessage( governanceDeploymentManager, bridgeDeploymentManager, diff --git a/scenario/utils/relayOptimismMessage.ts b/scenario/utils/relayOptimismMessage.ts index bd3c81d74..9261f09ca 100644 --- a/scenario/utils/relayOptimismMessage.ts +++ b/scenario/utils/relayOptimismMessage.ts @@ -61,7 +61,7 @@ export default async function relayOptimismMessage( const messageWithoutSigHash = '0x' + messageWithoutPrefix.slice(8); try { // 1a. Bridging ERC20 token - const { l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ l1Token, _l2Token, _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address l1Token', 'address l2Token', 'address from', 'address to', 'uint256 amount', 'bytes data'], messageWithoutSigHash ); @@ -71,7 +71,7 @@ export default async function relayOptimismMessage( ); } catch (e) { // 1a. Bridging ETH - const { _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address from', 'address to', 'uint256 amount', 'bytes data'], messageWithoutSigHash ); @@ -90,11 +90,18 @@ export default async function relayOptimismMessage( } } else if (target === bridgeReceiver.address) { // Cross-chain message passing - const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address === bridgeReceiver.address); - const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); - - // Add the proposal to the list of open bridged proposals to be executed after all the messages have been relayed - openBridgedProposals.push({ id, eta }); + try { + const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address === bridgeReceiver.address); + const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); + // Add the proposal to the list of open bridged proposals to be executed after all the messages have been relayed + openBridgedProposals.push({ id, eta }); + } catch (e) { + if(relayMessageTxn.events[0].event === 'FailedRelayedMessage'){ + console.log('Failed to relay message'); + continue; + } + throw e; + } } else { throw new Error(`[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Unrecognized target for cross-chain message`); } diff --git a/scenario/utils/relayScrollMessage.ts b/scenario/utils/relayScrollMessage.ts index e6feb53d9..4b7672778 100644 --- a/scenario/utils/relayScrollMessage.ts +++ b/scenario/utils/relayScrollMessage.ts @@ -81,21 +81,21 @@ export default async function relayScrollMessage( // 2. Cross-chain message passing if (target === l2ERC20Gateway.address) { // 1a. Bridging ERC20 token - const { l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ l1Token, _l2Token, _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address _l1Token', 'address _l2Token','address _from', 'address _to','uint256 _amount', 'bytes _data'], messageWithoutSigHash ); - + console.log( `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ${l1Token} to user ${to}` ); } else if (target === l2ETHGateway.address){ // 1a. Bridging ETH - const { _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address _from', 'address _to', 'uint256 _amount', 'bytes _data'], messageWithoutSigHash ); - + const oldBalance = await bridgeDeploymentManager.hre.ethers.provider.getBalance(to); const newBalance = oldBalance.add(BigNumber.from(amount)); // This is our best attempt to mimic the deposit transaction type (not supported in Hardhat) that Optimism uses to deposit ETH to an L2 address @@ -103,48 +103,48 @@ export default async function relayScrollMessage( to, ethers.utils.hexStripZeros(newBalance.toHexString()), ]); - + console.log( `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ETH to user ${to}` ); }else if (target === l2WETHGateway.address){ // 1c. Bridging WETH - const { _l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ _l1Token, _l2Token, _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address _l1Token', 'address _l2Token','address _from', 'address _to','uint256 _amount', 'bytes _data'], messageWithoutSigHash ); - + console.log( `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of WETH to user ${to}` ); } else if (target === l2WstETHGateway.address){ // 1d. Bridging WstETH - const { _l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + const [ _l1Token, _l2Token, _from, to, amount, _data ] = ethers.utils.defaultAbiCoder.decode( ['address _l1Token', 'address _l2Token','address _from', 'address _to','uint256 _amount', 'bytes _data'], messageWithoutSigHash ); - + console.log( `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of WstETH to user ${to}` ); } else if (target === bridgeReceiver.address) { // Cross-chain message passing - const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address === bridgeReceiver.address); + const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address.toLowerCase() === bridgeReceiver.address.toLowerCase()); const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); - + // Add the proposal to the list of open bridged proposals to be executed after all the messages have been relayed openBridgedProposals.push({ id, eta }); } else { throw new Error(`[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Unrecognized target for cross-chain message`); } } - + // Execute open bridged proposals now that all messages have been bridged for (let proposal of openBridgedProposals) { const { eta, id } = proposal; // Fast forward l2 time await setNextBlockTimestamp(bridgeDeploymentManager, eta.toNumber() + 1); - + // Execute queued proposal await setNextBaseFeeToZero(bridgeDeploymentManager); await bridgeReceiver.executeProposal(id, { gasPrice: 0 }); diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 80134f6e7..30b430b34 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -3,6 +3,7 @@ import { CometContext } from '../context/CometContext'; const config = { bulkerBase: 1000000, bulkerAsset: 5000, + bulkerAsset1: 5000, bulkerComet: 5000, bulkerBorrowBase: 1000, bulkerBorrowAsset: 500, @@ -15,13 +16,17 @@ const config = { rewardsBase: 1000, transferBase: 1000, transferAsset: 5000, + transferAsset1: 5000, interestSeconds: 110, + withdrawBase: 1000, + withdrawAsset: 3000, }; export function getConfigForScenario(ctx: CometContext) { if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'wbtc') { config.bulkerBase = 5000; config.bulkerAsset = 200; + config.bulkerAsset1 = 200; config.bulkerComet = 200; config.bulkerBorrowBase = 100; config.bulkerBorrowAsset = 50; @@ -55,5 +60,32 @@ export function getConfigForScenario(ctx: CometContext) { config.interestSeconds = 110; } + if (ctx.world.base.network === 'arbitrum' && ctx.world.base.deployment === 'usdc.e') { + config.liquidationDenominator = 84; + config.liquidationBase = 100000; + config.liquidationBase1 = 50000; + config.liquidationAsset = 10000; + } + + if (ctx.world.base.network === 'polygon' && ctx.world.base.deployment === 'usdc') { + config.bulkerAsset = 200; + config.bulkerAsset1 = 200; + } + + if (ctx.world.base.network === 'polygon' && ctx.world.base.deployment === 'usdt') { + config.withdrawAsset = 10000; + config.transferAsset = 500000; + config.transferBase = 100; + } + + if (ctx.world.base.network === 'scroll' && ctx.world.base.deployment === 'usdc') { + config.bulkerAsset = 500; + config.bulkerAsset1 = 500; + } + + if (ctx.world.base.network === 'sepolia' && ctx.world.base.deployment === 'usdc') { + config.bulkerAsset1 = 10; + } + return config; } \ No newline at end of file diff --git a/scripts/CCTP-attestation.ts b/scripts/CCTP-attestation.ts index dd82b0f5b..6eca4aaa1 100644 --- a/scripts/CCTP-attestation.ts +++ b/scripts/CCTP-attestation.ts @@ -1,7 +1,7 @@ /* A script to help check if CCTP's attestation server to acquire signature to mint native USDC on arbitrum Example: - DEPLOYMENT=usdc BURN_TXN_HASH= SOURCE_NETWORK=goerli DEST_NETWORK=arbitrum-goerli ETH_PK= npx hardhat run scripts/CCTP-attestation.ts + DEPLOYMENT=usdc BURN_TXN_HASH= SOURCE_NETWORK=sepolia DEST_NETWORK=arbitrum-sepolia ETH_PK= npx hardhat run scripts/CCTP-attestation.ts */ import hre from 'hardhat'; import { DeploymentManager } from '../plugins/deployment_manager/DeploymentManager'; diff --git a/scripts/liquidation_bot/index.ts b/scripts/liquidation_bot/index.ts index 16380c25d..6092e6c72 100644 --- a/scripts/liquidation_bot/index.ts +++ b/scripts/liquidation_bot/index.ts @@ -66,12 +66,12 @@ async function main() { hre.ethers.provider, // a normal ethers.js provider, to perform gas estimations and nonce lookups authSigner, // ethers.js signer wallet, only for signing request payloads, not transactions ); - } else if (network === 'goerli') { + } else if (network === 'sepolia') { flashbotsProvider = await FlashbotsBundleProvider.create( hre.ethers.provider, // a normal ethers.js provider, to perform gas estimations and nonce lookups authSigner, // ethers.js signer wallet, only for signing request payloads, not transactions - 'https://relay-goerli.flashbots.net', - 'goerli' + 'https://relay-sepolia.flashbots.net', + 'sepolia' ); } else { throw new Error(`Unsupported network: ${network}`); diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index 29a70303e..1c42437c0 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -42,9 +42,6 @@ const addresses = { RS_ETH: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, - goerli: { - WETH: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d' - }, polygon: { WBTC: '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', WETH: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', @@ -70,9 +67,6 @@ const liquidationThresholds = { 'usdc': 10e6, 'weth': 1e18 }, - goerli: { - 'usdc': 10e6 - }, polygon: { 'usdc': 10e6 }, @@ -105,12 +99,6 @@ export const flashLoanPools = { poolFee: 3000, } }, - goerli: { - usdc: { - tokenAddress: addresses.goerli.WETH, - poolFee: 3000 - } - }, polygon: { usdc: { tokenAddress: addresses.polygon.BOB, diff --git a/src/deploy/index.ts b/src/deploy/index.ts index fde711912..febb0ab9b 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -89,15 +89,17 @@ export const WHALES = { '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', - '0x426c4966fC76Bf782A663203c023578B744e4C5E', // wUSDM whale + '0x3B95bC951EE0f553ba487327278cAc44f29715E5', // wUSDM whale '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale '0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale - '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' + '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b', + '0x0B925eD163218f6662a35e0f0371Ac234f9E9371', // wstETH whale ], polygon: [ '0x2093b4281990a568c9d588b8bce3bfd7a1557ebd', // WETH whale '0xd814b26554204245a30f8a42c289af582421bf04', // WBTC whale - '0x167384319b41f7094e62f7506409eb38079abff8' // WMATIC whale + '0x167384319b41f7094e62f7506409eb38079abff8', // WMATIC whale + '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045', // USDC.e whale ], arbitrum: [ '0x8eb270e296023e9d92081fdf967ddd7878724424', // rETH whale @@ -115,7 +117,8 @@ export const WHALES = { '0xee3273f6d29ddfff08ffd9d513cff314734f01a2', // COMP whale '0x9e786a8fc88ee74b758b125071d45853356024c3', // COMP whale '0xd93f76944e870900779c09ddf1c46275f9d8bf9b', // COMP whale - '0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9' // native USDC whale + '0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9', // native USDC whale + '0x56CC5A9c0788e674f17F7555dC8D3e2F1C0313C0', // wUSDM whale ], base: [ '0x6D3c5a4a7aC4B1428368310E4EC3bB1350d01455', // USDbC whale @@ -126,20 +129,8 @@ export const WHALES = { '0xb125E6687d4313864e53df431d5425969c15Eb2F', // cbETH whale ], scroll: [ - '0xaaaaAAAACB71BF2C8CaE522EA5fa455571A74106' // USDC whale - ], - 'arbitrum-goerli': [ - '0x4984cbfa5b199e5920995883d345bbe14b005db7', // USDC whale - '0xbbfe34e868343e6f4f5e8b5308de980d7bd88c46', // LINK whale - '0x8DA65F8E3Aa22A498211fc4204C498ae9050DAE4', // COMP whale - '0x6ed0c4addc308bb800096b8daa41de5ae219cd36' // native USDC whale - ], - 'base-goerli': [ - '0x21856935e5689490c72865f34CC665D0FF25664b' // USDC whale - ], - 'linea-goerli': [ - '0xC858966280Da3Fa0348E51D2c3B892EcC889fC98', // USDC whale - '0x44411c605eb7e009cad03f3847cfbbfcf8895130' // COMP whale + '0xaaaaAAAACB71BF2C8CaE522EA5fa455571A74106', // USDC whale + '0x5B1322eeb46240b02e20062b8F0F9908d525B09c', // wstETH whale ], optimism: [ '0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26', // OP whale @@ -152,6 +143,7 @@ export const WHALES = { '0x651C9D1F9da787688225f49d63ad1623ba89A8D5', // FBTC whale '0xC455fE28a76da80022d4C35A37eB08FF405Eb78f', // FBTC whale '0x524db930F0886CdE7B5FFFc920Aae85e98C2abfb', // FBTC whale + '0xCd83CbBFCE149d141A5171C3D6a0F0fCCeE225Ab', // COMP whale ], }; diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index 5e08b7e37..5715a19a2 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -7,12 +7,12 @@ import { impersonateAddress } from '../../plugins/scenario/utils'; import hreForBase from '../../plugins/scenario/utils/hreForBase'; // TODO: Don't depend on scenario's hreForBase -function getForkEnv(env: HardhatRuntimeEnvironment, deployment: string): HardhatRuntimeEnvironment { +async function getForkEnv(env: HardhatRuntimeEnvironment, deployment: string): Promise { const base = env.config.scenario.bases.find(b => b.network == env.network.name && b.deployment == deployment); if (!base) { throw new Error(`No fork spec for ${env.network.name}`); } - return hreForBase(base); + return await hreForBase(base); } function getDefaultDeployment(config: HardhatConfig, network: string): string { @@ -65,7 +65,7 @@ task('deploy', 'Deploys market') .addFlag('overwrite', 'overwrites cache') .addParam('deployment', 'The deployment to deploy') .setAction(async ({ simulate, noDeploy, noVerify, noVerifyImpl, overwrite, deployment }, env) => { - const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env; + const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env; const network = env.network.name; const tag = `${network}/${deployment}`; const dm = new DeploymentManager( @@ -174,7 +174,7 @@ task('migrate', 'Runs migration') .addFlag('overwrite', 'overwrites artifact if exists, fails otherwise') .setAction( async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate }, env) => { - const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env; + const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env; const network = env.network.name; const dm = new DeploymentManager( network, @@ -193,7 +193,7 @@ task('migrate', 'Runs migration') const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined; if (governanceBase) { - const governanceEnv = hreForBase(governanceBase, simulate); + const governanceEnv = await hreForBase(governanceBase, simulate); governanceDm = new DeploymentManager( governanceBase.network, governanceBase.deployment, @@ -215,6 +215,12 @@ task('migrate', 'Runs migration') governanceDm._signers.unshift(signer); } + if(simulate) { + console.log('Simulating migration without verification'); + dm.setVerificationStrategy('lazy'); + governanceDm.setVerificationStrategy('lazy'); + } + const migrationPath = `${__dirname}/../../deployments/${network}/${deployment}/migrations/${migrationName}.ts`; const [migration] = await loadMigrations([migrationPath]); if (!migration) { @@ -246,7 +252,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') .addParam('deployment', 'The deployment to deploy') .setAction( async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate, noDeploy, noVerify, noVerifyImpl }, env) => { - const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env; + const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env; const network = env.network.name; const tag = `${network}/${deployment}`; const dm = new DeploymentManager( @@ -314,7 +320,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined; if (governanceBase) { - const governanceEnv = hreForBase(governanceBase, simulate); + const governanceEnv = await hreForBase(governanceBase, simulate); governanceDm = new DeploymentManager( governanceBase.network, governanceBase.deployment, diff --git a/tasks/scenario/task.ts b/tasks/scenario/task.ts index 658623144..36ddc1c1b 100644 --- a/tasks/scenario/task.ts +++ b/tasks/scenario/task.ts @@ -39,7 +39,7 @@ task('scenario:spider', 'Runs spider in preparation for scenarios') const bases: ForkSpec[] = getBasesFromTaskArgs(taskArgs.bases, env); await Promise.all(bases.map(async (base) => { if (base.network !== 'hardhat') { - let hre = hreForBase(base); + let hre = await hreForBase(base); let dm = new DeploymentManager( base.name, base.deployment, diff --git a/test/absorb-test.ts b/test/absorb-test.ts index 88ae85c5a..8b0fe708b 100644 --- a/test/absorb-test.ts +++ b/test/absorb-test.ts @@ -532,4 +532,88 @@ describe('absorb', function () { expect(await comet.getAssetList(underwater.address)).to.be.empty; }); + + it('updates assetsIn for liquidated account in 24 assets', async () => { + const protocol = await makeProtocol({ + assets: { + // 24 assets + COMP: { + initial: 1e7, + decimals: 18, + initialPrice: 175, + }, + WETH: { + initial: 1e4, + decimals: 18, + initialPrice: 3000, + }, + WBTC: { + initial: 1e3, + decimals: 8, + initialPrice: 41000, + }, + ASSET3: {}, + ASSET4: {}, + ASSET5: {}, + ASSET6: {}, + ASSET7: {}, + ASSET8: {}, + ASSET9: {}, + ASSET10: {}, + ASSET11: {}, + ASSET12: {}, + ASSET13: {}, + ASSET14: {}, + ASSET15: {}, + ASSET16: {}, + ASSET17: {}, + ASSET18: {}, + ASSET19: {}, + ASSET20: {}, + ASSET21: {}, + ASSET22: {}, + ASSET23: {}, + USDC: { + initial: 1e6, + decimals: 6, + }, + }, + reward: 'COMP', + }); + const { cometExtendedAssetList : comet, tokens: { + COMP, + WETH, + }, users: [absorber, underwater] } = protocol; + + await bumpTotalsCollateral(comet, COMP, exp(1, 18)); + await bumpTotalsCollateral(comet, WETH, exp(1, 18)); + + await comet.setCollateralBalance(underwater.address, COMP.address, exp(1, 18)); + await comet.setCollateralBalance(underwater.address, WETH.address, exp(1, 18)); + + + for (let i = 3; i < 24; i++) { + const asset = `ASSET${i}`; + await bumpTotalsCollateral(comet, protocol.tokens[asset], exp(1, 18)); + await comet.setCollateralBalance(underwater.address, protocol.tokens[asset].address, exp(1, 18)); + } + + expect(await comet.getAssetList(underwater.address)).to.deep.equal([ + COMP.address, + WETH.address, + ...Array.from({ length: 21 }, (_, i) => protocol.tokens[`ASSET${i + 3}`].address), + ]); + + const borrowAmount = exp(4000, 6); // borrow of $4k > collateral of $3k + $175 + await comet.setBasePrincipal(underwater.address, -borrowAmount); + await setTotalsBasic(comet, { totalBorrowBase: borrowAmount }); + + const isLiquidatable = await comet.isLiquidatable(underwater.address); + + expect(isLiquidatable).to.be.true; + + await comet.absorb(absorber.address, [underwater.address]); + + expect(await comet.getAssetList(underwater.address)).to.be.empty; + }); }); \ No newline at end of file diff --git a/test/asset-info-test-asset-list-comet.ts b/test/asset-info-test-asset-list-comet.ts new file mode 100644 index 000000000..14f511b31 --- /dev/null +++ b/test/asset-info-test-asset-list-comet.ts @@ -0,0 +1,101 @@ +import { expect, exp, makeConfigurator, ONE, makeProtocol } from './helpers'; + +describe('asset info', function () { + it('initializes protocol', async () => { + const { cometExtendedAssetList: comet, tokens } = await makeConfigurator({ + assets: { + USDC: {}, + ASSET1: {}, + ASSET2: {}, + ASSET3: {}, + }, + reward: 'ASSET1', + }); + + const cometNumAssets = await comet.numAssets(); + expect(cometNumAssets).to.be.equal(3); + + const assetInfo00 = await comet.getAssetInfo(0); + expect(assetInfo00.asset).to.be.equal(tokens['ASSET1'].address); + expect(assetInfo00.borrowCollateralFactor).to.equal(ONE - exp(1, 14)); + expect(assetInfo00.liquidateCollateralFactor).to.equal(ONE); + + const assetInfo01 = await comet.getAssetInfo(1); + expect(assetInfo01.asset).to.be.equal(tokens['ASSET2'].address); + expect(assetInfo01.borrowCollateralFactor).to.equal(ONE - exp(1, 14)); + expect(assetInfo01.liquidateCollateralFactor).to.equal(ONE); + + const assetInfo02 = await comet.getAssetInfo(2); + expect(assetInfo02.asset).to.be.equal(tokens['ASSET3'].address); + expect(assetInfo02.borrowCollateralFactor).to.equal(ONE - exp(1, 14)); + expect(assetInfo02.liquidateCollateralFactor).to.equal(ONE); + }); + + it('reverts if too many assets are passed', async () => { + await expect( + makeProtocol({ + assets: { + USDC: {}, + ASSET1: {}, + ASSET2: {}, + ASSET3: {}, + ASSET4: {}, + ASSET5: {}, + ASSET6: {}, + ASSET7: {}, + ASSET8: {}, + ASSET9: {}, + ASSET10: {}, + ASSET11: {}, + ASSET12: {}, + ASSET13: {}, + ASSET14: {}, + ASSET15: {}, + ASSET16: {}, + ASSET17: {}, + ASSET18: {}, + ASSET19: {}, + ASSET20: {}, + ASSET21: {}, + ASSET22: {}, + ASSET23: {}, + ASSET24: {}, + ASSET25: {}, + }, + reward: 'ASSET1', + }) + ).to.be.revertedWith("custom error 'TooManyAssets()'"); + }); + + it('reverts if index is greater than numAssets', async () => { + const { cometExtendedAssetList } = await makeConfigurator(); + await expect(cometExtendedAssetList.getAssetInfo(3)).to.be.revertedWith("custom error 'BadAsset()'"); + }); + + it('reverts if collateral factors are out of range', async () => { + await expect(makeConfigurator({ + assets: { + USDC: {}, + ASSET1: {borrowCF: exp(0.9, 18), liquidateCF: exp(0.9, 18)}, + ASSET2: {}, + }, + })).to.be.revertedWith("custom error 'BorrowCFTooLarge()'"); + + // check descaled factors + await expect(makeConfigurator({ + assets: { + USDC: {}, + ASSET1: {borrowCF: exp(0.9, 18), liquidateCF: exp(0.9, 18) + 1n}, + ASSET2: {}, + }, + })).to.be.revertedWith("custom error 'BorrowCFTooLarge()'"); + + await expect(makeConfigurator({ + assets: { + USDC: {}, + ASSET1: {borrowCF: exp(0.99, 18), liquidateCF: exp(1.1, 18)}, + ASSET2: {}, + }, + })).to.be.revertedWith("custom error 'LiquidateCFTooLarge()'"); + }); +}); diff --git a/test/asset-info-test.ts b/test/asset-info-test.ts index 686da6ac1..228ab3667 100644 --- a/test/asset-info-test.ts +++ b/test/asset-info-test.ts @@ -1,4 +1,10 @@ import { expect, exp, makeProtocol, ONE } from './helpers'; +import { ethers } from 'hardhat'; +import { + SimplePriceFeed__factory, + FaucetToken__factory, + CometHarness__factory +} from '../build/types'; describe('asset info', function () { it('initializes protocol', async () => { @@ -34,29 +40,86 @@ describe('asset info', function () { }); it('reverts if too many assets are passed', async () => { + let priceFeeds = {}; + const assets = { + USDC: {}, + ASSET1: {}, + ASSET2: {}, + ASSET3: {}, + ASSET4: {}, + ASSET5: {}, + ASSET6: {}, + ASSET7: {}, + ASSET8: {}, + ASSET9: {}, + ASSET10: {}, + ASSET11: {}, + ASSET12: {}, + ASSET13: {}, + ASSET14: {}, + ASSET15: {}, + ASSET16: {}, + }; + const base = 'USDC'; + const PriceFeedFactory = (await ethers.getContractFactory('SimplePriceFeed')) as SimplePriceFeed__factory; + for (const asset in assets) { + const initialPrice = exp(assets[asset].initialPrice || 1, 8); + const priceFeedDecimals = assets[asset].priceFeedDecimals || 8; + const priceFeed = await PriceFeedFactory.deploy(initialPrice, priceFeedDecimals); + await priceFeed.deployed(); + priceFeeds[asset] = priceFeed; + } + const FaucetFactory = (await ethers.getContractFactory('FaucetToken')) as FaucetToken__factory; + const tokens = {}; + for (const symbol in assets) { + const config = assets[symbol]; + const decimals = config.decimals || 18; + const initial = config.initial || 1e6; + const name = config.name || symbol; + const factory = config.factory || FaucetFactory; + let token; + token = (tokens[symbol] = await factory.deploy(initial, name, decimals, symbol)); + await token.deployed(); + } + const config = { + governor: ethers.constants.AddressZero, + pauseGuardian: ethers.constants.AddressZero, + extensionDelegate: ethers.constants.AddressZero, + baseToken: tokens[base].address, + baseTokenPriceFeed: priceFeeds[base].address, + supplyKink: 0, + supplyPerYearInterestRateBase: 0, + supplyPerYearInterestRateSlopeLow: 0, + supplyPerYearInterestRateSlopeHigh: 0, + borrowKink: 0, + borrowPerYearInterestRateBase: 0, + borrowPerYearInterestRateSlopeLow: 0, + borrowPerYearInterestRateSlopeHigh: 0, + storeFrontPriceFactor: 8, + trackingIndexScale: 0, + baseTrackingSupplySpeed: 0, + baseTrackingBorrowSpeed: 0, + baseMinForRewards: 0, + baseBorrowMin: 0, + targetReserves: 0, + assetConfigs: Object.entries(assets).reduce((acc, [symbol], _i) => { + if (symbol != base) { + acc.push({ + asset: tokens[symbol].address, + priceFeed: priceFeeds[symbol].address, + decimals: 18, + borrowCollateralFactor: ONE - 1n, + liquidateCollateralFactor: ONE, + liquidationFactor: ONE, + supplyCap: exp(100, 18), + }); + } + return acc; + }, []), + }; + const CometFactory = (await ethers.getContractFactory('CometHarness')) as CometHarness__factory; await expect( - makeProtocol({ - assets: { - USDC: {}, - ASSET1: {}, - ASSET2: {}, - ASSET3: {}, - ASSET4: {}, - ASSET5: {}, - ASSET6: {}, - ASSET7: {}, - ASSET8: {}, - ASSET9: {}, - ASSET10: {}, - ASSET11: {}, - ASSET12: {}, - ASSET13: {}, - ASSET14: {}, - ASSET15: {}, - ASSET16: {}, - }, - reward: 'ASSET1', - }) + CometFactory.deploy(config) ).to.be.revertedWith("custom error 'TooManyAssets()'"); }); diff --git a/test/bulker-test.ts b/test/bulker-test.ts index 3af444693..ca5c380de 100644 --- a/test/bulker-test.ts +++ b/test/bulker-test.ts @@ -46,6 +46,82 @@ describe('bulker', function () { expect(await comet.collateralBalanceOf(alice.address, COMP.address)).to.be.equal(supplyAmount); }); + it('supply 24 collateral assets', async () => { + const protocol = await makeProtocol({ + assets: { + // 24 assets + USDC: {}, + COMP: {}, + WETH: {}, + ASSET3: {}, + ASSET4: {}, + ASSET5: {}, + ASSET6: {}, + ASSET7: {}, + ASSET8: {}, + ASSET9: {}, + ASSET10: {}, + ASSET11: {}, + ASSET12: {}, + ASSET13: {}, + ASSET14: {}, + ASSET15: {}, + ASSET16: {}, + ASSET17: {}, + ASSET18: {}, + ASSET19: {}, + ASSET20: {}, + ASSET21: {}, + ASSET22: {}, + ASSET23: {}, + }, + reward: 'COMP', + }); + const { cometExtendedAssetList : comet, tokens: { + COMP, + WETH, + USDC, + }, users: [alice] } = protocol; + const bulkerInfo = await makeBulker({ weth: WETH.address }); + const { bulker } = bulkerInfo; + + // Alice approves 10 COMP to Comet + const supplyAmount = exp(10, 18); + await COMP.allocateTo(alice.address, supplyAmount); + await COMP.connect(alice).approve(comet.address, ethers.constants.MaxUint256); + + await USDC.connect(alice).approve(comet.address, ethers.constants.MaxUint256); + + for (let i = 3; i < 24; i++) { + const asset = `ASSET${i}`; + await protocol.tokens[asset].allocateTo(alice.address, supplyAmount); + await protocol.tokens[asset].connect(alice).approve(comet.address, ethers.constants.MaxUint256); + } + + // Alice gives the Bulker permission over her account + await comet.connect(alice).allow(bulker.address, true); + + + // Alice supplies 10 COMP through the bulker + const supplyCOMPCalldata = ethers.utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, alice.address, COMP.address, supplyAmount]); + const actions = [await bulker.ACTION_SUPPLY_ASSET()]; + const calldatas = [supplyCOMPCalldata]; + for(let i = 3; i < 24; i++) { + actions.push(await bulker.ACTION_SUPPLY_ASSET()); + const asset = `ASSET${i}`; + const supplyAssetCalldata = ethers.utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, alice.address, protocol.tokens[asset].address, supplyAmount]); + calldatas.push(supplyAssetCalldata); + } + + await bulker.connect(alice).invoke(actions, calldatas); + + expect(await comet.collateralBalanceOf(alice.address, COMP.address)).to.be.equal(supplyAmount); + for (let i = 3; i < 24; i++) { + const asset = `ASSET${i}`; + expect(await comet.collateralBalanceOf(alice.address, protocol.tokens[asset].address)).to.be.equal(supplyAmount); + } + }); + it('supply collateral asset to a different account', async () => { const protocol = await makeProtocol({}); const { comet, tokens: { COMP, WETH }, users: [alice, bob] } = protocol; @@ -285,6 +361,87 @@ describe('bulker', function () { expect(await COMP.balanceOf(alice.address)).to.be.equal(withdrawAmount); }); + it('withdraw 24 collateral assets', async () => { + const protocol = await makeProtocol({ + assets: { + // 24 assets + USDC: {}, + COMP: {}, + WETH: {}, + ASSET3: {}, + ASSET4: {}, + ASSET5: {}, + ASSET6: {}, + ASSET7: {}, + ASSET8: {}, + ASSET9: {}, + ASSET10: {}, + ASSET11: {}, + ASSET12: {}, + ASSET13: {}, + ASSET14: {}, + ASSET15: {}, + ASSET16: {}, + ASSET17: {}, + ASSET18: {}, + ASSET19: {}, + ASSET20: {}, + ASSET21: {}, + ASSET22: {}, + ASSET23: {}, + }, + reward: 'COMP', + }); + const { cometExtendedAssetList : comet, tokens: { + COMP, + WETH, + }, users: [alice] } = protocol; + const bulkerInfo = await makeBulker({ weth: WETH.address }); + const { bulker } = bulkerInfo; + + // Allocate collateral asset to Comet and Alice's Comet balance + const withdrawAmount = exp(10, 18); + await COMP.allocateTo(comet.address, withdrawAmount); + const t0 = Object.assign({}, await comet.totalsCollateral(COMP.address), { + totalSupplyAsset: withdrawAmount, + }); + await wait(comet.setTotalsCollateral(COMP.address, t0)); + await comet.setCollateralBalance(alice.address, COMP.address, withdrawAmount); + + for(let i = 3; i < 24; i++) { + const asset = `ASSET${i}`; + await protocol.tokens[asset].allocateTo(comet.address, withdrawAmount); + const t1 = Object.assign({}, await comet.totalsCollateral(protocol.tokens[asset].address), { + totalSupplyAsset: withdrawAmount, + }); + await wait(comet.setTotalsCollateral(protocol.tokens[asset].address, t1)); + await comet.setCollateralBalance(alice.address, protocol.tokens[asset].address, withdrawAmount); + } + + // Alice gives the Bulker permission over her account + await comet.connect(alice).allow(bulker.address, true); + + // Alice withdraws 10 COMP through the bulker + const withdrawAssetCalldata = ethers.utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, alice.address, COMP.address, withdrawAmount]); + const actions = [await bulker.ACTION_WITHDRAW_ASSET()]; + const calldatas = [withdrawAssetCalldata]; + for(let i = 3; i < 24; i++) { + actions.push(await bulker.ACTION_WITHDRAW_ASSET()); + const asset = `ASSET${i}`; + const withdrawAssetCalldata = ethers.utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, alice.address, protocol.tokens[asset].address, withdrawAmount]); + calldatas.push(withdrawAssetCalldata); + } + await bulker.connect(alice).invoke(actions, calldatas); + + expect(await comet.collateralBalanceOf(alice.address, COMP.address)).to.be.equal(0); + expect(await COMP.balanceOf(alice.address)).to.be.equal(withdrawAmount); + for (let i = 3; i < 24; i++) { + const asset = `ASSET${i}`; + expect(await comet.collateralBalanceOf(alice.address, protocol.tokens[asset].address)).to.be.equal(0); + expect(await protocol.tokens[asset].balanceOf(alice.address)).to.be.equal(withdrawAmount); + } + }); + it('withdraw collateral asset to a different account', async () => { const protocol = await makeProtocol({}); const { comet, tokens: { COMP, WETH }, users: [alice, bob] } = protocol; diff --git a/test/helpers.ts b/test/helpers.ts index 58acf19af..1be6a7b06 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -8,6 +8,7 @@ import { BaseBulker__factory, CometExt, CometExt__factory, + CometExtAssetList__factory, CometHarness__factory, CometHarnessInterface as Comet, CometRewards, @@ -32,6 +33,10 @@ import { CometInterface, NonStandardFaucetFeeToken, NonStandardFaucetFeeToken__factory, + AssetListFactory, + AssetListFactory__factory, + CometHarnessExtendedAssetList__factory, + CometHarnessInterfaceExtendedAssetList as CometExtendedAssetList, } from '../build/types'; import { BigNumber } from 'ethers'; import { TransactionReceipt, TransactionResponse } from '@ethersproject/abstract-provider'; @@ -98,6 +103,8 @@ export type Protocol = { base: string; reward: string; comet: Comet; + cometExtendedAssetList: CometExtendedAssetList; + assetListFactory: AssetListFactory; tokens: { [symbol: string]: FaucetToken | NonStandardFaucetFeeToken; }; @@ -270,6 +277,10 @@ export async function makeProtocol(opts: ProtocolOpts = {}): Promise { const unsupportedToken = await FaucetFactory.deploy(1e6, 'Unsupported Token', 6, 'USUP'); + const AssetListFactory = (await ethers.getContractFactory('AssetListFactory')) as AssetListFactory__factory; + const assetListFactory = await AssetListFactory.deploy(); + await assetListFactory.deployed(); + let extensionDelegate = opts.extensionDelegate; if (extensionDelegate === undefined) { const CometExtFactory = (await ethers.getContractFactory('CometExt')) as CometExt__factory; @@ -278,7 +289,7 @@ export async function makeProtocol(opts: ProtocolOpts = {}): Promise { } const CometFactory = (await ethers.getContractFactory('CometHarness')) as CometHarness__factory; - const comet = await CometFactory.deploy({ + const config = { governor: governor.address, pauseGuardian: pauseGuardian.address, extensionDelegate: extensionDelegate.address, @@ -300,7 +311,7 @@ export async function makeProtocol(opts: ProtocolOpts = {}): Promise { baseBorrowMin, targetReserves, assetConfigs: Object.entries(assets).reduce((acc, [symbol, config], _i) => { - if (symbol != base) { + if (symbol != base && _i <= 12) { acc.push({ asset: tokens[symbol].address, priceFeed: priceFeeds[symbol].address, @@ -313,12 +324,41 @@ export async function makeProtocol(opts: ProtocolOpts = {}): Promise { } return acc; }, []), - }); + }; + const comet = await CometFactory.deploy(config); await comet.deployed(); + config.assetConfigs = Object.entries(assets).reduce((acc, [symbol, config], _i) => { + if (symbol != base) { + acc.push({ + asset: tokens[symbol].address, + priceFeed: priceFeeds[symbol].address, + decimals: dfn(assets[symbol].decimals, 18), + borrowCollateralFactor: dfn(config.borrowCF, ONE - 1n), + liquidateCollateralFactor: dfn(config.liquidateCF, ONE), + liquidationFactor: dfn(config.liquidationFactor, ONE), + supplyCap: dfn(config.supplyCap, exp(100, dfn(config.decimals, 18))), + }); + } + return acc; + }, []); + let extensionDelegateAssetList = opts.extensionDelegate; + if (extensionDelegateAssetList === undefined) { + const CometExtFactory = (await ethers.getContractFactory('CometExtAssetList')) as CometExtAssetList__factory; + extensionDelegateAssetList = await CometExtFactory.deploy({ name32, symbol32 }, assetListFactory.address); + await extensionDelegateAssetList.deployed(); + } + config.extensionDelegate = extensionDelegateAssetList.address; + const CometFactoryExtendedAssetList = (await ethers.getContractFactory('CometHarnessExtendedAssetList')) as CometHarnessExtendedAssetList__factory; + + const cometExtendedAssetList = await CometFactoryExtendedAssetList.deploy(config); + await cometExtendedAssetList.deployed(); + if (opts.start) await ethers.provider.send('evm_setNextBlockTimestamp', [opts.start]); await comet.initializeStorage(); + await cometExtendedAssetList.initializeStorage(); + const baseTokenBalance = opts.baseTokenBalance; if (baseTokenBalance) { const baseToken = tokens[base]; @@ -334,6 +374,8 @@ export async function makeProtocol(opts: ProtocolOpts = {}): Promise { base, reward, comet: await ethers.getContractAt('CometHarnessInterface', comet.address) as Comet, + cometExtendedAssetList: await ethers.getContractAt('CometHarnessInterfaceExtendedAssetList', cometExtendedAssetList.address) as CometExtendedAssetList, + assetListFactory: assetListFactory, tokens, unsupportedToken, priceFeeds, @@ -352,6 +394,8 @@ export async function makeConfigurator(opts: ProtocolOpts = {}): Promise