diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index 6889852d6..c56d6d059 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -17,6 +17,7 @@ on: - base - base-goerli - linea-goerli + - optimism - scroll-goerli - scroll deployment: @@ -40,13 +41,14 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_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('{\"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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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] }}" port: 8585 if: github.event.inputs.eth_pk == '' @@ -88,4 +90,4 @@ jobs: git config user.email "<>" git add deployments/\*/roots.json git commit -m "Modified deployment roots from GitHub Actions" - git push origin + git push origin \ No newline at end of file diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 2ecc8c8ec..f0466c5a0 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -17,6 +17,7 @@ on: - base - base-goerli - linea-goerli + - optimism - scroll-goerli - scroll deployment: @@ -48,6 +49,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Get governance network run: | @@ -65,7 +67,7 @@ jobs: with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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] }}" port: 8585 if: github.event.inputs.eth_pk == '' @@ -74,7 +76,7 @@ jobs: with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ env.GOV_NETWORK }}" - ethereum_url: "${{ fromJSON('{\"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('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_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] }}" port: 8685 if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' @@ -120,4 +122,4 @@ jobs: git config user.email "<>" git add deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/migrations/${{ github.event.inputs.migration }}.ts git commit -m "Modified migration from GitHub Actions" || echo "No changes to commit" - git push origin + git push origin \ No newline at end of file diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index 3701da08d..fb0d38c20 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -16,6 +16,7 @@ on: - arbitrum-goerli - base - base-goerli + - optimism deployment: description: Deployment Name (e.g. "usdc") required: true @@ -40,13 +41,14 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_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('{\"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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_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://clean-spring-wind.base-mainnet.discover.quiknode.pro/$QUICKNODE_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] }}" port: 8585 if: github.event.inputs.eth_pk == '' @@ -79,4 +81,4 @@ jobs: if: success() || failure() # run this step even if previous step failed with: name: ${{ github.event.inputs.network }}-${{ github.event.inputs.deployment }}-${{ github.event.inputs.migration }} - path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/${{ github.event.inputs.migration }}.json + path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/${{ github.event.inputs.migration }}.json \ No newline at end of file diff --git a/.github/workflows/run-contract-linter.yaml b/.github/workflows/run-contract-linter.yaml index 04bc045d6..b027b58fb 100644 --- a/.github/workflows/run-contract-linter.yaml +++ b/.github/workflows/run-contract-linter.yaml @@ -13,6 +13,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - uses: actions/checkout@v2 with: @@ -26,4 +27,4 @@ jobs: run: yarn install --non-interactive --frozen-lockfile && yarn build - name: Run linter on modified contracts - run: git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep ^contracts/ | xargs yarn solhint \ No newline at end of file + run: git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep ^contracts/ | xargs yarn solhint diff --git a/.github/workflows/run-coverage.yaml b/.github/workflows/run-coverage.yaml index 559dd12cc..e2272a9d5 100644 --- a/.github/workflows/run-coverage.yaml +++ b/.github/workflows/run-coverage.yaml @@ -15,6 +15,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v2 diff --git a/.github/workflows/run-eslint.yaml b/.github/workflows/run-eslint.yaml index ade6bf2f4..9763d45d4 100644 --- a/.github/workflows/run-eslint.yaml +++ b/.github/workflows/run-eslint.yaml @@ -13,6 +13,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v2 diff --git a/.github/workflows/run-forge-tests.yaml b/.github/workflows/run-forge-tests.yaml index 5d343299c..069a30bdb 100644 --- a/.github/workflows/run-forge-tests.yaml +++ b/.github/workflows/run-forge-tests.yaml @@ -29,6 +29,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} - name: Build Comet with older solc versions run: | diff --git a/.github/workflows/run-gas-profiler.yaml b/.github/workflows/run-gas-profiler.yaml index 668376d9b..d2418ad42 100644 --- a/.github/workflows/run-gas-profiler.yaml +++ b/.github/workflows/run-gas-profiler.yaml @@ -14,6 +14,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v2 diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 8acd648f5..7c551a883 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, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} @@ -18,6 +18,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} runs-on: ubuntu-latest steps: - name: Checkout repository @@ -63,4 +64,4 @@ jobs: with: name: Scenario Tests (${{ matrix.bases }}) # Name of the check run which will be created path: scenario-results.json # Path to test results (inside artifact .zip) - reporter: mocha-json # Format of test results + reporter: mocha-json # Format of test results \ No newline at end of file diff --git a/.github/workflows/run-semgrep.yaml b/.github/workflows/run-semgrep.yaml index 0f752f1f2..dad4eb51a 100644 --- a/.github/workflows/run-semgrep.yaml +++ b/.github/workflows/run-semgrep.yaml @@ -4,20 +4,21 @@ name: Run Semgrep on: # Scan changed files in PRs (diff-aware scanning): pull_request: {} - # On-demand + # On-demand workflow_dispatch: {} jobs: semgrep: # User-definable name of this GitHub Actions job: name: Scan - # If you are self-hosting, change the following `runs-on` value: + # If you are self-hosting, change the following `runs-on` value: runs-on: ubuntu-latest env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} INFURA_KEY: ${{ secrets.INFURA_KEY }} POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} container: # A Docker image with Semgrep installed. Do not change this. image: returntocorp/semgrep @@ -43,7 +44,7 @@ jobs: # Run security, performance & Compound specific rules - run: semgrep ci --sarif --output=semgrep.sarif || true env: - SEMGREP_RULES: rules/solidity/security rules/solidity/performance compound/solidity + SEMGREP_RULES: rules/solidity/security rules/solidity/performance compound/solidity # Upload findings to GitHub Advanced Security Dashboard - name: Upload findings to GitHub Advanced Security Dashboard uses: github/codeql-action/upload-sarif@v2 diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index 11ebffc85..d198e156e 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -13,6 +13,7 @@ jobs: POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} + OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v2 diff --git a/README.md b/README.md index fd284b2ed..64f799814 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ native asset (i.e. 2 ETH for Goerli, 2 AVAX for Fuji) The `clone-multisig` script can be used to clone the multisig and its configuration from an existing deployment, e.g.: ```bash -DST_NETWORK=optimism-goerli npx hardhat run scripts/clone-multisig.ts +DST_NETWORK=optimism npx hardhat run scripts/clone-multisig.ts ``` ### Liquidation Bot diff --git a/deployments/goerli/usdc/relations.ts b/deployments/goerli/usdc/relations.ts index d9277c42c..6652288ff 100644 --- a/deployments/goerli/usdc/relations.ts +++ b/deployments/goerli/usdc/relations.ts @@ -40,7 +40,20 @@ export default { field: { slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' } - } + }, + }, + opL1CrossDomainMessenger: { + delegates: { + field: async () => '0xDa2332D0a7608919Cd331B1304Cd179129a90495', + }, + }, + opL1StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, }, lineaMessageService: { artifact: 'contracts/bridges/linea/IMessageService.sol:IMessageService', diff --git a/deployments/goerli/usdc/roots.json b/deployments/goerli/usdc/roots.json index 02a9e9b69..3add9436d 100644 --- a/deployments/goerli/usdc/roots.json +++ b/deployments/goerli/usdc/roots.json @@ -12,9 +12,11 @@ "baseL1StandardBridge": "0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a", "lineaMessageService": "0x70BaD09280FD342D02fe64119779BC1f0791BAC2", "lineaL1TokenBridge": "0xaA012D038E6440535Ec66eDf2DA592F4F8398133", - "lineaL1usdcBridge": "0x9c556D2cCfb6157E4A6305aa9963EdD6ca5047cB", + "lineaL1usdcBridge": "0x9c556D2cCfb6157E4A6305aa9963EdD6ca5047cB", "CCTPTokenMessenger": "0xd0c3da58f55358142b8d3e06c1c30c5c6114efe8", "CCTPMessageTransmitter": "0x26413e8157cd32011e726065a5462e97dd4d03d9", + "opL1CrossDomainMessenger": "0x5086d1eEF304eb5284A0f6720f79403b4e9bE294", + "opL1StandardBridge": "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8", "scrollMessenger": "0x5260e38080BFe97e6C4925d9209eCc5f964373b6", "scrollL1TokenBridge": "0xe5E30E7c24e4dFcb281A682562E53154C15D3332" -} \ No newline at end of file +} diff --git a/deployments/mainnet/usdc/relations.ts b/deployments/mainnet/usdc/relations.ts index a15c9370d..4aa0946b3 100644 --- a/deployments/mainnet/usdc/relations.ts +++ b/deployments/mainnet/usdc/relations.ts @@ -42,6 +42,19 @@ export default { } } }, + opL1CrossDomainMessenger: { + delegates: { + field: async () => '0x2150Bc3c64cbfDDbaC9815EF615D6AB8671bfe43' + } + }, + opL1StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, scrollMessenger: { delegates: { field: { @@ -56,4 +69,4 @@ export default { } } } -}; \ No newline at end of file +}; diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index 996d96beb..637e34be5 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -6,11 +6,13 @@ "bulker": "0xa397a8C2086C554B531c02E29f3291c9704B00c7", "fxRoot": "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2", "arbitrumInbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", - "arbitrumL1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", - "CCTPTokenMessenger": "0xbd3fa81b58ba92a82136038b25adec7066af3155", + "arbitrumL1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", + "CCTPTokenMessenger": "0xbd3fa81b58ba92a82136038b25adec7066af3155", "CCTPMessageTransmitter": "0x0a992d191deec32afe36203ad87d7d289a738f81", "baseL1CrossDomainMessenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa", "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", + "opL1CrossDomainMessenger": "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", + "opL1StandardBridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", "scrollMessenger": "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367", "scrollL1USDCGateway": "0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B" -} \ No newline at end of file +} diff --git a/deployments/optimism/usdc/configuration.json b/deployments/optimism/usdc/configuration.json new file mode 100644 index 000000000..244eb0c0a --- /dev/null +++ b/deployments/optimism/usdc/configuration.json @@ -0,0 +1,56 @@ +{ + "name": "Compound USDC", + "symbol": "cUSDCv3", + "baseToken": "USDC", + "baseTokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + "baseTokenPriceFeed": "0x16a9FA2FDa030272Ce99B29CF780dFA30361E0f3", + "pauseGuardian": "0x3fFd6c073a4ba24a113B18C8F373569640916A45", + "borrowMin": "1e5", + "storeFrontPriceFactor": 0.6, + "targetReserves": "20000000e6", + "rates": { + "supplyKink": 0.9, + "supplySlopeLow": 0.059, + "supplySlopeHigh": 2.9, + "supplyBase": 0, + "borrowKink": 0.9, + "borrowSlopeLow": 0.061, + "borrowSlopeHigh": 3.2, + "borrowBase": 0.015 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "0e15", + "baseBorrowSpeed": "0e15", + "baseMinForRewards": "1000e6" + }, + "assets": { + "OP": { + "address": "0x4200000000000000000000000000000000000042", + "priceFeed": "0x0D276FC14719f9292D5C1eA2198673d1f4269246", + "decimals": "18", + "borrowCF": 0.65, + "liquidateCF": 0.7, + "liquidationFactor": 0.8, + "supplyCap": "0e18" + }, + "WETH": { + "address": "0x4200000000000000000000000000000000000006", + "priceFeed": "0x13e3Ee699D1909E989722E753853AE30b17e08c5", + "decimals": "18", + "borrowCF": 0.83, + "liquidateCF": 0.9, + "liquidationFactor": 0.95, + "supplyCap": "0e18" + }, + "WBTC": { + "address": "0x68f180fcCe6836688e9084f035309E29Bf0A2095", + "priceFeed": "0x718A5788b89454aAE3A028AE9c111A29Be6c2a6F", + "decimals": "8", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "0e8" + } + } +} \ No newline at end of file diff --git a/deployments/optimism/usdc/deploy.ts b/deployments/optimism/usdc/deploy.ts new file mode 100644 index 000000000..a78ded852 --- /dev/null +++ b/deployments/optimism/usdc/deploy.ts @@ -0,0 +1,120 @@ +import { + Deployed, + DeploymentManager, +} from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +const HOUR = 60 * 60; +const DAY = 24 * HOUR; + +const MAINNET_TIMELOCK = '0x6d903f6003cca6255d85cca4d3b5e5146dc33925'; + +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(); + + // Pull in existing assets + const USDC = await deploymentManager.existing( + 'USDC', + '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + 'optimism' + ); + const WETH = await deploymentManager.existing( + 'WETH', + '0x4200000000000000000000000000000000000006', + 'optimism' + ); + const WBTC = await deploymentManager.existing( + 'WBTC', + '0x68f180fcCe6836688e9084f035309E29Bf0A2095', + 'optimism' + ); + const OP = await deploymentManager.existing( + 'OP', + '0x4200000000000000000000000000000000000042', + 'optimism' + ); + + const l2CrossDomainMessenger = await deploymentManager.existing( + 'l2CrossDomainMessenger', + [ + '0xC0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007', + '0x4200000000000000000000000000000000000007', + ], + 'optimism' + ); + + const l2StandardBridge = await deploymentManager.existing( + 'l2StandardBridge', + [ + '0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010', + '0x4200000000000000000000000000000000000010', + ], + 'optimism' + ); + + // 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 + 1 * DAY, // delay + 14 * DAY, // grace period + 12 * HOUR, // minimum delay + 30 * DAY, // maxiumum delay + ] + ); + + // Initialize OptimismBridgeReceiver + await deploymentManager.idempotent( + async () => !(await bridgeReceiver.initialized()), + async () => { + trace(`Initializing BridgeReceiver`); + await bridgeReceiver.initialize( + MAINNET_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, + l2CrossDomainMessenger, // TODO: don't have to part of roots. can be pulled via relations + l2StandardBridge, + bulker, + }; +} diff --git a/deployments/optimism/usdc/migrations/1707394874_configurate_and_ens.ts b/deployments/optimism/usdc/migrations/1707394874_configurate_and_ens.ts new file mode 100644 index 000000000..100c81ba3 --- /dev/null +++ b/deployments/optimism/usdc/migrations/1707394874_configurate_and_ens.ts @@ -0,0 +1,290 @@ +import { DeploymentManager } from "../../../../plugins/deployment_manager/DeploymentManager"; +import { migration } from "../../../../plugins/deployment_manager/Migration"; +import { + diffState, + getCometConfig, +} from "../../../../plugins/deployment_manager/DiffState"; +import { + calldata, + exp, + getConfigurationStruct, + proposal, +} from "../../../../src/deploy"; +import { expect } from "chai"; + +const SECONDS_PER_YEAR = 31_536_000n; +const ENSName = "compound-community-licenses.eth"; +const ENSResolverAddress = "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"; +const ENSRegistryAddress = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; +const ENSSubdomainLabel = "v3-additional-grants"; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = "v3-official-markets"; +const opCOMPAddress = "0x7e7d4467112689329f7E06571eD0E8CbAd4910eE"; + +export default migration("1707394874_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 { + opL1CrossDomainMessenger, + opL1StandardBridge, + governor, + comptrollerV2, + COMP: mainnetCOMP, + USDC: mainnetUSDC, + CCTPTokenMessenger, + } = await govDeploymentManager.getContracts(); + + // CCTP destination domain for Optimism + const OptimismDestinationDomain = 2; + + // ENS Setup + // See also: https://docs.ens.domains/contract-api-reference/name-processing + const ENSResolver = await govDeploymentManager.existing( + "ENSResolver", + ENSResolverAddress + ); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const baseChainId = ( + 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[baseChainId]) { + officialMarketsJSON[baseChainId].push(newMarketObject); + } else { + officialMarketsJSON[baseChainId] = [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, opCOMPAddress] + ); + 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(3_600, 18); + const USDCAmountToBridge = exp(10_000, 6); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Optimism. + { + contract: opL1CrossDomainMessenger, + signature: "sendMessage(address,bytes,uint32)", + args: [bridgeReceiver.address, l2ProposalData, 2_500_000], + }, + // 2. Approve USDC to CCTP + { + contract: mainnetUSDC, + signature: "approve(address,uint256)", + args: [CCTPTokenMessenger.address, USDCAmountToBridge], + }, + // 3. Burn USDC to Optimism via CCTP + { + contract: CCTPTokenMessenger, + signature: "depositForBurn(uint256,uint32,bytes32,address)", + args: [ + USDCAmountToBridge, + OptimismDestinationDomain, + utils.hexZeroPad(comet.address, 32), + mainnetUSDC.address, + ], + }, + // 4. Approve Ethereum's L1StandardBridge to take Timelock's COMP (for bridging) + { + contract: mainnetCOMP, + signature: "approve(address,uint256)", + args: [opL1StandardBridge.address, COMPAmountToBridge], + }, + // 5. Bridge COMP from Ethereum to OP Rewards using L1StandardBridge + { + contract: opL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + "depositERC20To(address,address,address,uint256,uint32,bytes)", + args: [ + mainnetCOMP.address, + opCOMPAddress, + rewards.address, + COMPAmountToBridge, + 200_000, + "0x", + ], + }, + // 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 = "# Initialize cUSDCv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes deployment of Compound III to Optimism network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDC market on Optimism; upon execution, cUSDCv3 will be ready for use. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975/6).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/838) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration and deploys a new Comet implementation on Optimism. This sends the encoded `setConfiguration` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism. It also calls `setRewardConfig` on the Optimism rewards contract, to establish Optimism’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 5 COMP/day and borrow speed to be 5 COMP/day.\n\nThe second action approves Circle’s Cross-Chain Transfer Protocol (CCTP) [TokenMessenger](https://etherscan.io/address/0xbd3fa81b58ba92a82136038b25adec7066af3155) to take the Timelock's USDC on Mainnet, in order to seed the market reserves through the CCTP.\n\nThe third action deposits and burns 10K USDC from mainnet via depositForBurn function on CCTP’s TokenMessenger contract to mint native USDC to Comet on Optimism.\n\nThe fourth action approves Optimism’s [L1StandardBridge](https://etherscan.io/address/0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1) to take Timelock's COMP, in order to seed the rewards contract through the bridge.\n\nThe fifth action deposits 3.6K COMP from mainnet to the Optimism L1StandardBridge contract to bridge to CometRewards.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Optimism cUSDCv3 market"; + const txn = await govDeploymentManager.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, + govDeploymentManager: DeploymentManager, + preMigrationBlockNumber: number + ) { + const ethers = deploymentManager.hre.ethers; + await deploymentManager.spider(); + + const { comet, rewards, COMP, USDC } = + await deploymentManager.getContracts(); + + // 1. + const stateChanges = await diffState( + comet, + getCometConfig, + preMigrationBlockNumber + ); + expect(stateChanges).to.deep.equal({ + storeFrontPriceFactor: exp(0.6, 18), + baseTrackingSupplySpeed: exp(5 / 86400, 15, 18), + baseTrackingBorrowSpeed: exp(5 / 86400, 15, 18), + borrowPerSecondInterestRateSlopeLow: exp(0.061, 18) / SECONDS_PER_YEAR, + borrowPerSecondInterestRateSlopeHigh: exp(3.2, 18) / SECONDS_PER_YEAR, + supplyPerSecondInterestRateSlopeLow: exp(0.059, 18) / SECONDS_PER_YEAR, + supplyPerSecondInterestRateSlopeHigh: exp(2.9, 18) / SECONDS_PER_YEAR, + WETH: { + supplyCap: exp(1600, 18), + }, + OP: { + supplyCap: exp(700000, 18), + }, + WBTC: { + supplyCap: exp(60, 8), + }, + wstETH: { + supplyCap: exp(500, 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 USDC.balanceOf(comet.address)).to.be.equal(exp(10_000, 6)); + + // 4. & 5. + expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(3_600, 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({ + 1: [ + { + baseSymbol: "USDC", + cometAddress: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", + }, + { + baseSymbol: "WETH", + cometAddress: "0xA17581A9E3356d9A858b789D68B4d866e593aE94", + }, + ], + 137: [ + { + baseSymbol: "USDC", + cometAddress: "0xF25212E676D1F7F89Cd72fFEe66158f541246445", + }, + ], + 8453: [ + { + baseSymbol: "USDbC", + cometAddress: "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", + }, + { + baseSymbol: "WETH", + cometAddress: "0x46e6b214b524310239732D51387075E0e70970bf", + }, + ], + 42161: [ + { + baseSymbol: "USDC.e", + cometAddress: "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA", + }, + { + baseSymbol: "USDC", + cometAddress: "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", + }, + ], + 10: [ + { + baseSymbol: "USDC", + cometAddress: comet.address, + }, + ], + }); + }, +}); diff --git a/deployments/optimism/usdc/relations.ts b/deployments/optimism/usdc/relations.ts new file mode 100644 index 000000000..a47a7f198 --- /dev/null +++ b/deployments/optimism/usdc/relations.ts @@ -0,0 +1,27 @@ +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', + }, + }, + } +}; diff --git a/deployments/optimism/usdc/roots.json b/deployments/optimism/usdc/roots.json new file mode 100644 index 000000000..a47badce7 --- /dev/null +++ b/deployments/optimism/usdc/roots.json @@ -0,0 +1,10 @@ +{ + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "CCTPMessageTransmitter": "0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8", + "comet": "0x2e44e174f7D53F0212823acC11C01A11d58c5bCB", + "configurator": "0x84E93EC6170ED630f5ebD89A1AAE72d4F63f2713", + "rewards": "0x443EA0340cb75a160F31A440722dec7b5bc3C2E9", + "bridgeReceiver": "0xC3a73A70d1577CD5B02da0bA91C0Afc8fA434DAF", + "bulker": "0xcb3643CC8294B23171272845473dEc49739d4Ba3" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 3b1799434..284c8cb5c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -36,10 +36,10 @@ import baseUsdcRelationConfigMap from './deployments/base/usdc/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 scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; - task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => { for (const account of await hre.ethers.getSigners()) console.log(account.address); }); @@ -54,6 +54,7 @@ const { ARBISCAN_KEY, BASESCAN_KEY, LINEASCAN_KEY, + OPTIMISMSCAN_KEY, INFURA_KEY, QUICKNODE_KEY, MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect', @@ -84,8 +85,9 @@ export function requireEnv(varName, msg?: string): string { 'INFURA_KEY', 'POLYGONSCAN_KEY', 'ARBISCAN_KEY', - 'LINEASCAN_KEY' -].map(v => requireEnv(v)); + 'LINEASCAN_KEY', + 'OPTIMISMSCAN_KEY' +].map((v) => requireEnv(v)); // Networks interface NetworkConfig { @@ -107,6 +109,11 @@ const networkConfigs: NetworkConfig[] = [ chainId: 137, url: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`, }, + { + network: 'optimism', + chainId: 10, + url: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`, + }, { network: 'base', chainId: 8453, @@ -247,6 +254,8 @@ const config: HardhatUserConfig = { 'base-goerli': BASESCAN_KEY, // Linea 'linea-goerli': LINEASCAN_KEY, + optimism: OPTIMISMSCAN_KEY, + optimisticEthereum: OPTIMISMSCAN_KEY, // Scroll Testnet 'scroll-goerli': ETHERSCAN_KEY, // Scroll @@ -362,6 +371,9 @@ const config: HardhatUserConfig = { 'linea-goerli': { usdc: lineaGoerliRelationConfigMap }, + optimism: { + usdc: optimismRelationConfigMap + }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap }, @@ -486,6 +498,12 @@ const config: HardhatUserConfig = { deployment: 'usdc', auxiliaryBase: 'goerli' }, + { + name: 'optimism-usdc', + network: 'optimism', + deployment: 'usdc', + auxiliaryBase: 'mainnet' + }, { name: 'scroll-goerli', network: 'scroll-goerli', diff --git a/plugins/import/etherscan.ts b/plugins/import/etherscan.ts index 81b500b0a..034c53042 100644 --- a/plugins/import/etherscan.ts +++ b/plugins/import/etherscan.ts @@ -22,6 +22,7 @@ export function getEtherscanApiUrl(network: string): string { base: 'api.basescan.org', 'base-goerli': 'api-goerli.basescan.org', 'linea-goerli': 'api-goerli.lineascan.build', + optimism: 'api-optimistic.etherscan.io', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'api.scrollscan.com' }[network]; @@ -49,6 +50,7 @@ export function getEtherscanUrl(network: string): string { base: 'basescan.org', 'base-goerli': 'goerli.basescan.org', 'linea-goerli': 'goerli.lineascan.build', + optimism: 'optimistic.etherscan.io', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'scrollscan.com' }[network]; @@ -76,6 +78,7 @@ export function getEtherscanApiKey(network: string): string { base: process.env.BASESCAN_KEY, 'base-goerli': process.env.BASESCAN_KEY, 'linea-goerli': process.env.LINEASCAN_KEY, + optimism: process.env.OPTIMISMSCAN_KEY, 'scroll-goerli': process.env.ETHERSCAN_KEY, scroll: process.env.ETHERSCAN_KEY }[network]; diff --git a/plugins/import/import.ts b/plugins/import/import.ts index 34c2109c0..439ace505 100644 --- a/plugins/import/import.ts +++ b/plugins/import/import.ts @@ -88,7 +88,8 @@ async function scrapeContractCreationCodeFromEtherscan(network: string, address: debug(`Attempting to scrape Contract Creation code at ${url}`); const result = await get(url, {}); const regex = /
[\s\r\n]*([0-9a-fA-F]*)[\s\r\n]*<\/div>/g; - const matches = [...result.matchAll(regex)]; + const regexDoubleQuotes = /
[\s\r\n]*([0-9a-fA-F]*)[\s\r\n]*<\/div>/g; + const matches = [...result.matchAll(regex), ...result.matchAll(regexDoubleQuotes)]; if (matches.length === 0) { if (result.match(/request throttled/i) || result.match(/try again later/i)) { throw new Error(`Request throttled: ${url}`); diff --git a/scenario/context/CometContext.ts b/scenario/context/CometContext.ts index 73e3d6e99..8354fbb7b 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'].includes(this.world.base.network); + const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism'].includes(this.world.base.network); return COMP_WHALES[useMainnetComp ? 'mainnet' : 'testnet']; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 061c5f7e7..492c0ab8e 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -504,6 +504,21 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } + case 'optimism': { + const sendMessageCalldata = utils.defaultAbiCoder.encode( + ['address', 'bytes', 'uint32'], + [bridgeReceiver.address, l2ProposalData, 2_500_000] + ); + const opL1CrossDomainMessenger = await govDeploymentManager.getContractOrThrow( + 'opL1CrossDomainMessenger' + ); + + targets.push(opL1CrossDomainMessenger.address); + values.push(0); + signatures.push('sendMessage(address,bytes,uint32)'); + calldata.push(sendMessageCalldata); + break; + } case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 03ec57b26..39f46bef3 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -52,6 +52,18 @@ export async function isBridgeProposal( 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( + 'opL1CrossDomainMessenger' + ); + const opL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + 'opL1StandardBridge' + ); + const { targets } = await governor.getActions(openProposal.id); + const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; + return targets.some(t => bridgeContracts.includes(t)); + } case 'scroll': case 'scroll-goerli': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index 25ad63849..b3a6f75d6 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -3,6 +3,7 @@ import relayPolygonMessage from './relayPolygonMessage'; import { relayArbitrumMessage, relayCCTPMint } from './relayArbitrumMessage'; import relayBaseMessage from './relayBaseMessage'; import relayLineaMessage from './relayLineaMessage'; +import relayOptimismMessage from './relayOptimismMessage'; import relayScrollMessage from './relayScrollMessage'; export default async function relayMessage( @@ -20,6 +21,13 @@ export default async function relayMessage( startingBlockNumber ); break; + case 'optimism': + await relayOptimismMessage( + governanceDeploymentManager, + bridgeDeploymentManager, + startingBlockNumber + ); + break; case 'mumbai': case 'polygon': await relayPolygonMessage( @@ -36,8 +44,8 @@ export default async function relayMessage( startingBlockNumber ); await relayCCTPMint( - governanceDeploymentManager, - bridgeDeploymentManager, + governanceDeploymentManager, + bridgeDeploymentManager, startingBlockNumber ); break; diff --git a/scenario/utils/relayOptimismMessage.ts b/scenario/utils/relayOptimismMessage.ts new file mode 100644 index 000000000..bd3c81d74 --- /dev/null +++ b/scenario/utils/relayOptimismMessage.ts @@ -0,0 +1,116 @@ +import { DeploymentManager } from '../../plugins/deployment_manager'; +import { impersonateAddress } from '../../plugins/scenario/utils'; +import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; +import { BigNumber, ethers } from 'ethers'; +import { Log } from '@ethersproject/abstract-provider'; +import { OpenBridgedProposal } from '../context/Gov'; + +function applyL1ToL2Alias(address: string) { + const offset = BigInt('0x1111000000000000000000000000000000001111'); + return `0x${(BigInt(address) + offset).toString(16)}`; +} + +export default async function relayOptimismMessage( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + startingBlockNumber: number +) { + const opL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow('opL1CrossDomainMessenger'); + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + const l2CrossDomainMessenger = await bridgeDeploymentManager.getContractOrThrow('l2CrossDomainMessenger'); + const l2StandardBridge = await bridgeDeploymentManager.getContractOrThrow('l2StandardBridge'); + + const openBridgedProposals: OpenBridgedProposal[] = []; + + // Grab all events on the L1CrossDomainMessenger contract since the `startingBlockNumber` + const filter = opL1CrossDomainMessenger.filters.SentMessage(); + const sentMessageEvents: Log[] = await governanceDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: startingBlockNumber, + toBlock: 'latest', + address: opL1CrossDomainMessenger.address, + topics: filter.topics! + }); + + for (let sentMessageEvent of sentMessageEvents) { + const { args: { target, sender, message, messageNonce, gasLimit } } = opL1CrossDomainMessenger.interface.parseLog(sentMessageEvent); + const aliasedSigner = await impersonateAddress( + bridgeDeploymentManager, + applyL1ToL2Alias(opL1CrossDomainMessenger.address) + ); + + await setNextBaseFeeToZero(bridgeDeploymentManager); + const relayMessageTxn = await ( + await l2CrossDomainMessenger.connect(aliasedSigner).relayMessage( + messageNonce, + sender, + target, + 0, + 0, + message, + { gasPrice: 0, gasLimit } + ) + ).wait(); + + // Try to decode the SentMessage data to determine what type of cross-chain activity this is. So far, + // there are two types: + // 1. Bridging ERC20 token or ETH + // 2. Cross-chain message passing + if (target === l2StandardBridge.address) { + // Bridging ERC20 token + const messageWithoutPrefix = message.slice(2); // strip out the 0x prefix + const messageWithoutSigHash = '0x' + messageWithoutPrefix.slice(8); + try { + // 1a. Bridging ERC20 token + 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}` + ); + } catch (e) { + // 1a. Bridging ETH + 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 + await bridgeDeploymentManager.hre.ethers.provider.send('hardhat_setBalance', [ + to, + ethers.utils.hexStripZeros(newBalance.toHexString()), + ]); + + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ETH to user ${to}` + ); + } + } 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 }); + } 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 }); + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Executed bridged proposal ${id}` + ); + } +} \ No newline at end of file diff --git a/src/deploy/index.ts b/src/deploy/index.ts index be2d38860..209bfff19 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -69,10 +69,12 @@ export type Proposal = [ // Ideally these wouldn't be hardcoded, but other solutions are much more complex, and slower export const COMP_WHALES = { mainnet: [ - '0xea6c3db2e7fca00ea9d7211a03e83f568fc13bf7', - '0x61258f12c459984f32b83c86a6cc10aa339396de', '0x9aa835bc7b8ce13b9b0c9764a52fbf71ac62ccf1', - '0x683a4f9915d6216f73d6df50151725036bd26c02' + '0x683a4f9915d6216f73d6df50151725036bd26c02', + '0x8169522c2C57883E8EF80C498aAB7820dA539806', + '0x8d07D225a769b7Af3A923481E1FdF49180e6A265', + '0x7d1a02C0ebcF06E1A36231A54951E061673ab27f', + '0x54A37d93E57c5DA659F508069Cf65A381b61E189' ], testnet: ['0xbbfe34e868343e6f4f5e8b5308de980d7bd88c46'] @@ -118,6 +120,10 @@ export const WHALES = { 'linea-goerli': [ '0xC858966280Da3Fa0348E51D2c3B892EcC889fC98', // USDC whale '0x44411c605eb7e009cad03f3847cfbbfcf8895130' // COMP whale + ], + optimism: [ + '0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26', // OP whale + '0x8af3827a41c26c7f32c81e93bb66e837e0210d5c' // USDC whale ] };