From bbee0877efd8a8ea4929fe194fad608054931a6e Mon Sep 17 00:00:00 2001 From: isabellewei Date: Thu, 28 Mar 2024 14:47:14 -0400 Subject: [PATCH] Add USDC market on Scroll mainnet (#824) * debug * update price feed * update config * temp workaround * add scroll to scenarios * uncomment spider debug messages * add scroll relations * catch failed spider crawls * Fix spider for scroll * update bridge receiver to be compatible with scroll bridge * update relations * Modified deployment roots from GitHub Actions * remove console.log * rename scroll messenger * add scroll to governance scenarios * adapt scroll relay message fn * fix unit tests * rename contracts and update message relaying * update scroll testnet block explorer URL * remove base fee * fix scroll l2 sender address * remove old roots * fix type errors * Modified deployment roots from GitHub Actions * proposal for intializing usdc market on scroll goerli * add configurator to scroll relations * PR comments * add docstrings * emit event when intializing ScrollBridgeReceiver * PR comments * update comment * add usdc market on scroll mainnet * fix wseth * create migration * use gauntlet recommended parameters * lint and fix timelock params * set rewards speed to 0 * add pause guardian * fix scientific notation * add scroll mainnet roots * update scroll relations for wsteth * PR comments * update wsteth/USD price feed to exchange rate based * Modified deployment roots from GitHub Actions * update proposal description * update scroll governance proposal to bridge USDC instead of COMP * final PR comments * set message value on propsal actions --------- Co-authored-by: Isabelle Wei Co-authored-by: isabelle Co-authored-by: kevincheng96 Co-authored-by: GitHub Actions Bot <> --- .github/workflows/deploy-market.yaml | 3 +- .github/workflows/enact-migration.yaml | 5 +- .github/workflows/prepare-migration.yaml | 2 +- .github/workflows/run-scenarios.yaml | 2 +- deployments/mainnet/usdc/relations.ts | 14 ++ deployments/mainnet/usdc/roots.json | 4 +- deployments/scroll/usdc/configuration.json | 46 ++++ deployments/scroll/usdc/deploy.ts | 87 +++++++ .../1706149385_configurate_and_ens.ts | 228 ++++++++++++++++++ deployments/scroll/usdc/relations.ts | 52 ++++ deployments/scroll/usdc/roots.json | 12 + hardhat.config.ts | 27 ++- plugins/import/etherscan.ts | 3 + scenario/CrossChainGovernanceScenario.ts | 2 +- scenario/utils/index.ts | 1 + scenario/utils/isBridgeProposal.ts | 1 + scenario/utils/relayMessage.ts | 1 + scenario/utils/relayScrollMessage.ts | 29 ++- 18 files changed, 506 insertions(+), 13 deletions(-) create mode 100644 deployments/scroll/usdc/configuration.json create mode 100644 deployments/scroll/usdc/deploy.ts create mode 100644 deployments/scroll/usdc/migrations/1706149385_configurate_and_ens.ts create mode 100644 deployments/scroll/usdc/relations.ts create mode 100644 deployments/scroll/usdc/roots.json diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index 4938b6a4c..5eb34eb97 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - scroll-goerli + - scroll deployment: description: Deployment Name (e.g. "usdc") required: true @@ -45,7 +46,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\"}')[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\",\"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 == '' diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 33a7a700e..159f57eee 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - scroll-goerli + - scroll deployment: description: Deployment Name (e.g. "usdc") required: true @@ -53,7 +54,7 @@ jobs: case ${{ github.event.inputs.network }} in polygon | arbitrum | base) echo "GOV_NETWORK=mainnet" >> $GITHUB_ENV ;; - mumbai | arbitrum-goerli | base-goerli | linea-goerli | scroll-goerli) + mumbai | arbitrum-goerli | base-goerli | linea-goerli | scroll-goerli | scroll) echo "GOV_NETWORK=goerli" >> $GITHUB_ENV ;; *) echo "No governance network for selected network" ;; @@ -64,7 +65,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\",\"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\"}')[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\",\"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 == '' diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index 9715ead6c..14b7d8c4d 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -46,7 +46,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\",\"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\"}')[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\",\"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 == '' diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index f0c27e83f..1808e485a 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, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-goerli, base-goerli-weth, linea-goerli, scroll-goerli] + bases: [ development, mainnet, mainnet-weth, goerli, goerli-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-goerli, base-goerli-weth, linea-goerli, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/mainnet/usdc/relations.ts b/deployments/mainnet/usdc/relations.ts index a076adcaa..a15c9370d 100644 --- a/deployments/mainnet/usdc/relations.ts +++ b/deployments/mainnet/usdc/relations.ts @@ -41,5 +41,19 @@ export default { slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' } } + }, + scrollMessenger: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + scrollL1USDCGateway: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } } }; \ No newline at end of file diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index 0aa64b6cc..996d96beb 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -10,5 +10,7 @@ "CCTPTokenMessenger": "0xbd3fa81b58ba92a82136038b25adec7066af3155", "CCTPMessageTransmitter": "0x0a992d191deec32afe36203ad87d7d289a738f81", "baseL1CrossDomainMessenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa", - "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35" + "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", + "scrollMessenger": "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367", + "scrollL1USDCGateway": "0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B" } \ No newline at end of file diff --git a/deployments/scroll/usdc/configuration.json b/deployments/scroll/usdc/configuration.json new file mode 100644 index 000000000..db3819878 --- /dev/null +++ b/deployments/scroll/usdc/configuration.json @@ -0,0 +1,46 @@ +{ + "name": "Compound USDC", + "symbol": "cUSDCv3", + "baseToken": "USDC", + "baseTokenAddress": "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4", + "baseTokenPriceFeed": "0x43d12Fb3AfCAd5347fA764EeAB105478337b7200", + "borrowMin": "0.001e6", + "pauseGuardian": "0x0747a435b8a60070A7a111D015046d765098e4cc", + "storeFrontPriceFactor": 0.6, + "targetReserves": "1000000e6", + "rates": { + "supplyKink": 0.85, + "supplySlopeLow": 0.048, + "supplySlopeHigh": 1.6, + "supplyBase": 0, + "borrowKink": 0.85, + "borrowSlopeLow": 0.053, + "borrowSlopeHigh": 1.8, + "borrowBase": 0.015 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "0e15", + "baseBorrowSpeed": "0e15", + "baseMinForRewards": "1000e6" + }, + "assets": { + "WETH": { + "address": "0x5300000000000000000000000000000000000004", + "priceFeed": "0x6bF14CB0A831078629D993FDeBcB182b21A8774C", + "decimals": "18", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.9, + "supplyCap": "300e18" + }, + "wstETH": { + "address": "0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32", + "decimals": "18", + "borrowCF": 0.75, + "liquidateCF": 0.8, + "liquidationFactor": 0.85, + "supplyCap": "50e18" + } + } +} diff --git a/deployments/scroll/usdc/deploy.ts b/deployments/scroll/usdc/deploy.ts new file mode 100644 index 000000000..786e773a8 --- /dev/null +++ b/deployments/scroll/usdc/deploy.ts @@ -0,0 +1,87 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet, exp, wait } from '../../../src/deploy'; + +const HOUR = 60 * 60; +const DAY = 24 * HOUR; + +const MAINNET_TIMELOCK = '0x6d903f6003cca6255d85cca4d3b5e5146dc33925'; // 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','0x5300000000000000000000000000000000000004','scroll'); + const wstETH = await deploymentManager.existing('wstETH', '0xf610A9dfB7C89644979b4A0f27063E9e7d7Cda32', 'scroll'); + + const l2Messenger = await deploymentManager.existing('l2Messenger','0x781e90f1c8Fc4611c9b7497C3B47F99Ef6969CbC','scroll'); + const l2ERC20Gateway = await deploymentManager.existing('l2ERC20Gateway','0xE2b4795039517653c5Ae8C2A9BFdd783b48f447A','scroll'); + const l2ETHGateway = await deploymentManager.existing('l2ETHGateway', '0x6EA73e05AdC79974B931123675ea8F78FfdacDF0', 'scroll'); + const l2WETHGateway = await deploymentManager.existing('l2WETHGateway','0x7003E7B7186f0E6601203b99F7B8DECBfA391cf9','scroll'); + const l2WstETHGateway = await deploymentManager.existing('l2WstETHGateway', '0x8aE8f22226B9d789A36AC81474e633f8bE2856c9', 'scroll'); + + // 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 + 1 * DAY, // delay + 14 * DAY, // grace period + 12 * HOUR, // minimum delay + 30 * DAY // maxiumum delay + ]); + + // Deploy multiplicative price feed for wstETH / USD + const wstETHMultiplicativePriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + '0xE61Da4C909F7d86797a0D06Db63c34f76c9bCBDC', // wstETH-stETH price feed + '0x6bF14CB0A831078629D993FDeBcB182b21A8774C', // ETH / USD price feed + 8, // decimals + 'wstETH / USD price feed' // description + ] + ); + + // Initialize BridgeReceiver + 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, + l2Messenger, + l2ERC20Gateway, + l2ETHGateway, + l2WETHGateway, + l2WstETHGateway, + bulker, + }; +} diff --git a/deployments/scroll/usdc/migrations/1706149385_configurate_and_ens.ts b/deployments/scroll/usdc/migrations/1706149385_configurate_and_ens.ts new file mode 100644 index 000000000..d5c4dd59d --- /dev/null +++ b/deployments/scroll/usdc/migrations/1706149385_configurate_and_ens.ts @@ -0,0 +1,228 @@ +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 = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; +const scrollCOMPAddress = '0x643e160a3C3E2B7eae198f0beB1BfD2441450e86'; + +export default migration('1706149385_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, + scrollL1USDCGateway, + governor, + USDC + } = await govDeploymentManager.getContracts(); + + // 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 scrollChainId = ( + 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[scrollChainId]) { + officialMarketsJSON[scrollChainId].push(newMarketObject); + } else { + officialMarketsJSON[scrollChainId] = [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 USDCAmountToBridge = exp(10_000, 6); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Scroll + { + contract: scrollMessenger, + signature: 'sendMessage(address,uint256,bytes,uint256)', + args: [bridgeReceiver.address, 0, l2ProposalData, 600_000], + value: exp(0.1, 18) + }, + + // 2. Approve Scroll's L1 USDC Gateway to take Timelock's USDC (for bridging) + { + contract: USDC, + signature: 'approve(address,uint256)', + args: [scrollL1USDCGateway.address, USDCAmountToBridge] + }, + // 3. Bridge USDC from Ethereum to Scroll Comet using L1 USDC Gateway + { + contract: scrollL1USDCGateway, + signature: 'depositERC20(address,address,uint256,uint256)', + args: [USDC.address, comet.address, USDCAmountToBridge, 300_000], + value: exp(0.1, 18) + }, + // 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 = + "# Initialize cUSDCv3 on Scroll\n\nThis proposal takes the governance steps recommended and necessary to initialize a Compound III USDC market on Scroll; 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). Although real tests have also been run over the Goerli/Scroll Alpha bridge, this will be the first proposal to actually bridge from Ethereum mainnet to Scroll mainnet, and therefore includes risks not present in previous proposals.\n\nAlthough the proposal sets the entire configuration in the Configurator, with parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-scroll/4917/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/824) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-scroll/4917).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration and deploys a new Comet implementation on Scroll. This sends the encoded `setConfiguration` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Scroll. It also calls `setRewardConfig` on the Scroll rewards contract to establish Scroll’s bridged version of COMP as the reward token for the deployment (note that rewards speeds have been set to 0, as Gauntlet has recommended to hold off on including rewards in the comet deployment for now).\n\nThe second action approves Scroll’s [L1USDCGateway](https://etherscan.io/address/0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B) to take Timelock's USDC, in order to seed the market reserves through the bridge.\n\nThe third action deposits 10K USDC from mainnet to the Scroll L1USDCGateway contract to bridge to Comet.\n\nThe fourth 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 Scroll 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(); // 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); + 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: '0x149B7023781d1D37689d447A565a1bf5854a8e3d' + } + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: comet.address + } + ] + }); + } + +}); diff --git a/deployments/scroll/usdc/relations.ts b/deployments/scroll/usdc/relations.ts new file mode 100644 index 000000000..40baae4e7 --- /dev/null +++ b/deployments/scroll/usdc/relations.ts @@ -0,0 +1,52 @@ +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' + } + } + }, + l2WstETHGateway: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // wstETH + '0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; diff --git a/deployments/scroll/usdc/roots.json b/deployments/scroll/usdc/roots.json new file mode 100644 index 000000000..1960e352a --- /dev/null +++ b/deployments/scroll/usdc/roots.json @@ -0,0 +1,12 @@ +{ + "comet": "0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44", + "configurator": "0xECAB0bEEa3e5DEa0c35d3E69468EAC20098032D7", + "rewards": "0x70167D30964cbFDc315ECAe02441Af747bE0c5Ee", + "bridgeReceiver": "0xC6bf5A64896D679Cf89843DbeC6c0f5d3C9b610D", + "l2Messenger": "0x781e90f1c8Fc4611c9b7497C3B47F99Ef6969CbC", + "l2ERC20Gateway": "0xE2b4795039517653c5Ae8C2A9BFdd783b48f447A", + "l2ETHGateway": "0x6EA73e05AdC79974B931123675ea8F78FfdacDF0", + "l2WETHGateway": "0x7003E7B7186f0E6601203b99F7B8DECBfA391cf9", + "l2WstETHGateway": "0x8aE8f22226B9d789A36AC81474e633f8bE2856c9", + "bulker": "0x53C6D04e3EC7031105bAeA05B36cBc3C987C56fA" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 5103ab61c..68178820f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -37,6 +37,7 @@ import baseGoerliRelationConfigMap from './deployments/base-goerli/usdc/relation import baseGoerliWethRelationConfigMap from './deployments/base-goerli/weth/relations'; import lineaGoerliRelationConfigMap from './deployments/linea-goerli/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) => { @@ -150,6 +151,11 @@ const networkConfigs: NetworkConfig[] = [ network: 'scroll-goerli', chainId: 534353, url: 'https://alpha-rpc.scroll.io/l2', + }, + { + network: 'scroll', + chainId: 534352, + url: 'https://rpc.scroll.io', } ]; @@ -241,8 +247,10 @@ const config: HardhatUserConfig = { 'base-goerli': BASESCAN_KEY, // Linea 'linea-goerli': LINEASCAN_KEY, + // Scroll Testnet + 'scroll-goerli': ETHERSCAN_KEY, // Scroll - 'scroll-goerli': ETHERSCAN_KEY + 'scroll': ETHERSCAN_KEY, }, customChains: [ { @@ -296,6 +304,14 @@ const config: HardhatUserConfig = { apiURL: 'https://alpha-blockscout.scroll.io/api', browserURL: 'https://alpha-blockscout.scroll.io/' } + }, + { + network: 'scroll', + chainId: 534352, + urls: { + apiURL: 'https://api.scrollscan.com/api', + browserURL: 'https://scrollscan.com/' + } } ] }, @@ -348,6 +364,9 @@ const config: HardhatUserConfig = { }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap + }, + 'scroll': { + usdc: scrollRelationConfigMap } }, }, @@ -472,6 +491,12 @@ const config: HardhatUserConfig = { network: 'scroll-goerli', deployment: 'usdc', auxiliaryBase: 'goerli' + }, + { + name: 'scroll-usdc', + network: 'scroll', + deployment: 'usdc', + auxiliaryBase: 'mainnet' } ], }, diff --git a/plugins/import/etherscan.ts b/plugins/import/etherscan.ts index ccde6fd8a..81b500b0a 100644 --- a/plugins/import/etherscan.ts +++ b/plugins/import/etherscan.ts @@ -23,6 +23,7 @@ export function getEtherscanApiUrl(network: string): string { 'base-goerli': 'api-goerli.basescan.org', 'linea-goerli': 'api-goerli.lineascan.build', 'scroll-goerli': 'alpha-blockscout.scroll.io', + scroll: 'api.scrollscan.com' }[network]; if (!host) { @@ -49,6 +50,7 @@ export function getEtherscanUrl(network: string): string { 'base-goerli': 'goerli.basescan.org', 'linea-goerli': 'goerli.lineascan.build', 'scroll-goerli': 'alpha-blockscout.scroll.io', + scroll: 'scrollscan.com' }[network]; if (!host) { @@ -75,6 +77,7 @@ export function getEtherscanApiKey(network: string): string { 'base-goerli': process.env.BASESCAN_KEY, 'linea-goerli': process.env.LINEASCAN_KEY, 'scroll-goerli': process.env.ETHERSCAN_KEY, + scroll: process.env.ETHERSCAN_KEY }[network]; if (!apiKey) { diff --git a/scenario/CrossChainGovernanceScenario.ts b/scenario/CrossChainGovernanceScenario.ts index 945c4d65c..cc25ab765 100644 --- a/scenario/CrossChainGovernanceScenario.ts +++ b/scenario/CrossChainGovernanceScenario.ts @@ -386,7 +386,7 @@ scenario( scenario( 'upgrade Scroll governance contracts and ensure they work properly', { - filter: async ctx => matchesDeployment(ctx, [{ network: 'scroll-goerli' }]) + filter: async ctx => matchesDeployment(ctx, [{ network: 'scroll-goerli' }, {network: 'scroll'}]) }, async ( { diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 54ae9063f..061c5f7e7 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -504,6 +504,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } + case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'uint256', 'bytes', 'uint256'], diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 9e1bf67e0..03ec57b26 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -52,6 +52,7 @@ export async function isBridgeProposal( const { targets } = await governor.getActions(openProposal.id); return targets.includes(lineaMessageService.address); } + case 'scroll': case 'scroll-goerli': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); const scrollMessenger = await governanceDeploymentManager.getContractOrThrow( diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index 2d71907c5..25ad63849 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -48,6 +48,7 @@ export default async function relayMessage( startingBlockNumber ); break; + case 'scroll': case 'scroll-goerli': await relayScrollMessage( governanceDeploymentManager, diff --git a/scenario/utils/relayScrollMessage.ts b/scenario/utils/relayScrollMessage.ts index fac3443c7..e6feb53d9 100644 --- a/scenario/utils/relayScrollMessage.ts +++ b/scenario/utils/relayScrollMessage.ts @@ -29,6 +29,7 @@ export default async function relayScrollMessage( const l2ERC20Gateway = await bridgeDeploymentManager.getContractOrThrow('l2ERC20Gateway'); const l2ETHGateway = await bridgeDeploymentManager.getContractOrThrow('l2ETHGateway'); const l2WETHGateway = await bridgeDeploymentManager.getContractOrThrow('l2WETHGateway'); + const l2WstETHGateway = await bridgeDeploymentManager.getContractOrThrow('l2WstETHGateway'); const openBridgedProposals: OpenBridgedProposal[] = []; @@ -47,10 +48,18 @@ export default async function relayScrollMessage( await setNextBaseFeeToZero(bridgeDeploymentManager); - const aliasAccount = await impersonateAddress( - bridgeDeploymentManager, - "0xD69c917c7F1C0a724A51c189B4A8F4F8C8E8cA0a" - ); + let aliasAccount; + if (bridgeDeploymentManager.network == 'scroll-goerli'){ + aliasAccount = await impersonateAddress( + bridgeDeploymentManager, + '0xD69c917c7F1C0a724A51c189B4A8F4F8C8E8cA0a' + ); + } else { + aliasAccount = await impersonateAddress( + bridgeDeploymentManager, + applyL1ToL2Alias(scrollMessenger.address) + ); + } const relayMessageTxn = await ( await l2Messenger.connect(aliasAccount).relayMessage( @@ -99,7 +108,7 @@ export default async function relayScrollMessage( `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ETH to user ${to}` ); }else if (target === l2WETHGateway.address){ - // Bridging WETH + // 1c. Bridging WETH const { _l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( ['address _l1Token', 'address _l2Token','address _from', 'address _to','uint256 _amount', 'bytes _data'], messageWithoutSigHash @@ -108,6 +117,16 @@ export default async function relayScrollMessage( 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( + ['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);