diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 9e15d956b..2bda4668c 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -55,15 +55,19 @@ jobs: esac - name: Seacrest - uses: hayesgm/seacrest@v1 + uses: hayesgm/seacrest@v2 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-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' - name: Seacrest (governance network) - uses: hayesgm/seacrest@v1 + uses: hayesgm/seacrest@v2 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\",\"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 != '' diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index a0e1832d2..76ff21314 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -38,8 +38,10 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} steps: - name: Seacrest - uses: hayesgm/seacrest@v1 + uses: hayesgm/seacrest@v2 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-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' diff --git a/deployments/arbitrum-goerli/usdc/configuration.json b/deployments/arbitrum-goerli/usdc/configuration.json index 1d46f5381..fda04e2ac 100644 --- a/deployments/arbitrum-goerli/usdc/configuration.json +++ b/deployments/arbitrum-goerli/usdc/configuration.json @@ -20,8 +20,8 @@ }, "tracking": { "indexScale": "1e15", - "baseSupplySpeed": "0e15", - "baseBorrowSpeed": "0e15", + "baseSupplySpeed": "0.000402083333333e15", + "baseBorrowSpeed": "0.000402083333333e15", "baseMinForRewards": "10000e6" }, "assets": { @@ -32,7 +32,7 @@ "borrowCF": 0.775, "liquidateCF": 0.825, "liquidationFactor": 0.95, - "supplyCap": "0e18" + "supplyCap": "5000000e18" }, "WETH": { "address": "0xe39ab88f8a4777030a534146a9ca3b52bd5d43a3", @@ -41,7 +41,7 @@ "borrowCF": 0.775, "liquidateCF": 0.825, "liquidationFactor": 0.95, - "supplyCap": "0e18" + "supplyCap": "5000e18" }, "WBTC": { "address": "0x22d5e2dE578677791f6c90e0110Ec629be9d5Fb5", @@ -50,7 +50,7 @@ "borrowCF": 0.7, "liquidateCF": 0.75, "liquidationFactor": 0.93, - "supplyCap": "0e8" + "supplyCap": "300e8" } } } diff --git a/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts b/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts new file mode 100644 index 000000000..4ba4b9935 --- /dev/null +++ b/deployments/arbitrum-goerli/usdc/migrations/1689112067_configurate_and_ens.ts @@ -0,0 +1,283 @@ +import { Contract } from 'ethers'; +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; +import { applyL1ToL2Alias, estimateL2Transaction, estimateTokenBridge } from '../../../../scenario/utils/arbitrumUtils'; + +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const arbitrumCOMPAddress = '0xf03370d2aCf26Dde26389B66498B7c293038F5aF'; + +export default migration('1689112067_configurate_and_ens', { + prepare: async (deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + const { utils } = ethers; + + const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum-goerli', 'usdc.e'); + const usdceComet = await deploymentManager.fromDep('usdceComet', 'arbitrum-goerli', 'usdc.e', 'comet'); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator, + rewards, + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + arbitrumL1GatewayRouter, + timelock, + governor, + USDC, + COMP, + CCTPTokenMessenger, + } = await govDeploymentManager.getContracts(); + + // CCTP destination domain for Arbitrum + const ArbitrumDestinationDomain = 3; + const USDCAmountToBridge = exp(10, 6); + const refundAddress = l2Timelock.address; + const configuration = await getConfigurationStruct(deploymentManager); + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactory.address) + ); + + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration(comet.address, configuration) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, arbitrumCOMPAddress] + ); + + const turnOffUSDCeCometSupplySpeedCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint64'], + [usdceComet.address, 0] + ); + + const turnOffUSDCeCometBorrowSpeedCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint64'], + [usdceComet.address, 0] + ); + + const deployAndUpgradeToUSDCeCometCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, usdceComet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address, rewards.address, configurator.address, configurator.address, cometAdmin.address], + [0, 0, 0, 0, 0, 0, 0], + [ + 'setFactory(address,address)', + 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', + 'deployAndUpgradeTo(address,address)', + 'setRewardConfig(address,address)', + 'setBaseTrackingSupplySpeed(address,uint64)', + 'setBaseTrackingBorrowSpeed(address,uint64)', + 'deployAndUpgradeTo(address,address)', + ], + [setFactoryCalldata, setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata, turnOffUSDCeCometSupplySpeedCalldata, turnOffUSDCeCometBorrowSpeedCalldata, deployAndUpgradeToUSDCeCometCalldata] + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + + const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli'); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const arbitrumChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString(); + const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + // Rename old USDC market into USDC.e + officialMarketsJSON[arbitrumChainId][0].baseSymbol = 'USDC.e'; + + if (officialMarketsJSON[arbitrumChainId]) { + officialMarketsJSON[arbitrumChainId].push(newMarketObject); + } else { + officialMarketsJSON[arbitrumChainId] = [newMarketObject]; + } + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + // 2. Approve USDC to CCTP + { + contract: USDC, + signature: 'approve(address,uint256)', + args: [CCTPTokenMessenger.address, USDCAmountToBridge] + }, + // 3. Burn USDC to Arbitrum via CCTP + { + contract: CCTPTokenMessenger, + signature: 'depositForBurn(uint256,uint32,bytes32,address)', + args: [USDCAmountToBridge, ArbitrumDestinationDomain, utils.hexZeroPad(comet.address, 32), USDC.address], + }, + // 4. Update the list of official markets + { + target: ENSResolverAddress, + signature: 'setText(bytes32,string,string)', + calldata: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'string', 'string'], + [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] + ) + } + ]; + + // TODO: Will update this description to be more accurate once the contract is deployed + const description = "# Configurate Arbitrum cUSDCv3 market for Native USDC native, and set ENS record for official markets"; + const txn = await govDeploymentManager.retry(async () => + trace(await governor.propose(...(await proposal(mainnetActions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return true; + }, + + async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { + const ethers = deploymentManager.hre.ethers; + await deploymentManager.spider(); // Pull in Arbitrum COMP now that reward config has been set + const usdceComet = await deploymentManager.fromDep('usdceComet', 'arbitrum-goerli', 'usdc.e', 'comet'); + const { + comet, + rewards, + } = await deploymentManager.getContracts(); + + const config = await rewards.rewardConfig(comet.address); + + // 1. Verify state changes + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + expect(stateChanges).to.deep.equal({ + LINK: { + supplyCap: exp(5_000_000, 18) + }, + WETH: { + supplyCap: exp(5_000, 18) + }, + WBTC: { + supplyCap: exp(300, 8) + }, + baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18), + baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18), + }); + + expect(config.token).to.be.equal(arbitrumCOMPAddress); + expect(config.rescaleFactor).to.be.equal(exp(1, 12)); + expect(config.shouldUpscale).to.be.equal(true); + // Ensure proposal has set usdce market to 0 + expect(await usdceComet.baseTrackingSupplySpeed()).to.be.equal(0); + expect(await usdceComet.baseTrackingBorrowSpeed()).to.be.equal(0); + + // 2. & 3. Verify the seeded USDC reaches Comet reserve + expect(await comet.getReserves()).to.be.equal(exp(10, 6)); + + // 4. Verify the official markets are updated + const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); + const officialMarkets = JSON.parse(officialMarketsJSON); + + expect(officialMarkets).to.deep.equal({ + 5: [ + { + baseSymbol: 'USDC', + cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F', + }, + ], + + 420: [ + { + baseSymbol: 'USDC', + cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4' + } + ], + + 421613: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456', + }, + { + baseSymbol: 'USDC', + cometAddress: comet.address + }, + ], + + 59140: [ + { + baseSymbol: 'USDC', + cometAddress: '0xa84b24A43ba1890A165f94Ad13d0196E5fD1023a' + } + ], + + 84531: [ + { + baseSymbol: 'USDC', + cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1' + }, + { + baseSymbol: 'WETH', + cometAddress: '0xED94f3052638620fE226a9661ead6a39C2a265bE' + } + ], + + 80001: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839' + }, + ] + }); + } +}); diff --git a/deployments/arbitrum-goerli/usdc/roots.json b/deployments/arbitrum-goerli/usdc/roots.json index d820ab653..6f3a59a8c 100644 --- a/deployments/arbitrum-goerli/usdc/roots.json +++ b/deployments/arbitrum-goerli/usdc/roots.json @@ -3,5 +3,6 @@ "configurator": "0x1Ead344570F0f0a0cD86d95d8adDC7855C8723Fb", "rewards": "0x8DA65F8E3Aa22A498211fc4204C498ae9050DAE4", "bridgeReceiver": "0xAC9fC1a9532BC92a9f33eD4c6Ce4A7a54930F376", - "bulker": "0x987350Af5a17b6DdafeB95E6e329c178f44841d7" + "bulker": "0x987350Af5a17b6DdafeB95E6e329c178f44841d7", + "CCTPMessageTransmitter": "0x109bc137cb64eab7c0b1dddd1edf341467dc2d35" } \ No newline at end of file diff --git a/deployments/goerli/usdc/relations.ts b/deployments/goerli/usdc/relations.ts index ef197000d..f9edfd59b 100644 --- a/deployments/goerli/usdc/relations.ts +++ b/deployments/goerli/usdc/relations.ts @@ -43,11 +43,12 @@ export default { } }, lineaMessageService: { - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } + artifact: 'contracts/bridges/linea/IMessageService.sol:IMessageService', + // delegates: { + // field: { + // slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + // } + // } }, lineaL1TokenBridge: { delegates: { diff --git a/scripts/CCTP-attestation.ts b/scripts/CCTP-attestation.ts new file mode 100644 index 000000000..dd82b0f5b --- /dev/null +++ b/scripts/CCTP-attestation.ts @@ -0,0 +1,65 @@ +/* + A script to help check if CCTP's attestation server to acquire signature to mint native USDC on arbitrum + Example: + DEPLOYMENT=usdc BURN_TXN_HASH= SOURCE_NETWORK=goerli DEST_NETWORK=arbitrum-goerli ETH_PK= npx hardhat run scripts/CCTP-attestation.ts +*/ +import hre from 'hardhat'; +import { DeploymentManager } from '../plugins/deployment_manager/DeploymentManager'; +import { requireEnv } from '../hardhat.config'; + +async function main() { + const DEPLOYMENT = requireEnv('DEPLOYMENT'); + const BURN_TXN_HASH = requireEnv('BURN_TXN_HASH'); + const SOURCE_NETWORK = requireEnv('SOURCE_NETWORK'); + const DEST_NETWORK = requireEnv('DEST_NETWORK'); + await hre.changeNetwork(SOURCE_NETWORK); + const src_dm = new DeploymentManager(SOURCE_NETWORK, DEPLOYMENT, hre, { + writeCacheToDisk: true + }); + + const circleAttestationApiHost = SOURCE_NETWORK === 'mainnet' ? 'https://iris-api.circle.com' : 'https://iris-api-sandbox.circle.com'; + const transactionReceipt = await src_dm.hre.ethers.provider.getTransactionReceipt(BURN_TXN_HASH); + const eventTopic = src_dm.hre.ethers.utils.id('MessageSent(bytes)'); + const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic); + const messageBytes = src_dm.hre.ethers.utils.defaultAbiCoder.decode(['bytes'], log.data)[0]; + const messageHash = src_dm.hre.ethers.utils.keccak256(messageBytes); + console.log(`Message hash: ${messageHash}`); + let attestationResponse = { status: 'pending', attestation: ''}; + while (attestationResponse.status != 'complete') { + console.log(`Polling... ${circleAttestationApiHost}/attestations/${messageHash}`); + const response = await fetch(`${circleAttestationApiHost}/attestations/${messageHash}`); + attestationResponse = await response.json(); + console.log(`Response: ${JSON.stringify(attestationResponse)}`); + await new Promise(r => setTimeout(r, 2000)); + } + + console.log(`Attestation complete, proceeding to mint native usdc on ${DEST_NETWORK}:`); + console.log(`------Parameters value------`); + console.log(`receivingMessageBytes: ${messageBytes}`); + console.log(`signature: ${attestationResponse.attestation}`); + console.log(`----------------------------`); + await hre.changeNetwork(DEST_NETWORK); + const dest_dm = new DeploymentManager(DEST_NETWORK, DEPLOYMENT, hre, { + writeCacheToDisk: true + }); + + const CCTPMessageTransmitter = await dest_dm.getContractOrThrow('CCTPMessageTransmitter'); + const signer = await dest_dm.getSigner(); + const transactionRequest = await signer.populateTransaction({ + to: CCTPMessageTransmitter.address, + from: signer.address, + data: CCTPMessageTransmitter.interface.encodeFunctionData('receiveMessage', [messageBytes, attestationResponse.attestation]), + gasPrice: Math.ceil(1.3 * (await hre.ethers.provider.getGasPrice()).toNumber()) + }); + + const mintTxn = await signer.sendTransaction(transactionRequest); + + console.log(`Mint completed, transaction hash: ${mintTxn.hash}`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + });