diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 65537babf..fe2eb0111 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, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-goerli, base-goerli-weth, linea-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] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/arbitrum/usdc.e/configuration.json b/deployments/arbitrum/usdc.e/configuration.json new file mode 100644 index 000000000..d95c0af43 --- /dev/null +++ b/deployments/arbitrum/usdc.e/configuration.json @@ -0,0 +1,65 @@ +{ + "name": "Compound USDC", + "symbol": "cUSDCv3", + "baseToken": "USDC.e", + "baseTokenAddress": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", + "baseTokenPriceFeed": "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", + "borrowMin": "100e6", + "pauseGuardian": "0x78E6317DD6D43DdbDa00Dce32C2CbaFc99361a9d", + "storeFrontPriceFactor": 0.8, + "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": "0e15", + "baseMinForRewards": "10000e6" + }, + "assets": { + "ARB": { + "address": "0x912ce59144191c1204e64559fe8253a0e49e6548", + "priceFeed": "0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6", + "decimals": "18", + "borrowCF": 0.55, + "liquidateCF": 0.60, + "liquidationFactor": 0.93, + "supplyCap": "4000000e18" + }, + "GMX": { + "address": "0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a", + "priceFeed": "0xDB98056FecFff59D032aB628337A4887110df3dB", + "decimals": "18", + "borrowCF": 0.40, + "liquidateCF": 0.45, + "liquidationFactor": 0.93, + "supplyCap": "50000e18" + }, + "WETH": { + "address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", + "priceFeed": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", + "decimals": "18", + "borrowCF": 0.78, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "5000e18" + }, + "WBTC": { + "address": "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f", + "priceFeed": "0xd0C7101eACbB49F3deCcCc166d238410D6D46d57", + "decimals": "8", + "borrowCF": 0.70, + "liquidateCF": 0.77, + "liquidationFactor": 0.95, + "supplyCap": "300e8" + } + } +} \ No newline at end of file diff --git a/deployments/arbitrum/usdc.e/deploy.ts b/deployments/arbitrum/usdc.e/deploy.ts new file mode 100644 index 000000000..e2456c03c --- /dev/null +++ b/deployments/arbitrum/usdc.e/deploy.ts @@ -0,0 +1,72 @@ +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 trace = deploymentManager.tracer() + const ethers = deploymentManager.hre.ethers; + + // pull in existing assets + const USDC = await deploymentManager.existing('USDC', '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', 'arbitrum'); + const ARB = await deploymentManager.existing('ARB', '0x912ce59144191c1204e64559fe8253a0e49e6548', 'arbitrum'); + const GMX = await deploymentManager.existing('GMX', '0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a', 'arbitrum'); + const WETH = await deploymentManager.existing('WETH', '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 'arbitrum'); + const WBTC = await deploymentManager.existing('WBTC', '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f', 'arbitrum'); + + // 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 + 1 * DAY, // delay + 14 * DAY, // grace period + 12 * HOUR, // minimum delay + 30 * DAY // maxiumum delay + ] + ); + + // Initialize ArbitrumBridgeReceiver + 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, + bulker + }; +} \ No newline at end of file diff --git a/deployments/arbitrum/usdc/migrations/1679020486_configurate_and_ens.ts b/deployments/arbitrum/usdc.e/migrations/1679020486_configurate_and_ens.ts similarity index 100% rename from deployments/arbitrum/usdc/migrations/1679020486_configurate_and_ens.ts rename to deployments/arbitrum/usdc.e/migrations/1679020486_configurate_and_ens.ts diff --git a/deployments/arbitrum/usdc.e/relations.ts b/deployments/arbitrum/usdc.e/relations.ts new file mode 100644 index 000000000..f8265b45f --- /dev/null +++ b/deployments/arbitrum/usdc.e/relations.ts @@ -0,0 +1,38 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: 'contracts/bridges/arbitrum/ArbitrumBridgeReceiver.sol:ArbitrumBridgeReceiver' + }, + ClonableBeaconProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + // USDC + '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // ARB + '0x912ce59144191c1204e64559fe8253a0e49e6548': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // WETH + '0x82af49447d8a07e3bd95bd0d56f35241523fbab1': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + } +}; diff --git a/deployments/arbitrum/usdc.e/roots.json b/deployments/arbitrum/usdc.e/roots.json new file mode 100644 index 000000000..dce14a07b --- /dev/null +++ b/deployments/arbitrum/usdc.e/roots.json @@ -0,0 +1,7 @@ +{ + "comet": "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA", + "configurator": "0xb21b06D71c75973babdE35b49fFDAc3F82Ad3775", + "rewards": "0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae", + "bridgeReceiver": "0x42480C37B249e33aABaf4c22B20235656bd38068", + "bulker": "0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d" +} \ No newline at end of file diff --git a/deployments/arbitrum/usdc/configuration.json b/deployments/arbitrum/usdc/configuration.json index e7d68ae12..7690422f7 100644 --- a/deployments/arbitrum/usdc/configuration.json +++ b/deployments/arbitrum/usdc/configuration.json @@ -2,9 +2,9 @@ "name": "Compound USDC", "symbol": "cUSDCv3", "baseToken": "USDC", - "baseTokenAddress": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", + "baseTokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "baseTokenPriceFeed": "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", - "borrowMin": "100e6", + "borrowMin": "1e0", "pauseGuardian": "0x78E6317DD6D43DdbDa00Dce32C2CbaFc99361a9d", "storeFrontPriceFactor": 0.8, "targetReserves": "5000000e6", @@ -20,7 +20,7 @@ }, "tracking": { "indexScale": "1e15", - "baseSupplySpeed": "0.000402083333333e15", + "baseSupplySpeed": "0.000115740740740e15", "baseBorrowSpeed": "0e15", "baseMinForRewards": "10000e6" }, diff --git a/deployments/arbitrum/usdc/deploy.ts b/deployments/arbitrum/usdc/deploy.ts index e2456c03c..fcbbd09a6 100644 --- a/deployments/arbitrum/usdc/deploy.ts +++ b/deployments/arbitrum/usdc/deploy.ts @@ -11,62 +11,30 @@ export default async function deploy(deploymentManager: DeploymentManager, deplo const ethers = deploymentManager.hre.ethers; // pull in existing assets - const USDC = await deploymentManager.existing('USDC', '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', 'arbitrum'); + // USDC native + const USDC = await deploymentManager.existing('USDC', '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', 'arbitrum'); const ARB = await deploymentManager.existing('ARB', '0x912ce59144191c1204e64559fe8253a0e49e6548', 'arbitrum'); const GMX = await deploymentManager.existing('GMX', '0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a', 'arbitrum'); const WETH = await deploymentManager.existing('WETH', '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 'arbitrum'); const WBTC = await deploymentManager.existing('WBTC', '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f', 'arbitrum'); - // 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 - 1 * DAY, // delay - 14 * DAY, // grace period - 12 * HOUR, // minimum delay - 30 * DAY // maxiumum delay - ] - ); - - // Initialize ArbitrumBridgeReceiver - await deploymentManager.idempotent( - async () => !(await bridgeReceiver.initialized()), - async () => { - trace(`Initializing BridgeReceiver`); - await bridgeReceiver.initialize( - MAINNET_TIMELOCK, // govTimelock - localTimelock.address // localTimelock - ); - trace(`BridgeReceiver initialized`); - } - ); + // Import shared contracts from the USDC.e market + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'arbitrum', 'usdc.e'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum', 'usdc.e'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'arbitrum', 'usdc.e'); + const configurator = await deploymentManager.fromDep('configurator', 'arbitrum', 'usdc.e'); + const rewards = await deploymentManager.fromDep('rewards', 'arbitrum', 'usdc.e'); + const bulker = await deploymentManager.fromDep('bulker', 'arbitrum', 'usdc.e'); + const localTimelock = await deploymentManager.fromDep('timelock', 'arbitrum', 'usdc.e'); + const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'arbitrum', 'usdc.e'); // 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 + bridgeReceiver, + bulker, + rewards, }; } \ No newline at end of file diff --git a/deployments/arbitrum/usdc/migrations/1686953660_configurate_and_ens.ts b/deployments/arbitrum/usdc/migrations/1686953660_configurate_and_ens.ts new file mode 100644 index 000000000..0c9184741 --- /dev/null +++ b/deployments/arbitrum/usdc/migrations/1686953660_configurate_and_ens.ts @@ -0,0 +1,267 @@ +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 = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const arbitrumCOMPAddress = '0x354A6dA3fcde098F8389cad84b0182725c6C91dE'; + +export default migration('1686953660_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', 'usdc.e'); + const usdceComet = await deploymentManager.fromDep('usdceComet', 'arbitrum', 'usdc.e', 'comet'); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator, + rewards, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + arbitrumL1GatewayRouter, + timelock, + governor, + USDC, + CCTPTokenMessenger, + } = await govDeploymentManager.getContracts(); + + 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 USDCAmountToBridge = exp(10_000, 6); + // CCTP destination domain for Arbitrum + const ArbitrumDestinationDomain = 3; + 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); + 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)] + ) + } + ]; + + const description = "# Initialize native USDC market cUSDCv3 on Arbitrum\n\nThis proposal takes the governance steps recommended and necessary to initialize a Compound III USDC (native USDC on Arbitrum) market on Arbitrum; 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/Arbitrum Goerli, this will be the first proposal to mint native USDC on Arbitrum mainnet by burning USDC on mainnet via the `depositAndBurn` function on the Cross-Chain Transfer Protocol (CCTP) provided by Circle, and therefore includes risks not present in previous proposals.\n\nThe proposal sets the entire configuration in the Configurator to be the same as the existing bridged USDC.e market. Finally, the parameters include a migration of bridged USDC.e market supply-side COMP incentives to users in the new native USDC market.\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/789) and [forum discussion](https://www.comp.xyz/t/initialize-compound-iii-native-usdc-on-arbitrum/4542).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet Factory, Comet configuration and deploys a new Comet implementation on Arbitrum. This sends the encoded `setFactory`, `setConfiguration` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Arbitrum. It also calls `setRewardConfig` on the Arbitrum rewards contract, to establish Arbitrum’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 10 COMP/day and borrow speed to be 0 COMP/day. It calls another `setBaseTrackingSupplySpeed` and `setBaseTrackingBorrowSpeed` to set the supply speed and borrow speed of the existing USDC.e market to be 0 COMP/day. Lastly it calls `deployAndUpgradeTo` to deploy an updated Comet implementation for the existing bridged USDC.e market to have the new supply and borrow rewards speed.\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 Arbitrum.\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 native USDC market and renames the bridged USDC market’s baseSymbol to USDC.e from USDC."; + 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', 'usdc.e', 'comet'); + const { + comet, + rewards, + } = await deploymentManager.getContracts(); + + const { + comptrollerV2 + } = await govDeploymentManager.getContracts(); + + // 1. Verify state changes + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + expect(stateChanges).to.deep.equal({ + ARB: { + supplyCap: exp(4_000_000, 18) + }, + GMX: { + supplyCap: exp(50_000, 18) + }, + WETH: { + supplyCap: exp(5_000, 18) + }, + WBTC: { + supplyCap: exp(300, 8) + }, + baseTrackingSupplySpeed: exp(10 / 86400, 15, 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); + // 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. + expect(await comet.getReserves()).to.be.equal(exp(10_000, 6)); + + // 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({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf' + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + ], + + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: comet.address, + } + ], + }); + } +}); \ No newline at end of file diff --git a/deployments/arbitrum/usdc/relations.ts b/deployments/arbitrum/usdc/relations.ts index f8265b45f..b1ca91f34 100644 --- a/deployments/arbitrum/usdc/relations.ts +++ b/deployments/arbitrum/usdc/relations.ts @@ -8,7 +8,16 @@ export default { ClonableBeaconProxy: { artifact: 'contracts/ERC20.sol:ERC20' }, - // USDC + // Native USDC + '0xaf88d065e77c8cC2239327C5EDb3A432268e5831': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // Bridged USDC '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8': { artifact: 'contracts/ERC20.sol:ERC20', delegates: { @@ -34,5 +43,5 @@ export default { slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' } } - } -}; + }, +}; \ No newline at end of file diff --git a/deployments/arbitrum/usdc/roots.json b/deployments/arbitrum/usdc/roots.json index dce14a07b..54603b0c6 100644 --- a/deployments/arbitrum/usdc/roots.json +++ b/deployments/arbitrum/usdc/roots.json @@ -1,7 +1,8 @@ { - "comet": "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA", + "comet": "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", "configurator": "0xb21b06D71c75973babdE35b49fFDAc3F82Ad3775", "rewards": "0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae", "bridgeReceiver": "0x42480C37B249e33aABaf4c22B20235656bd38068", - "bulker": "0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d" + "bulker": "0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d", + "CCTPMessageTransmitter": "0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca" } \ No newline at end of file diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index 027b22ac9..0aa64b6cc 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -6,7 +6,9 @@ "bulker": "0xa397a8C2086C554B531c02E29f3291c9704B00c7", "fxRoot": "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2", "arbitrumInbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", - "arbitrumL1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", + "arbitrumL1GatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", + "CCTPTokenMessenger": "0xbd3fa81b58ba92a82136038b25adec7066af3155", + "CCTPMessageTransmitter": "0x0a992d191deec32afe36203ad87d7d289a738f81", "baseL1CrossDomainMessenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa", "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35" } \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index ec467b2b7..c38d1b4e1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -24,7 +24,8 @@ import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; -import arbitrumRelationConfigMap from './deployments/arbitrum/usdc/relations'; +import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; +import arbitrumNativeUsdcRelationConfigMap from './deployments/arbitrum/usdc/relations'; import arbitrumBridgedUsdcGoerliRelationConfigMap from './deployments/arbitrum-goerli/usdc.e/relations'; import arbitrumGoerliNativeUsdcRelationConfigMap from './deployments/arbitrum-goerli/usdc/relations'; import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations'; @@ -301,7 +302,8 @@ const config: HardhatUserConfig = { usdc: polygonRelationConfigMap }, arbitrum: { - usdc: arbitrumRelationConfigMap + 'usdc.e': arbitrumBridgedUsdcRelationConfigMap, + usdc: arbitrumNativeUsdcRelationConfigMap }, 'arbitrum-goerli': { 'usdc.e': arbitrumBridgedUsdcGoerliRelationConfigMap, @@ -367,7 +369,13 @@ const config: HardhatUserConfig = { auxiliaryBase: 'mainnet' }, { - name: 'arbitrum', + name: 'arbitrum-usdc.e', + network: 'arbitrum', + deployment: 'usdc.e', + auxiliaryBase: 'mainnet' + }, + { + name: 'arbitrum-usdc', network: 'arbitrum', deployment: 'usdc', auxiliaryBase: 'mainnet' diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 25da5de7f..763468c9e 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -98,6 +98,7 @@ for (let i = 0; i < MAX_ASSETS; i++) { usdc: 2250000 }, arbitrum: { + 'usdc.e': 10000000, usdc: 10000000 } }; @@ -135,6 +136,16 @@ for (let i = 0; i < MAX_ASSETS; i++) { ], }, arbitrum: { + 'usdc.e': [ + // ARB + ' == 500000', + // GMX + ' == 4000', + // WETH + ' == 500', + // WBTC + ' == 50' + ], usdc: [ // ARB ' == 500000', @@ -272,6 +283,7 @@ for (let i = 0; i < MAX_ASSETS; i++) { usdc: 3000000 }, arbitrum: { + 'usdc.e': 10000000, usdc: 10000000 } }; @@ -309,6 +321,16 @@ for (let i = 0; i < MAX_ASSETS; i++) { ] }, arbitrum: { + 'usdc.e': [ + // ARB + ' == 1000000', + // GMX + ' == 10000', + // WETH + ' == 5000', + // WBTC + ' == 300' + ], usdc: [ // ARB ' == 1000000', @@ -355,6 +377,16 @@ for (let i = 0; i < MAX_ASSETS; i++) { ] }, arbitrum: { + 'usdc.e': [ + // ARB + exp(300000, 18), + // GMX + exp(3000, 18), + // WETH + exp(500, 18), + // WBTC + exp(50, 8), + ], usdc: [ // ARB exp(300000, 18), diff --git a/scenario/utils/bridgeProposal.ts b/scenario/utils/bridgeProposal.ts index 14d7cf923..10c103040 100644 --- a/scenario/utils/bridgeProposal.ts +++ b/scenario/utils/bridgeProposal.ts @@ -34,8 +34,9 @@ export async function executeBridgedProposal( const receiver = await deploymentManager.getContractOrThrow('bridgeReceiver'); const { id, eta } = proposal; + const blockNow = await deploymentManager.hre.ethers.provider.getBlock('latest'); // fast forward l2 time - await setNextBlockTimestamp(deploymentManager, eta.toNumber() + 1); + await setNextBlockTimestamp(deploymentManager, Math.max(eta.toNumber() + 1, blockNow.timestamp + 1)); // execute queued proposal await setNextBaseFeeToZero(deploymentManager); diff --git a/scenario/utils/relayArbitrumMessage.ts b/scenario/utils/relayArbitrumMessage.ts index b9cb75f34..4929b9533 100644 --- a/scenario/utils/relayArbitrumMessage.ts +++ b/scenario/utils/relayArbitrumMessage.ts @@ -4,7 +4,7 @@ import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; import { utils, BigNumber } from 'ethers'; import { Log } from '@ethersproject/abstract-provider'; -export default async function relayArbitrumMessage( +export async function relayArbitrumMessage( governanceDeploymentManager: DeploymentManager, bridgeDeploymentManager: DeploymentManager, startingBlockNumber: number @@ -111,3 +111,124 @@ export default async function relayArbitrumMessage( } } } + +export async function relayCCTPMint( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + startingBlockNumber: number +){ + // CCTP relay + // 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 depositForBurnEvents: Log[] = await governanceDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: startingBlockNumber, + toBlock: 'latest', + address: L1MessageTransmitter.address, + topics: [utils.id('MessageSent(bytes)')] + }); + + // Decode message body + const burnEvents = depositForBurnEvents.map(({ data }) => { + const dataBytes = utils.arrayify(data); + // Since data is encodePacked, so can't simply decode via AbiCoder.decode + const offset = 64; + const length = { + uint32: 4, + uint64: 8, + bytes32: 32, + uint256: 32, + }; + let start = offset; + let end = start + length.uint32; + // msgVersion, skip won't use + start = end; + end = start + length.uint32; + // msgSourceDomain + const msgSourceDomain = BigNumber.from(dataBytes.slice(start, end)).toNumber(); + + start = end; + end = start + length.uint32; + // msgDestinationDomain, skip won't use + + start = end; + end = start + length.uint64; + // msgNonce, skip won't use + + start = end; + end = start + length.bytes32; + // msgSender, skip won't use + + start = end; + end = start + length.bytes32; + // msgRecipient, skip won't use + + start = end; + end = start + length.bytes32; + // msgDestination, skip won't use + + start = end; + end = start + length.uint32; + // rawMsgBody version, skip won't use + + start = end; + end = start + length.bytes32; + // rawMsgBody burnToken + const burnToken = utils.hexlify(dataBytes.slice(start, end)); + + start = end; + end = start + length.bytes32; + // rawMsgBody mintRecipient + const mintRecipient = utils.getAddress(utils.hexlify(dataBytes.slice(start, end)).slice(-40)); + + start = end; + end = start + length.uint256; + + // rawMsgBody amount + const amount = BigNumber.from(dataBytes.slice(start, end)).toNumber(); + + start = end; + end = start + length.bytes32; + // rawMsgBody messageSender, skip won't use + + return { + recipient: mintRecipient, + amount: amount, + sourceDomain: msgSourceDomain, + burnToken: burnToken + }; + }); + + // Impersonate the Arbitrum TokenMinter and mint token to recipient + const ImpersonateLocalTokenMessenger = + bridgeDeploymentManager.network === 'arbitrum' ? '0x19330d10d9cc8751218eaf51e8885d058642e08a' : + bridgeDeploymentManager.network === 'arbitrum-goerli' ? '0x12dcfd3fe2e9eac2859fd1ed86d2ab8c5a2f9352' : + '0x0'; + // Impersonate the Arbitrum TokenMinter and mint token to recipient + for (let burnEvent of burnEvents) { + const { recipient, amount, sourceDomain, burnToken } = burnEvent; + const localTokenMessengerSigner = await impersonateAddress( + bridgeDeploymentManager, + ImpersonateLocalTokenMessenger + ); + + const transactionRequest = await localTokenMessengerSigner.populateTransaction({ + to: TokenMinter.address, + from: ImpersonateLocalTokenMessenger, + data: TokenMinter.interface.encodeFunctionData('mint', [sourceDomain, burnToken, utils.getAddress(recipient), amount]), + gasPrice: 0 + }); + + await setNextBaseFeeToZero(bridgeDeploymentManager); + + await ( + await localTokenMessengerSigner.sendTransaction(transactionRequest) + ).wait(); + } +} diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index 08bb3f933..0848cb1ba 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -1,6 +1,6 @@ import { DeploymentManager } from '../../plugins/deployment_manager'; import relayPolygonMessage from './relayPolygonMessage'; -import relayArbitrumMessage from './relayArbitrumMessage'; +import { relayArbitrumMessage, relayCCTPMint } from './relayArbitrumMessage'; import relayBaseMessage from './relayBaseMessage'; import relayLineaMessage from './relayLineaMessage'; @@ -34,6 +34,11 @@ export default async function relayMessage( bridgeDeploymentManager, startingBlockNumber ); + await relayCCTPMint( + governanceDeploymentManager, + bridgeDeploymentManager, + startingBlockNumber + ); break; case 'linea-goerli': await relayLineaMessage( diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index 5da732c4e..d073fb587 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -73,7 +73,8 @@ const liquidationThresholds = { 'usdc': 10e6 }, arbitrum: { - 'usdc': 10e6 + 'usdc.e': 10e6, + usdc: 10e6 } }; @@ -101,10 +102,14 @@ export const flashLoanPools = { } }, arbitrum: { - usdc: { + 'usdc.e': { tokenAddress: addresses.arbitrum.USDC, // USDC/USDC_E/.01% pool poolFee: 100 - } + }, + usdc: { + tokenAddress: addresses.arbitrum.USDC_E, + poolFee: 100 + }, } }; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 36d36a69e..bbcbf8f54 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -96,7 +96,8 @@ export const WHALES = { arbitrum: [ '0xf89d7b9c864f589bbf53a82105107622b35eaa40', // USDC whale '0x7b7b957c284c2c227c980d6e2f804311947b84d0', // WBTC whale - '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae' // COMP whale + '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae', // COMP whale + '0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9' // native USDC whale ], base: [ '0x6D3c5a4a7aC4B1428368310E4EC3bB1350d01455', // USDbC whale @@ -143,4 +144,4 @@ export async function proposal(actions: ProposalAction[], description: string): } } return [targets, values, signatures, calldatas, description]; -} +} \ No newline at end of file