From 55960cdd9dc32dee3dde8ed8ec72c8542b7750b5 Mon Sep 17 00:00:00 2001 From: Kevin Cheng Date: Sat, 2 Mar 2024 18:24:52 -0800 Subject: [PATCH] Sepolia cWETHv3 deploy and proposal (#832) Deploys cWETHv3 to Sepolia and initializes it with a proposal. --- .github/workflows/run-scenarios.yaml | 2 +- MIGRATIONS.md | 2 +- deployments/sepolia/weth/configuration.json | 44 ++++++ deployments/sepolia/weth/deploy.ts | 147 ++++++++++++++++++ .../1709094543_initialize_market.ts | 96 ++++++++++++ deployments/sepolia/weth/relations.ts | 13 ++ deployments/sepolia/weth/roots.json | 7 + hardhat.config.ts | 9 +- 8 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 deployments/sepolia/weth/configuration.json create mode 100644 deployments/sepolia/weth/deploy.ts create mode 100644 deployments/sepolia/weth/migrations/1709094543_initialize_market.ts create mode 100644 deployments/sepolia/weth/relations.ts create mode 100644 deployments/sepolia/weth/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 41ef95485..9e3a5c7b9 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, 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-goerli, base-goerli-weth, linea-goerli] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 0ba3921dd..702c8e297 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -59,7 +59,7 @@ After preparation, a migration stores some artifacts under `deployments/goerli/u Migrations can be tested using Comet's [scenario framework](https://github.com/compound-finance/comet/blob/main/SCENARIO.md). -Migrations that have been committed to a branch but not enacted yet will automatically be picked up and run by the scenarios framework (in the [MigrationConstraint](https://github.com/compound-finance/comet/blob/main/scenario/constraints/MigrationConstraint.ts)). This ensures that any new migrations are checked against all existing scenarios and any issues with a migration can be proactively caught. +Migrations that have been staged to a branch but not enacted yet will automatically be picked up and run by the scenarios framework (in the [MigrationConstraint](https://github.com/compound-finance/comet/blob/main/scenario/constraints/MigrationConstraint.ts)). This ensures that any new migrations are checked against all existing scenarios and any issues with a migration can be proactively caught. Remember, migrations **need to be staged in git** before it can be picked up by scenarios. Migrations should also include a `verify` function to check that the correct state-changes are made by it. This `verify` block is also run as part of the scenario framework. diff --git a/deployments/sepolia/weth/configuration.json b/deployments/sepolia/weth/configuration.json new file mode 100644 index 000000000..11f104aa9 --- /dev/null +++ b/deployments/sepolia/weth/configuration.json @@ -0,0 +1,44 @@ +{ + "name": "Compound WETH", + "symbol": "cWETHv3", + "baseToken": "WETH", + "baseTokenAddress": "0x2D5ee574e710219a521449679A4A7f2B43f046ad", + "borrowMin": "1e12", + "governor": "0x54a06047087927D9B0fb21c1cf0ebd792764dDB8", + "pauseGuardian": "0x008a4C5448ac1Df676d6F39A0C6F13b21b189389", + "storeFrontPriceFactor": 0.5, + "targetReserves": "5000e18", + "rates": { + "supplyKink": 0.9, + "supplySlopeLow": 0.01690681444, + "supplySlopeHigh": 0.6066567706, + "supplyBase": 0, + "borrowKink": 0.9, + "borrowSlopeLow": 0.05171500002, + "borrowSlopeHigh": 0.5171500339, + "borrowBase": 0.009945209674 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "0.000011574074074074073e15", + "baseBorrowSpeed": "0e15", + "baseMinForRewards": "0.1e18" + }, + "assets": { + "cbETH": { + "decimals": "18", + "borrowCF": 0.90, + "liquidateCF": 0.93, + "liquidationFactor": 0.95, + "supplyCap": "9_000e18" + }, + "wstETH": { + "address": "0xB82381A3fBD3FaFA77B3a7bE693342618240067b", + "decimals": "18", + "borrowCF": 0.90, + "liquidateCF": 0.93, + "liquidationFactor": 0.95, + "supplyCap": "80_000e18" + } + } +} \ No newline at end of file diff --git a/deployments/sepolia/weth/deploy.ts b/deployments/sepolia/weth/deploy.ts new file mode 100644 index 000000000..e7a0d15cb --- /dev/null +++ b/deployments/sepolia/weth/deploy.ts @@ -0,0 +1,147 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { debug, DeploySpec, deployComet, exp, sameAddress, wait } from '../../../src/deploy'; + +const clone = { + cbETHImpl: '0x31724cA0C982A31fbb5C57f4217AB585271fc9a5', + cbETHProxy: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', +}; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const deployed = await deployContracts(deploymentManager, deploySpec); + await mintTokens(deploymentManager); + return deployed; +} + +async function deployContracts(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const ethers = deploymentManager.hre.ethers; + const signer = await deploymentManager.getSigner(); + + // Declare existing assets as aliases + const WETH = await deploymentManager.existing('WETH', '0x2D5ee574e710219a521449679A4A7f2B43f046ad', 'sepolia'); + const wstETH = await deploymentManager.existing('wstETH', '0xB82381A3fBD3FaFA77B3a7bE693342618240067b', 'sepolia'); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'sepolia', 'usdc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'sepolia', 'usdc'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'sepolia', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'sepolia', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'sepolia', 'usdc'); + const fauceteer = await deploymentManager.fromDep('fauceteer', 'sepolia', 'usdc'); + + // Clone cbETH + const cbETHProxyAdmin = await deploymentManager.deploy('cbETH:admin', 'vendor/proxy/transparent/ProxyAdmin.sol', []); + const cbETHImpl = await deploymentManager.clone('cbETH:implementation', clone.cbETHImpl, []); + const cbETHProxy = await deploymentManager.clone('cbETH', clone.cbETHProxy, [cbETHImpl.address]); + const cbETHProxyAdminSlot = '0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b'; + const cbETH = cbETHImpl.attach(cbETHProxy.address); + await deploymentManager.idempotent( + async () => !sameAddress(await ethers.provider.getStorageAt(cbETHProxy.address, cbETHProxyAdminSlot), cbETHProxyAdmin.address), + async () => { + debug(`Changing admin of cbETH proxy to ${cbETHProxyAdmin.address}`); + await wait(cbETHProxy.connect(signer).changeAdmin(cbETHProxyAdmin.address)); + + debug(`Initializing cbETH`); + await wait(cbETH.connect(signer).initialize( + 'Coinbase Wrapped Staked ETH', // name + 'cbETH', // symbol + '', // currency + 18, // decimals + signer.address, // Master Minter + signer.address, // Pauser + signer.address, // Blacklister + signer.address // Owner + )); + } + ); + + // Deploy stETH / ETH SimplePriceFeed + const stETHtoETHPriceFeed = await deploymentManager.deploy( + 'stETHToETH:simplePriceFeed', + 'test/SimplePriceFeed.sol', + [ + exp(0.98882408, 18), // Latest answer on mainnet at block 16170924 + 18 + ] + ); + + // Deploy cbETH / ETH SimplePriceFeed + const cbETHtoETHPriceFeed = await deploymentManager.deploy( + 'cbETHToETH:simplePriceFeed', + 'test/SimplePriceFeed.sol', + [ + exp(0.97, 18), + 18 + ] + ); + + // Deploy WstETHPriceFeed + const wstETHPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/WstETHPriceFeed.sol', + [ + stETHtoETHPriceFeed.address, // stETH / ETH price feed + wstETH.address, // wstETH + 8 // decimals + ] + ); + + // Deploy constant price feed for WETH + const wethConstantPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ConstantPriceFeed.sol', + [ + 8, // decimals + exp(1, 8) // constantPrice + ] + ); + + // Deploy scaling price feed for cbETH + const cbETHScalingPriceFeed = await deploymentManager.deploy( + 'cbETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + cbETHtoETHPriceFeed.address, // cbETH / ETH price feed + 8 // decimals + ] + ); + + // Deploy all Comet-related contracts + const deployed = await deployComet(deploymentManager, deploySpec); + const { comet } = deployed; + + // Deploy Bulker + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/MainnetBulker.sol', + [ + await comet.governor(), // admin_ + WETH.address, // weth_ + wstETH.address // wsteth_ + ] + ); + + return { ...deployed, bulker, fauceteer }; +} + +async function mintTokens(deploymentManager: DeploymentManager) { + const signer = await deploymentManager.getSigner(); + const contracts = await deploymentManager.contracts(); + const fauceteer = contracts.get('fauceteer')!; + + debug(`Attempting to mint as ${signer.address}...`); + + // If we haven't spidered new contracts (which we could before minting, but its slow), + // then the proxy contract won't have the impl functions yet, so just do it explicitly + const cbETHProxy = contracts.get('cbETH')!, cbETHImpl = contracts.get('cbETH:implementation')!; + const cbETH = cbETHImpl.attach(cbETHProxy.address); + await deploymentManager.idempotent( + async () => (await cbETH.balanceOf(fauceteer.address)).eq(0), + async () => { + debug(`Minting 1M cbETH to fauceteer`); + const amount = exp(1_000_000, await cbETH.decimals()); + await wait(cbETH.connect(signer).configureMinter(signer.address, amount)); + await wait(cbETH.connect(signer).mint(fauceteer.address, amount)); + debug(`cbETH.balanceOf(${fauceteer.address}): ${await cbETH.balanceOf(fauceteer.address)}`); + } + ); +} diff --git a/deployments/sepolia/weth/migrations/1709094543_initialize_market.ts b/deployments/sepolia/weth/migrations/1709094543_initialize_market.ts new file mode 100644 index 000000000..99515b5f3 --- /dev/null +++ b/deployments/sepolia/weth/migrations/1709094543_initialize_market.ts @@ -0,0 +1,96 @@ +import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; +import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; + +import { expect } from 'chai'; + +const COMPAddress = '0xA6c8D1c55951e8AC44a0EaA959Be5Fd21cc07531'; + +export default migration('1709094543_initialize_market', { + prepare: async (deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + + // Import shared contracts from cUSDCv3 + const cometFactory = await deploymentManager.fromDep('cometFactory', 'sepolia', 'usdc'); + + const { + governor, + comet, + configurator, + cometAdmin, + rewards, + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + + const actions = [ + // 1. Set the factory in the Configurator + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactory.address], + }, + + // 2. Set the configuration in the Configurator + { + contract: configurator, + signature: '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)[]))', + args: [comet.address, configuration], + }, + + // 3. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: "deployAndUpgradeTo(address,address)", + args: [configurator.address, comet.address], + }, + + // 4. Set the rewards configuration to COMP + { + contract: rewards, + signature: "setRewardConfig(address,address)", + args: [comet.address, COMPAddress], + }, + ]; + const description = "# Initialize cWETHv3 on Sepolia" + const txn = await deploymentManager.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) { + const { + comet, + rewards, + wstETH, + cbETH, + } = await deploymentManager.getContracts(); + // 2. & 3. + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(1 / 86400, 15, 18)); // ~ 1 COMP / day + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(0); + + const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); + expect(wstETHInfo.supplyCap).to.be.equal(exp(80_000, 18)); // ~ $100M / $1225 + + const cbETHInfo = await comet.getAssetInfoByAddress(cbETH.address); + expect(cbETHInfo.supplyCap).to.be.equal(exp(9_000, 18)); // ~ $10M / $1091 + + // 4. + const config = await rewards.rewardConfig(comet.address); + expect(config.token).to.be.equal(COMPAddress); + expect(config.rescaleFactor).to.be.equal(1000000000000n); + expect(config.shouldUpscale).to.be.equal(true); + }, +}); \ No newline at end of file diff --git a/deployments/sepolia/weth/relations.ts b/deployments/sepolia/weth/relations.ts new file mode 100644 index 000000000..13f315c2a --- /dev/null +++ b/deployments/sepolia/weth/relations.ts @@ -0,0 +1,13 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + 'wstETH': { + artifact: 'contracts/bulkers/IWstETH.sol', + relations: { + stETH: { + field: async (wstETH) => wstETH.stETH() + } + } + }, +}; diff --git a/deployments/sepolia/weth/roots.json b/deployments/sepolia/weth/roots.json new file mode 100644 index 000000000..825b5a431 --- /dev/null +++ b/deployments/sepolia/weth/roots.json @@ -0,0 +1,7 @@ +{ + "comet": "0x2943ac1216979aD8dB76D9147F64E61adc126e96", + "configurator": "0xc28aD44975C614EaBe0Ed090207314549e1c6624", + "rewards": "0x8bF5b658bdF0388E8b482ED51B14aef58f90abfD", + "bulker": "0xaD0C044425D81a2E223f4CE699156900fead2Aaa", + "fauceteer": "0x68793eA49297eB75DFB4610B68e076D2A5c7646C" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index a08c0b085..5b5ac4256 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -21,6 +21,7 @@ import relationConfigMap from './deployments/relations'; import goerliRelationConfigMap from './deployments/goerli/usdc/relations'; import goerliWethRelationConfigMap from './deployments/goerli/weth/relations'; import sepoliaUsdcRelationConfigMap from './deployments/sepolia/usdc/relations'; +import sepoliaWethRelationConfigMap from './deployments/sepolia/weth/relations'; import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; @@ -295,7 +296,8 @@ const config: HardhatUserConfig = { weth: goerliWethRelationConfigMap }, sepolia: { - usdc: sepoliaUsdcRelationConfigMap + usdc: sepoliaUsdcRelationConfigMap, + weth: sepoliaWethRelationConfigMap }, mumbai: { usdc: mumbaiRelationConfigMap @@ -367,6 +369,11 @@ const config: HardhatUserConfig = { network: 'sepolia', deployment: 'usdc' }, + { + name: 'sepolia-weth', + network: 'sepolia', + deployment: 'weth' + }, { name: 'mumbai', network: 'mumbai',