diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 9e3a5c7b9..97237fe13 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-goerli, base-goerli-weth, linea-goerli] + 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] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/base/usdc/configuration.json b/deployments/base/usdc/configuration.json new file mode 100644 index 000000000..a063b1d99 --- /dev/null +++ b/deployments/base/usdc/configuration.json @@ -0,0 +1,47 @@ +{ + "name": "Compound USDC", + "symbol": "cUSDCv3", + "baseToken": "USDC", + "baseTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "baseTokenPriceFeed": "0x7e860098F58bBFC8648a4311b374B1D669a2bc6B", + "borrowMin": "1e0", + "pauseGuardian": "0x3cb4653f3b45f448d9100b118b75a1503281d2ee", + "storeFrontPriceFactor": 0.6, + "targetReserves": "5000000e6", + "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": "231_481_481_481e0", + "baseBorrowSpeed": "92_592_592_592e0", + "baseMinForRewards": "1000e6" + }, + "assets": { + "cbETH": { + "address": "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", + "priceFeed": "0x4687670f5f01716fAA382E2356C103BaD776752C", + "decimals": "18", + "borrowCF": 0.75, + "liquidateCF": 0.8, + "liquidationFactor": 0.85, + "supplyCap": "7500e18" + }, + "WETH": { + "address": "0x4200000000000000000000000000000000000006", + "priceFeed": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", + "decimals": "18", + "borrowCF": 0.80, + "liquidateCF": 0.90, + "liquidationFactor": 0.95, + "supplyCap": "11000e18" + } + } +} diff --git a/deployments/base/usdc/deploy.ts b/deployments/base/usdc/deploy.ts new file mode 100644 index 000000000..ab911fc49 --- /dev/null +++ b/deployments/base/usdc/deploy.ts @@ -0,0 +1,42 @@ +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 { + // Pull in existing assets + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'base', 'usdbc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'base', 'usdbc'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'base', 'usdbc'); + const configurator = await deploymentManager.fromDep('configurator', 'base', 'usdbc'); + const rewards = await deploymentManager.fromDep('rewards', 'base', 'usdbc'); + const bulker = await deploymentManager.fromDep('bulker', 'base', 'usdbc'); + const l2CrossDomainMessenger = await deploymentManager.fromDep('l2CrossDomainMessenger', 'base', 'usdbc'); + const l2StandardBridge = await deploymentManager.fromDep('l2StandardBridge', 'base', 'usdbc'); + const localTimelock = await deploymentManager.fromDep('timelock', 'base', 'usdbc'); + const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'base', 'usdbc'); + const cbETHMultiplicativePriceFeed = await deploymentManager.fromDep('cbETH:priceFeed','base', 'usdbc'); + + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bridgeReceiver, + l2CrossDomainMessenger, // TODO: don't have to part of roots. can be pulled via relations + l2StandardBridge, + bulker, + }; +} diff --git a/deployments/base/usdc/migrations/1689892563_configurate_and_ens.ts b/deployments/base/usdc/migrations/1689892563_configurate_and_ens.ts new file mode 100644 index 000000000..f6896d220 --- /dev/null +++ b/deployments/base/usdc/migrations/1689892563_configurate_and_ens.ts @@ -0,0 +1,205 @@ +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 baseCOMPAddress = '0x9e1028F5F1D5eDE59748FFceE5532509976840E0'; + +export default migration('1689892563_configurate_and_ens', { + prepare: async (deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + const { utils } = ethers; + + const cometFactory = await deploymentManager.fromDep('cometFactory', 'base', 'usdbc'); + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + rewards + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor, + COMP: mainnetCOMP + } = 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 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 setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactory.address) + ); + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration(comet.address, configuration) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, baseCOMPAddress] + ); + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address, rewards.address], + [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)' + ], + [setFactoryCalldata, setConfigurationCalldata, deployAndUpgradeToCalldata, setRewardConfigCalldata] + ] + ); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Base. + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 2_500_000] + }, + + // 2. 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)] + ) + }, + + // 3. Send 108 COMP to arr00 + { + contract: mainnetCOMP, + signature: 'transfer(address,uint256)', + args: ['0x2B384212EDc04Ae8bB41738D05BA20E33277bf33', exp(115, 18)] + } + ]; + + const description = '# Initialize cUSDCv3 on Base\n\nThis proposal initializes the Compound III USDC market on Base as a first step to fully migrating to native USDC on base. The entirety of this proposal was simulated and indicates success. The [parameters for this deployment](https://www.comp.xyz/t/gauntlet-usdc-native-comet-market-to-compound-base-02-09-24/4982) and the [rewards configuration](https://www.comp.xyz/t/gauntlet-native-usdc-base-comet-incentives-recommendations/5009) were recommended by Gauntlet.\n\nSee links for more details:\n- [proposal pull request](https://github.com/compound-finance/comet/pull/828)\n- [forum discussion](https://www.comp.xyz/t/gauntlet-usdc-native-comet-market-to-compound-base-02-09-24/4982)\n\n## Proposal Actions\n1) The first proposal action sets the configurator factory address, sets Comet configuration, and deploys a new Comet implementation on Base. It also sets the rewards config for the new market to 20 COMP/Day for suppliers and 8 COMP/Day for borrowers.\n2) The second 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 Base cUSDCv3 market.\n3) The third action pays arr00 115 COMP for the development effort for this proposal.'; + 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 Base COMP now that reward config has been set + + const { + comet, + rewards, + COMP + } = await deploymentManager.getContracts(); + + // 1. + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + expect(stateChanges).to.deep.equal({ + baseTrackingSupplySpeed: exp(20 / 86400, 15, 18), + baseTrackingBorrowSpeed: exp(8 / 86400, 15, 18), + WETH: { + supplyCap: exp(11000, 18) + }, + cbETH: { + supplyCap: exp(7500, 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. + 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', + }, + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress:'0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + } + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: comet.address, + } + ], + }); + + // 20 comp per day to suppliers + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(20 / 86400, 15, 18)); + // 8 comp per day to borrowers + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(8 / 86400, 15, 18)); + } +}); \ No newline at end of file diff --git a/deployments/base/usdc/relations.ts b/deployments/base/usdc/relations.ts new file mode 100644 index 000000000..31819114d --- /dev/null +++ b/deployments/base/usdc/relations.ts @@ -0,0 +1,28 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver' + }, + + Proxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + l2StandardBridge: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + } +}; \ No newline at end of file diff --git a/deployments/base/usdc/roots.json b/deployments/base/usdc/roots.json new file mode 100644 index 000000000..8cbb34276 --- /dev/null +++ b/deployments/base/usdc/roots.json @@ -0,0 +1,9 @@ +{ + "comet": "0xb125E6687d4313864e53df431d5425969c15Eb2F", + "configurator": "0x45939657d1CA34A8FA39A924B71D28Fe8431e581", + "rewards": "0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1", + "bridgeReceiver": "0x18281dfC4d00905DA1aaA6731414EABa843c468A", + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "bulker": "0x78D0677032A35c63D142a48A2037048871212a8C" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 5b5ac4256..41900ebdd 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -32,6 +32,7 @@ import arbitrumBridgedUsdcGoerliRelationConfigMap from './deployments/arbitrum-g import arbitrumGoerliNativeUsdcRelationConfigMap from './deployments/arbitrum-goerli/usdc/relations'; import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations'; import baseWethRelationConfigMap from './deployments/base/weth/relations'; +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'; @@ -319,7 +320,8 @@ const config: HardhatUserConfig = { }, 'base': { usdbc: baseUsdbcRelationConfigMap, - weth: baseWethRelationConfigMap + weth: baseWethRelationConfigMap, + usdc: baseUsdcRelationConfigMap }, 'base-goerli': { usdc: baseGoerliRelationConfigMap, @@ -422,6 +424,12 @@ const config: HardhatUserConfig = { deployment: 'weth', auxiliaryBase: 'mainnet' }, + { + name: 'base-usdc', + network: 'base', + deployment: 'usdc', + auxiliaryBase: 'mainnet' + }, { name: 'base-goerli', network: 'base-goerli',