From 13742338c09b64cde7a372ea728d1dfb62222c1d Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 10 Sep 2024 20:27:15 +0300 Subject: [PATCH 01/22] Add ezETH as collateral to WETH market on Optimism (#905) Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .../1723475862_add_ezeth_as_collateral.ts | 150 ++++++++++++++++++ deployments/optimism/weth/relations.ts | 9 ++ scenario/constraints/ProposalConstraint.ts | 8 +- 3 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 deployments/optimism/weth/migrations/1723475862_add_ezeth_as_collateral.ts diff --git a/deployments/optimism/weth/migrations/1723475862_add_ezeth_as_collateral.ts b/deployments/optimism/weth/migrations/1723475862_add_ezeth_as_collateral.ts new file mode 100644 index 000000000..19b4078c2 --- /dev/null +++ b/deployments/optimism/weth/migrations/1723475862_add_ezeth_as_collateral.ts @@ -0,0 +1,150 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const EZETH_ADDRESS = '0x2416092f143378750bb29b79eD961ab195CcEea5'; +const EZETH_ETH_PRICE_FEED_ADDRESS = '0xFAD40C0e2BeF93c6a822015863045CAAeAAde4d3'; +let newPriceFeedAddress: string; + +export default migration('1723475862_add_ezeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _ezETHScalingPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + EZETH_ETH_PRICE_FEED_ADDRESS, // ezETH / ETH price feed + 8 // decimals + ] + ); + return { ezETHPriceFeedAddress: _ezETHScalingPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { ezETHPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'optimism', + 'contracts/ERC20.sol:ERC20' + ); + const ezETHPriceFeed = await deploymentManager.existing( + 'ezETH:priceFeed', + ezETHPriceFeedAddress, + 'optimism' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: ezETH.address, + priceFeed: ezETHPriceFeed.address, + decimals: await ezETH.decimals(), + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.94, 18), + supplyCap: exp(400, 18), + }; + + newPriceFeedAddress = ezETHPriceFeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add ezETH as collateral into cWETHv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ezETH into cWETHv3 on Optimism network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Optimism. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404/11).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/905) and [forum discussion](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404).\n\n\n## Proposal Actions\n\nThe first proposal action adds ezETH to the WETH Comet on Optimism. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const ezETHAssetIndex = Number(await comet.numAssets()) - 1; + + const ezETHAssetConfig = { + asset: EZETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.94, 18), + supplyCap: exp(400, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const ezETHAssetInfo = await comet.getAssetInfoByAddress( + EZETH_ADDRESS + ); + expect(ezETHAssetIndex).to.be.equal(ezETHAssetInfo.offset); + expect(ezETHAssetConfig.asset).to.be.equal(ezETHAssetInfo.asset); + expect(ezETHAssetConfig.priceFeed).to.be.equal(ezETHAssetInfo.priceFeed); + expect(exp(1, ezETHAssetConfig.decimals)).to.be.equal(ezETHAssetInfo.scale); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(ezETHAssetInfo.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(ezETHAssetInfo.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(ezETHAssetInfo.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(ezETHAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorEzETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[ezETHAssetIndex]; + expect(ezETHAssetConfig.asset).to.be.equal(configuratorEzETHAssetConfig.asset); + expect(ezETHAssetConfig.priceFeed).to.be.equal(configuratorEzETHAssetConfig.priceFeed); + expect(ezETHAssetConfig.decimals).to.be.equal(configuratorEzETHAssetConfig.decimals); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(configuratorEzETHAssetConfig.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(configuratorEzETHAssetConfig.supplyCap); + }, +}); diff --git a/deployments/optimism/weth/relations.ts b/deployments/optimism/weth/relations.ts index 0b4c85f7b..45457356a 100644 --- a/deployments/optimism/weth/relations.ts +++ b/deployments/optimism/weth/relations.ts @@ -43,6 +43,15 @@ export default { } }, + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // WBTC WBTC: { artifact: 'contracts/ERC20.sol:ERC20' diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index f1b9fa204..83c309740 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,12 +62,6 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 281 - if (proposal.id.eq(281)) { - console.log('Skipping proposal 281'); - continue; - } - try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); @@ -126,4 +120,4 @@ export class ProposalConstraint implements StaticConstra async check() { return; // XXX } -} \ No newline at end of file +} From d8b75c6b7bd13425b067b5ee3a04a5db106cc346 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 10 Sep 2024 20:27:33 +0300 Subject: [PATCH 02/22] Add ezETH as collateral to WETH market on Arbitrum (#907) Co-authored-by: GitHub Actions Bot <> --- .../1723541797_add_ezeth_as_collateral.ts | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 deployments/arbitrum/weth/migrations/1723541797_add_ezeth_as_collateral.ts diff --git a/deployments/arbitrum/weth/migrations/1723541797_add_ezeth_as_collateral.ts b/deployments/arbitrum/weth/migrations/1723541797_add_ezeth_as_collateral.ts new file mode 100644 index 000000000..55cb15048 --- /dev/null +++ b/deployments/arbitrum/weth/migrations/1723541797_add_ezeth_as_collateral.ts @@ -0,0 +1,195 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; +import { ethers } from 'ethers'; + +const EZETH_ADDRESS = '0x2416092f143378750bb29b79eD961ab195CcEea5'; +const EZETH_PRICE_FEED_ADDRESS = '0x989a480b6054389075CBCdC385C18CfB6FC08186'; + +let newPriceFeed: string; + +export default migration('1723541797_add_ezeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _ezETHScalingPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + EZETH_PRICE_FEED_ADDRESS, // ezETH / ETH price feed + 8 // decimals + ] + ); + + return { ezETHScalingPriceFeed: _ezETHScalingPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { ezETHScalingPriceFeed }) => { + const trace = deploymentManager.tracer(); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newPriceFeed = ezETHScalingPriceFeed; + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const ezETHPriceFeed = await deploymentManager.existing( + 'ezETH:priceFeed', + ezETHScalingPriceFeed, + 'arbitrum' + ); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: ezETHPriceFeed.address, + decimals: 18n, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.94, 18), + supplyCap: exp(2500, 18), + }; + + const addAssetCalldata = ethers.utils.defaultAbiCoder.encode( + ['address', 'tuple(address,address,uint8,uint64,uint64,uint64,uint128)'], + [comet.address, + [ + ezETHAssetConfig.asset, + ezETHAssetConfig.priceFeed, + ezETHAssetConfig.decimals, + ezETHAssetConfig.borrowCollateralFactor, + ezETHAssetConfig.liquidateCollateralFactor, + ezETHAssetConfig.liquidationFactor, + ezETHAssetConfig.supplyCap + ] + ] + ); + + const deployAndUpgradeToCalldata = ethers.utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + cometAdmin.address + ], + [ + 0, + 0 + ], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [ + addAssetCalldata, + deployAndUpgradeToCalldata, + ] + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo WETH 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 + }, + ]; + + const description = '# Add ezETH as collateral into cWETHv3 on Arbitrum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ezETH into cWETHv3 on Arbitrum network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Arbitrum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404/11).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/907) and [forum discussion](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404).\n\n\n## Proposal Actions\n\nThe first proposal action adds ezETH to the WETH Comet on Arbitrum. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Arbitrum.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const ezETHAssetIndex = Number(await comet.numAssets()) - 1; + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: newPriceFeed, + decimals: 18n, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.94, 18), + supplyCap: exp(2500, 18), + }; + + // 1. & 2. Compare ezETH asset config with Comet and Configurator asset info + const cometEzETHAssetInfo = await comet.getAssetInfoByAddress(EZETH_ADDRESS); + expect(ezETHAssetIndex).to.be.equal(cometEzETHAssetInfo.offset); + expect(ezETHAssetConfig.asset).to.be.equal(cometEzETHAssetInfo.asset); + expect(exp(1, ezETHAssetConfig.decimals)).to.be.equal(cometEzETHAssetInfo.scale); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(cometEzETHAssetInfo.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(cometEzETHAssetInfo.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(cometEzETHAssetInfo.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(cometEzETHAssetInfo.supplyCap); + + const configuratorEzETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[ezETHAssetIndex]; + expect(ezETHAssetConfig.asset).to.be.equal(configuratorEzETHAssetConfig.asset); + expect(ezETHAssetConfig.decimals).to.be.equal(configuratorEzETHAssetConfig.decimals); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(configuratorEzETHAssetConfig.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(configuratorEzETHAssetConfig.supplyCap); + }, +}); From 42b7c0d1cda18955b6d0493595caa4b3a236bf63 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 10 Sep 2024 20:27:51 +0300 Subject: [PATCH 03/22] Add ezETH as collateral to USDC market on Arbitrum (#908) Co-authored-by: GitHub Actions Bot <> --- .../1723551632_add_ezeth_as_collateral.ts | 198 ++++++++++++++++++ deployments/arbitrum/usdc/relations.ts | 3 + 2 files changed, 201 insertions(+) create mode 100644 deployments/arbitrum/usdc/migrations/1723551632_add_ezeth_as_collateral.ts diff --git a/deployments/arbitrum/usdc/migrations/1723551632_add_ezeth_as_collateral.ts b/deployments/arbitrum/usdc/migrations/1723551632_add_ezeth_as_collateral.ts new file mode 100644 index 000000000..25b239cd8 --- /dev/null +++ b/deployments/arbitrum/usdc/migrations/1723551632_add_ezeth_as_collateral.ts @@ -0,0 +1,198 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; +import { ethers } from 'ethers'; + +const EZETH_ADDRESS = '0x2416092f143378750bb29b79eD961ab195CcEea5'; +const EZETH_TO_ETH_PRICE_FEED_ADDRESS = '0x989a480b6054389075CBCdC385C18CfB6FC08186'; +const ETH_TO_USD_PRICE_FEED_ADDRESS = '0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612'; + +let newPriceFeed: string; + +export default migration('1723551632_add_ezeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _ezETHPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + EZETH_TO_ETH_PRICE_FEED_ADDRESS, // ezETH / ETH price feed + ETH_TO_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'ezETH / USD price feed' // description + ] + ); + + return { ezETHPriceFeedAddress: _ezETHPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { ezETHPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + newPriceFeed = ezETHPriceFeedAddress; + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const ezETHPriceFeed = await deploymentManager.existing( + 'ezETH:priceFeed', + ezETHPriceFeedAddress, + 'arbitrum' + ); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: ezETHPriceFeed.address, + decimals: 18n, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.90, 18), + supplyCap: exp(500, 18), + }; + + const addAssetCalldata = ethers.utils.defaultAbiCoder.encode( + ['address', 'tuple(address,address,uint8,uint64,uint64,uint64,uint128)'], + [comet.address, + [ + ezETHAssetConfig.asset, + ezETHAssetConfig.priceFeed, + ezETHAssetConfig.decimals, + ezETHAssetConfig.borrowCollateralFactor, + ezETHAssetConfig.liquidateCollateralFactor, + ezETHAssetConfig.liquidationFactor, + ezETHAssetConfig.supplyCap + ] + ] + ); + + const deployAndUpgradeToCalldata = ethers.utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + cometAdmin.address + ], + [ + 0, + 0 + ], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [ + addAssetCalldata, + deployAndUpgradeToCalldata, + ] + ] + ); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + const refundAddress = l2Timelock.address; + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo USDC 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 + }, + ]; + + const description = '# Add ezETH as collateral into cUSDCv3 on Arbitrum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ezETH into cUSDCv3 on Arbitrum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDC market on Arbitrum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404/11).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/908) and [forum discussion](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404).\n\n\n## Proposal Actions\n\nThe first proposal action adds ezETH to the USDC Comet on Arbitrum. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Arbitrum.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const ezETHAssetIndex = Number(await comet.numAssets()) - 1; + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: newPriceFeed, + decimals: 18n, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.90, 18), + supplyCap: exp(500, 18), + }; + + // 1. & 2. Compare ezETH asset config with Comet and Configurator asset info + const cometEzETHAssetInfo = await comet.getAssetInfoByAddress(EZETH_ADDRESS); + expect(ezETHAssetIndex).to.be.equal(cometEzETHAssetInfo.offset); + expect(ezETHAssetConfig.asset).to.be.equal(cometEzETHAssetInfo.asset); + expect(exp(1, ezETHAssetConfig.decimals)).to.be.equal(cometEzETHAssetInfo.scale); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(cometEzETHAssetInfo.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(cometEzETHAssetInfo.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(cometEzETHAssetInfo.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(cometEzETHAssetInfo.supplyCap); + + const configuratorEzETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[ezETHAssetIndex]; + expect(ezETHAssetConfig.asset).to.be.equal(configuratorEzETHAssetConfig.asset); + expect(ezETHAssetConfig.decimals).to.be.equal(configuratorEzETHAssetConfig.decimals); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.borrowCollateralFactor); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorEzETHAssetConfig.liquidateCollateralFactor); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal(configuratorEzETHAssetConfig.liquidationFactor); + expect(ezETHAssetConfig.supplyCap).to.be.equal(configuratorEzETHAssetConfig.supplyCap); + }, +}); diff --git a/deployments/arbitrum/usdc/relations.ts b/deployments/arbitrum/usdc/relations.ts index 103293632..462bfc1a4 100644 --- a/deployments/arbitrum/usdc/relations.ts +++ b/deployments/arbitrum/usdc/relations.ts @@ -8,6 +8,9 @@ export default { ClonableBeaconProxy: { artifact: 'contracts/ERC20.sol:ERC20' }, + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, // Native USDC '0xaf88d065e77c8cC2239327C5EDb3A432268e5831': { artifact: 'contracts/ERC20.sol:ERC20', From 2f915673b1c96e92739be2293d233a3812a778ae Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 10 Sep 2024 20:28:14 +0300 Subject: [PATCH 04/22] Add weETH as collateral to WETH market on Optimism (#909) Co-authored-by: GitHub Actions Bot <> --- .../1723630908_add_weeth_as_collateral.ts | 149 ++++++++++++++++++ deployments/optimism/weth/relations.ts | 10 ++ 2 files changed, 159 insertions(+) create mode 100644 deployments/optimism/weth/migrations/1723630908_add_weeth_as_collateral.ts diff --git a/deployments/optimism/weth/migrations/1723630908_add_weeth_as_collateral.ts b/deployments/optimism/weth/migrations/1723630908_add_weeth_as_collateral.ts new file mode 100644 index 000000000..86fd6f6da --- /dev/null +++ b/deployments/optimism/weth/migrations/1723630908_add_weeth_as_collateral.ts @@ -0,0 +1,149 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const WEETH_ADDRESS = '0x5A7fACB970D094B6C7FF1df0eA68D99E6e73CBFF'; +const WEETH_STETH_PRICE_FEED_ADDRESS = '0x72EC6bF88effEd88290C66DCF1bE2321d80502f5'; + +let newPriceFeedAddress: string; + +export default migration('1723630908_add_weeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _weETHPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + WEETH_STETH_PRICE_FEED_ADDRESS, // weETH / ETH price feed + 8 // decimals + ] + ); + return { weETHPriceFeedAddress: _weETHPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { weETHPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const weETH = await deploymentManager.existing( + 'weETH', + WEETH_ADDRESS, + 'optimism', + 'contracts/ERC20.sol:ERC20' + ); + const weETHPricefeed = await deploymentManager.existing( + 'weETH:priceFeed', + weETHPriceFeedAddress, + 'optimism' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: weETH.address, + priceFeed: weETHPricefeed.address, + decimals: await weETH.decimals(), + borrowCollateralFactor: exp(0.90, 18), + liquidateCollateralFactor: exp(0.93, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(400, 18), + }; + + newPriceFeedAddress = weETHPricefeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add weETH as collateral into cWETHv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add weETH into cWETHv3 on Optimism network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Optimism. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-weeth-as-collateral-to-eth-markets-on-optimism-and-base/5520/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/909) and [forum discussion](https://www.comp.xyz/t/add-weeth-as-collateral-to-eth-markets-on-optimism-and-base/5520).\n\n\n## Proposal Actions\n\nThe first proposal action adds weETH to the WETH Comet on Optimism. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const weETHAssetIndex = Number(await comet.numAssets()) - 1; + + const weETHAssetConfig = { + asset: WEETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.90, 18), + liquidateCollateralFactor: exp(0.93, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(400, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const weETHAssetInfo = await comet.getAssetInfoByAddress(WEETH_ADDRESS); + expect(weETHAssetIndex).to.be.equal(weETHAssetInfo.offset); + expect(weETHAssetConfig.asset).to.be.equal(weETHAssetInfo.asset); + expect(weETHAssetConfig.priceFeed).to.be.equal(weETHAssetInfo.priceFeed); + expect(exp(1, weETHAssetConfig.decimals)).to.be.equal(weETHAssetInfo.scale); + expect(weETHAssetConfig.borrowCollateralFactor).to.be.equal(weETHAssetInfo.borrowCollateralFactor); + expect(weETHAssetConfig.liquidateCollateralFactor).to.be.equal(weETHAssetInfo.liquidateCollateralFactor); + expect(weETHAssetConfig.liquidationFactor).to.be.equal(weETHAssetInfo.liquidationFactor); + expect(weETHAssetConfig.supplyCap).to.be.equal(weETHAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWeETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[weETHAssetIndex]; + expect(weETHAssetConfig.asset).to.be.equal(configuratorWeETHAssetConfig.asset); + expect(weETHAssetConfig.priceFeed).to.be.equal(configuratorWeETHAssetConfig.priceFeed); + expect(weETHAssetConfig.decimals).to.be.equal(configuratorWeETHAssetConfig.decimals); + expect(weETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWeETHAssetConfig.borrowCollateralFactor); + expect(weETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWeETHAssetConfig.liquidateCollateralFactor); + expect(weETHAssetConfig.liquidationFactor).to.be.equal(configuratorWeETHAssetConfig.liquidationFactor); + expect(weETHAssetConfig.supplyCap).to.be.equal(configuratorWeETHAssetConfig.supplyCap); + }, +}); diff --git a/deployments/optimism/weth/relations.ts b/deployments/optimism/weth/relations.ts index 45457356a..0d817b8e6 100644 --- a/deployments/optimism/weth/relations.ts +++ b/deployments/optimism/weth/relations.ts @@ -56,4 +56,14 @@ export default { WBTC: { artifact: 'contracts/ERC20.sol:ERC20' }, + + // weETH + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; From b5f1380189952fe68a22dd027d9dc396afc87357 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 10 Sep 2024 20:28:27 +0300 Subject: [PATCH 05/22] Add weETH as collateral to WETH market on Base (#910) Co-authored-by: GitHub Actions Bot <> --- .../1723638164_add_weeth_as_collateral.ts | 149 ++++++++++++++++++ deployments/base/weth/relations.ts | 4 +- 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 deployments/base/weth/migrations/1723638164_add_weeth_as_collateral.ts diff --git a/deployments/base/weth/migrations/1723638164_add_weeth_as_collateral.ts b/deployments/base/weth/migrations/1723638164_add_weeth_as_collateral.ts new file mode 100644 index 000000000..83de80f95 --- /dev/null +++ b/deployments/base/weth/migrations/1723638164_add_weeth_as_collateral.ts @@ -0,0 +1,149 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const WEETH_ADDRESS = '0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A'; +const WEETH_STETH_PRICE_FEED_ADDRESS = '0x35e9D7001819Ea3B39Da906aE6b06A62cfe2c181'; + +let newPriceFeedAddress: string; + +export default migration('1723638164_add_weeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _weETHPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + WEETH_STETH_PRICE_FEED_ADDRESS, // weETH / ETH price feed + 8 // decimals + ] + ); + return { weETHPriceFeedAddress: _weETHPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { weETHPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const weETH = await deploymentManager.existing( + 'weETH', + WEETH_ADDRESS, + 'base', + 'contracts/ERC20.sol:ERC20' + ); + const weETHPricefeed = await deploymentManager.existing( + 'weETH:priceFeed', + weETHPriceFeedAddress, + 'base' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, baseL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: weETH.address, + priceFeed: weETHPricefeed.address, + decimals: await weETH.decimals(), + borrowCollateralFactor: exp(0.90, 18), + liquidateCollateralFactor: exp(0.93, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(2100, 18), + }; + + newPriceFeedAddress = weETHPricefeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add weETH as collateral into cWETHv3 on Base\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add weETH into cWETHv3 on Base network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Base. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-weeth-as-collateral-to-eth-markets-on-optimism-and-base/5520/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/910) and [forum discussion](https://www.comp.xyz/t/add-weeth-as-collateral-to-eth-markets-on-optimism-and-base/5520).\n\n\n## Proposal Actions\n\nThe first proposal action adds weETH to the WETH Comet on Base. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Base.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const weETHAssetIndex = Number(await comet.numAssets()) - 1; + + const weETHAssetConfig = { + asset: WEETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.90, 18), + liquidateCollateralFactor: exp(0.93, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(2100, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const weETHAssetInfo = await comet.getAssetInfoByAddress(WEETH_ADDRESS); + expect(weETHAssetIndex).to.be.equal(weETHAssetInfo.offset); + expect(weETHAssetConfig.asset).to.be.equal(weETHAssetInfo.asset); + expect(weETHAssetConfig.priceFeed).to.be.equal(weETHAssetInfo.priceFeed); + expect(exp(1, weETHAssetConfig.decimals)).to.be.equal(weETHAssetInfo.scale); + expect(weETHAssetConfig.borrowCollateralFactor).to.be.equal(weETHAssetInfo.borrowCollateralFactor); + expect(weETHAssetConfig.liquidateCollateralFactor).to.be.equal(weETHAssetInfo.liquidateCollateralFactor); + expect(weETHAssetConfig.liquidationFactor).to.be.equal(weETHAssetInfo.liquidationFactor); + expect(weETHAssetConfig.supplyCap).to.be.equal(weETHAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWeETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[weETHAssetIndex]; + expect(weETHAssetConfig.asset).to.be.equal(configuratorWeETHAssetConfig.asset); + expect(weETHAssetConfig.priceFeed).to.be.equal(configuratorWeETHAssetConfig.priceFeed); + expect(weETHAssetConfig.decimals).to.be.equal(configuratorWeETHAssetConfig.decimals); + expect(weETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWeETHAssetConfig.borrowCollateralFactor); + expect(weETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWeETHAssetConfig.liquidateCollateralFactor); + expect(weETHAssetConfig.liquidationFactor).to.be.equal(configuratorWeETHAssetConfig.liquidationFactor); + expect(weETHAssetConfig.supplyCap).to.be.equal(configuratorWeETHAssetConfig.supplyCap); + }, +}); diff --git a/deployments/base/weth/relations.ts b/deployments/base/weth/relations.ts index ab5df33f2..ac825da26 100644 --- a/deployments/base/weth/relations.ts +++ b/deployments/base/weth/relations.ts @@ -31,7 +31,7 @@ export default { } }, - TransparentUpgradeableProxy: { + OssifiableProxy: { artifact: 'contracts/ERC20.sol:ERC20', delegates: { field: { @@ -40,7 +40,7 @@ export default { } }, - OssifiableProxy: { + TransparentUpgradeableProxy: { artifact: 'contracts/ERC20.sol:ERC20', delegates: { field: { From f99b4621ee7ac8d4e19600436e852ab33b4a3d06 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 12 Sep 2024 21:14:58 +0300 Subject: [PATCH 06/22] Add wrsETH as collateral to WETH market on Optimism (#913) Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> --- .../1724837643_add_wrseth_as_collateral.ts | 149 ++++++++++++++++++ deployments/optimism/weth/relations.ts | 2 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 deployments/optimism/weth/migrations/1724837643_add_wrseth_as_collateral.ts diff --git a/deployments/optimism/weth/migrations/1724837643_add_wrseth_as_collateral.ts b/deployments/optimism/weth/migrations/1724837643_add_wrseth_as_collateral.ts new file mode 100644 index 000000000..c666483b7 --- /dev/null +++ b/deployments/optimism/weth/migrations/1724837643_add_wrseth_as_collateral.ts @@ -0,0 +1,149 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const WRSETH_ADDRESS = '0x87eEE96D50Fb761AD85B1c982d28A042169d61b1'; +const WRSETH_ETH_PRICE_FEED_ADDRESS = '0x73b8BE3b653c5896BC34fC87cEBC8AcF4Fb7A545'; +let newPriceFeedAddress: string; + +export default migration('1724837643_add_wrseth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wrsETHPriceFeed = await deploymentManager.deploy( + 'wrsETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + WRSETH_ETH_PRICE_FEED_ADDRESS, // wrsETH / ETH price feed + 8 // decimals + ], + true + ); + return { wrsETHPriceFeedAddress: _wrsETHPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { wrsETHPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const wrsETH = await deploymentManager.existing( + 'wrsETH', + WRSETH_ADDRESS, + 'optimism', + 'contracts/ERC20.sol:ERC20' + ); + const wrsETHPriceFeed = await deploymentManager.existing( + 'wrsETH:priceFeed', + wrsETHPriceFeedAddress, + 'optimism' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, opL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: wrsETH.address, + priceFeed: wrsETHPriceFeed.address, + decimals: await wrsETH.decimals(), + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(220, 18), + }; + + newPriceFeedAddress = wrsETHPriceFeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add wrsETH as collateral into cWETHv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wrsETH into cWETHv3 on Optimism network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Optimism. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-rseth-as-collateral-on-arbitrum-and-wrseth-as-collateral-on-optimism-base-and-scroll/5445/5).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/913) and [forum discussion](https://www.comp.xyz/t/add-rseth-as-collateral-on-arbitrum-and-wrseth-as-collateral-on-optimism-base-and-scroll/5445).\n\n\n## Proposal Actions\n\nThe first proposal action adds wrsETH to the WETH Comet on Optimism. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism.'; + 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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const wrsETHAssetIndex = Number(await comet.numAssets()) - 1; + + const wrsETHAssetConfig = { + asset: WRSETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(220, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const wrsETHAssetInfo = await comet.getAssetInfoByAddress(WRSETH_ADDRESS); + expect(wrsETHAssetIndex).to.be.equal(wrsETHAssetInfo.offset); + expect(wrsETHAssetConfig.asset).to.be.equal(wrsETHAssetInfo.asset); + expect(wrsETHAssetConfig.priceFeed).to.be.equal(wrsETHAssetInfo.priceFeed); + expect(exp(1, wrsETHAssetConfig.decimals)).to.be.equal(wrsETHAssetInfo.scale); + expect(wrsETHAssetConfig.borrowCollateralFactor).to.be.equal(wrsETHAssetInfo.borrowCollateralFactor); + expect(wrsETHAssetConfig.liquidateCollateralFactor).to.be.equal(wrsETHAssetInfo.liquidateCollateralFactor); + expect(wrsETHAssetConfig.liquidationFactor).to.be.equal(wrsETHAssetInfo.liquidationFactor); + expect(wrsETHAssetConfig.supplyCap).to.be.equal(wrsETHAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWrsETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[wrsETHAssetIndex]; + expect(wrsETHAssetConfig.asset).to.be.equal(configuratorWrsETHAssetConfig.asset); + expect(wrsETHAssetConfig.priceFeed).to.be.equal(configuratorWrsETHAssetConfig.priceFeed); + expect(wrsETHAssetConfig.decimals).to.be.equal(configuratorWrsETHAssetConfig.decimals); + expect(wrsETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWrsETHAssetConfig.borrowCollateralFactor); + expect(wrsETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWrsETHAssetConfig.liquidateCollateralFactor); + expect(wrsETHAssetConfig.liquidationFactor).to.be.equal(configuratorWrsETHAssetConfig.liquidationFactor); + expect(wrsETHAssetConfig.supplyCap).to.be.equal(configuratorWrsETHAssetConfig.supplyCap); + }, +}); diff --git a/deployments/optimism/weth/relations.ts b/deployments/optimism/weth/relations.ts index 0d817b8e6..e88708f5d 100644 --- a/deployments/optimism/weth/relations.ts +++ b/deployments/optimism/weth/relations.ts @@ -56,7 +56,7 @@ export default { WBTC: { artifact: 'contracts/ERC20.sol:ERC20' }, - + // weETH TransparentUpgradeableProxy: { artifact: 'contracts/ERC20.sol:ERC20', From 2fc94d987ebd46572da93a3323b67c5d27642b1b Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 12 Sep 2024 21:16:08 +0300 Subject: [PATCH 07/22] Add wrsETH as collateral to WETH market on Base (#914) Co-authored-by: GitHub Actions Bot <> --- .../1724852274_add_wrseth_as_collateral.ts | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 deployments/base/weth/migrations/1724852274_add_wrseth_as_collateral.ts diff --git a/deployments/base/weth/migrations/1724852274_add_wrseth_as_collateral.ts b/deployments/base/weth/migrations/1724852274_add_wrseth_as_collateral.ts new file mode 100644 index 000000000..f6aa644e6 --- /dev/null +++ b/deployments/base/weth/migrations/1724852274_add_wrseth_as_collateral.ts @@ -0,0 +1,148 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const WRSETH_ADDRESS = '0xEDfa23602D0EC14714057867A78d01e94176BEA0'; +const WRSETHETH_ETH_PRICE_FEED_ADDRESS = '0xe8dD07CCf5BC4922424140E44Eb970F5950725ef'; +let newPriceFeedAddress: string; + +export default migration('1724852274_add_wrseth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wrsETHPriceFeed = await deploymentManager.deploy( + 'wrsETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + WRSETHETH_ETH_PRICE_FEED_ADDRESS, // wrsETH / ETH price feed + 8, // decimals + ] + ); + return { wrsETHPriceFeedAddress: _wrsETHPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { wrsETHPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const wrsETH = await deploymentManager.existing( + 'wrsETH', + WRSETH_ADDRESS, + 'base', + 'contracts/ERC20.sol:ERC20' + ); + const wrsETHPricefeed = await deploymentManager.existing( + 'wrsETH:priceFeed', + wrsETHPriceFeedAddress, + 'base' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, baseL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: wrsETH.address, + priceFeed: wrsETHPricefeed.address, + decimals: await wrsETH.decimals(), + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(230, 18), + }; + + newPriceFeedAddress = wrsETHPricefeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add wrsETH as collateral into cWETHv3 on Base\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wrsETH into cWETHv3 on Base network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Base. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-rseth-as-collateral-on-arbitrum-and-wrseth-as-collateral-on-optimism-base-and-scroll/5445/5).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/914) and [forum discussion](https://www.comp.xyz/t/add-rseth-as-collateral-on-arbitrum-and-wrseth-as-collateral-on-optimism-base-and-scroll/5445).\n\n\n## Proposal Actions\n\nThe first proposal action adds wrsETH to the WETH Comet on Base. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Base.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const wrsETHAssetIndex = Number(await comet.numAssets()) - 1; + + const wrsETHAssetConfig = { + asset: WRSETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.91, 18), + liquidationFactor: exp(0.96, 18), + supplyCap: exp(230, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const wrsETHAssetInfo = await comet.getAssetInfoByAddress(WRSETH_ADDRESS); + expect(wrsETHAssetIndex).to.be.equal(wrsETHAssetInfo.offset); + expect(wrsETHAssetConfig.asset).to.be.equal(wrsETHAssetInfo.asset); + expect(wrsETHAssetConfig.priceFeed).to.be.equal(wrsETHAssetInfo.priceFeed); + expect(exp(1, wrsETHAssetConfig.decimals)).to.be.equal(wrsETHAssetInfo.scale); + expect(wrsETHAssetConfig.borrowCollateralFactor).to.be.equal(wrsETHAssetInfo.borrowCollateralFactor); + expect(wrsETHAssetConfig.liquidateCollateralFactor).to.be.equal(wrsETHAssetInfo.liquidateCollateralFactor); + expect(wrsETHAssetConfig.liquidationFactor).to.be.equal(wrsETHAssetInfo.liquidationFactor); + expect(wrsETHAssetConfig.supplyCap).to.be.equal(wrsETHAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWrsETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[wrsETHAssetIndex]; + expect(wrsETHAssetConfig.asset).to.be.equal(configuratorWrsETHAssetConfig.asset); + expect(wrsETHAssetConfig.priceFeed).to.be.equal(configuratorWrsETHAssetConfig.priceFeed); + expect(wrsETHAssetConfig.decimals).to.be.equal(configuratorWrsETHAssetConfig.decimals); + expect(wrsETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWrsETHAssetConfig.borrowCollateralFactor); + expect(wrsETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWrsETHAssetConfig.liquidateCollateralFactor); + expect(wrsETHAssetConfig.liquidationFactor).to.be.equal(configuratorWrsETHAssetConfig.liquidationFactor); + expect(wrsETHAssetConfig.supplyCap).to.be.equal(configuratorWrsETHAssetConfig.supplyCap); + }, +}); From b382d9e05480a058e44b5aa220c78af0b12ec0a0 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Mon, 16 Sep 2024 22:09:06 +0300 Subject: [PATCH 08/22] Update workflow versions and fix one relation (#924) --- .github/workflows/deploy-market.yaml | 6 +++--- .github/workflows/enact-migration.yaml | 6 +++--- .github/workflows/prepare-migration.yaml | 6 +++--- .github/workflows/run-contract-linter.yaml | 4 ++-- .github/workflows/run-coverage.yaml | 4 ++-- .github/workflows/run-eslint.yaml | 4 ++-- .github/workflows/run-forge-tests.yaml | 2 +- .github/workflows/run-gas-profiler.yaml | 4 ++-- .github/workflows/run-scenarios.yaml | 10 +++++----- .github/workflows/run-slither.yaml | 4 ++-- .github/workflows/run-unit-tests.yaml | 6 +++--- deployments/optimism/weth/relations.ts | 10 ---------- 12 files changed, 28 insertions(+), 38 deletions(-) diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index 5de6eb684..fe6e35024 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -53,9 +53,9 @@ jobs: if: github.event.inputs.eth_pk == '' - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' @@ -77,7 +77,7 @@ jobs: NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - - uses: actions/upload-artifact@v2 # upload test results + - uses: actions/upload-artifact@v4 # upload test results if: success() || failure() # run this step even if previous step failed with: name: ${{ github.event.inputs.network }}-${{ github.event.inputs.deployment }}-verify-args diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index f3a5e4524..cf1310a8f 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -85,9 +85,9 @@ jobs: if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' @@ -100,7 +100,7 @@ jobs: - name: Check types run: yarn tsc - - uses: dawidd6/action-download-artifact@v2 + - uses: dawidd6/action-download-artifact@v4 with: workflow: prepare-migration.yml run_id: ${{ github.event.inputs.run_id }} diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index c27bc569e..9d6206a18 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -53,9 +53,9 @@ jobs: if: github.event.inputs.eth_pk == '' - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' @@ -77,7 +77,7 @@ jobs: NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - - uses: actions/upload-artifact@v2 # upload test results + - uses: actions/upload-artifact@v4 # upload test results if: success() || failure() # run this step even if previous step failed with: name: ${{ github.event.inputs.network }}-${{ github.event.inputs.deployment }}-${{ github.event.inputs.migration }} diff --git a/.github/workflows/run-contract-linter.yaml b/.github/workflows/run-contract-linter.yaml index b027b58fb..2513cc18c 100644 --- a/.github/workflows/run-contract-linter.yaml +++ b/.github/workflows/run-contract-linter.yaml @@ -15,11 +15,11 @@ jobs: LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' diff --git a/.github/workflows/run-coverage.yaml b/.github/workflows/run-coverage.yaml index e2272a9d5..b28829b56 100644 --- a/.github/workflows/run-coverage.yaml +++ b/.github/workflows/run-coverage.yaml @@ -18,9 +18,9 @@ jobs: OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: cache: 'yarn' node-version: '16' diff --git a/.github/workflows/run-eslint.yaml b/.github/workflows/run-eslint.yaml index 9763d45d4..1961fec4c 100644 --- a/.github/workflows/run-eslint.yaml +++ b/.github/workflows/run-eslint.yaml @@ -16,9 +16,9 @@ jobs: OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: cache: 'yarn' node-version: '16' diff --git a/.github/workflows/run-forge-tests.yaml b/.github/workflows/run-forge-tests.yaml index 069a30bdb..6a10f7523 100644 --- a/.github/workflows/run-forge-tests.yaml +++ b/.github/workflows/run-forge-tests.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: recursive diff --git a/.github/workflows/run-gas-profiler.yaml b/.github/workflows/run-gas-profiler.yaml index d2418ad42..d49ba9152 100644 --- a/.github/workflows/run-gas-profiler.yaml +++ b/.github/workflows/run-gas-profiler.yaml @@ -17,9 +17,9 @@ jobs: OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 0fecdd98c..c2b7adcce 100644 --- a/.github/workflows/run-scenarios.yaml +++ b/.github/workflows/run-scenarios.yaml @@ -22,17 +22,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: cache: 'yarn' node-version: '16' - name: Cache Deployments - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | deployments/*/.contracts @@ -54,10 +54,10 @@ jobs: - name: Run scenarios run: yarn scenario --bases ${{ matrix.bases }} - - uses: actions/upload-artifact@v2 # upload scenario results + - uses: actions/upload-artifact@v4 # upload scenario results if: success() || failure() # run this step even if previous step failed with: - name: scenario-results + name: scenario-results-${{ matrix.bases }} path: scenario-results.json - uses: dorny/test-reporter@v1 diff --git a/.github/workflows/run-slither.yaml b/.github/workflows/run-slither.yaml index 3c80df49e..2ca5a871d 100644 --- a/.github/workflows/run-slither.yaml +++ b/.github/workflows/run-slither.yaml @@ -8,9 +8,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index d198e156e..49779a40f 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -16,9 +16,9 @@ jobs: OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: '16' @@ -34,7 +34,7 @@ jobs: - name: Run tests run: yarn test - - uses: actions/upload-artifact@v2 # upload test results + - uses: actions/upload-artifact@v4 # upload test results if: success() || failure() # run this step even if previous step failed with: name: test-results diff --git a/deployments/optimism/weth/relations.ts b/deployments/optimism/weth/relations.ts index e88708f5d..45457356a 100644 --- a/deployments/optimism/weth/relations.ts +++ b/deployments/optimism/weth/relations.ts @@ -56,14 +56,4 @@ export default { WBTC: { artifact: 'contracts/ERC20.sol:ERC20' }, - - // weETH - TransparentUpgradeableProxy: { - artifact: 'contracts/ERC20.sol:ERC20', - delegates: { - field: { - slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' - } - } - }, }; From 1d30582704b16930275806337f2efe4984f5ed32 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 17 Sep 2024 22:08:28 +0300 Subject: [PATCH 09/22] Deploy wstETH market on Mainnet (#911) Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> --- .github/workflows/enact-migration.yaml | 20 +- .github/workflows/run-scenarios.yaml | 2 +- .../MainnetBulkerWithWstETHSupport.sol | 109 ++++++++ deployments/mainnet/wsteth/configuration.json | 46 ++++ deployments/mainnet/wsteth/deploy.ts | 77 ++++++ .../1723732097_configurate_and_ens.ts | 237 ++++++++++++++++++ deployments/mainnet/wsteth/relations.ts | 34 +++ deployments/mainnet/wsteth/roots.json | 6 + hardhat.config.ts | 9 +- .../deployment_manager/DeploymentManager.ts | 3 +- scenario/BulkerScenario.ts | 209 ++++++++++++++- scenario/LiquidationBotScenario.ts | 6 +- scenario/utils/index.ts | 2 +- src/deploy/index.ts | 1 + tasks/deployment_manager/task.ts | 121 +++++++++ 15 files changed, 870 insertions(+), 12 deletions(-) create mode 100644 contracts/bulkers/MainnetBulkerWithWstETHSupport.sol create mode 100644 deployments/mainnet/wsteth/configuration.json create mode 100644 deployments/mainnet/wsteth/deploy.ts create mode 100644 deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts create mode 100644 deployments/mainnet/wsteth/relations.ts create mode 100644 deployments/mainnet/wsteth/roots.json diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index cf1310a8f..d2d62842e 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -40,6 +40,11 @@ on: description: Impersonate Account required: false default: '' + with_deploy: + type: boolean + description: Deploy Market + required: false + default: false jobs: enact-migration: name: Enact Migration @@ -108,6 +113,17 @@ jobs: path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/ if: github.event.inputs.run_id != '' + - name: Run Deploy Market and Enact Migration (impersonate) + run: | + yarn hardhat deploy_and_migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} + env: + DEBUG: true + ETH_PK: "${{ inputs.eth_pk }}" + NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} + GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} + GOV_NETWORK: ${{ env.GOV_NETWORK }} + REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} + if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'true' - name: Run Enact Migration run: | yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} @@ -118,7 +134,7 @@ jobs: GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} GOV_NETWORK: ${{ env.GOV_NETWORK }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - if: github.event.inputs.impersonateAccount == '' + if: github.event.inputs.impersonateAccount == '' && github.event.inputs.with_deploy == 'false' - name: Run Enact Migration (impersonate) run: | yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} @@ -129,7 +145,7 @@ jobs: GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} GOV_NETWORK: ${{ env.GOV_NETWORK }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - if: github.event.inputs.impersonateAccount != '' + if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'false' - name: Commit changes if: ${{ github.event.inputs.simulate == 'false' }} run: | diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index c2b7adcce..fcff8271e 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, mainnet-usdt, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol new file mode 100644 index 000000000..a05016d91 --- /dev/null +++ b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./BaseBulker.sol"; +import "../IWstETH.sol"; + +/** + * @title Compound's Bulker contract for Ethereum mainnet + * @notice Executes multiple Comet-related actions in a single transaction + * @author Compound + */ +contract MainnetBulkerWithWstETHSupport is BaseBulker { + /** General configuration constants **/ + + /// @notice The address of Lido staked ETH + address public immutable steth; + + /// @notice The address of Lido wrapped staked ETH + address public immutable wsteth; + + /** Actions **/ + + /// @notice The action for supplying staked ETH to Comet + bytes32 public constant ACTION_SUPPLY_STETH = "ACTION_SUPPLY_STETH"; + + /// @notice The action for withdrawing staked ETH from Comet + bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH"; + + /** Custom errors **/ + + error UnsupportedBaseAsset(); + + /** + * @notice Construct a new MainnetBulker instance + * @param admin_ The admin of the Bulker contract + * @param weth_ The address of wrapped ETH + * @param wsteth_ The address of Lido wrapped staked ETH + **/ + constructor( + address admin_, + address payable weth_, + address wsteth_ + ) BaseBulker(admin_, weth_) { + wsteth = wsteth_; + steth = IWstETH(wsteth_).stETH(); + } + + /** + * @notice Handles actions specific to the Ethereum mainnet version of Bulker, specifically supplying and withdrawing stETH + */ + function handleAction(bytes32 action, bytes calldata data) override internal { + if (action == ACTION_SUPPLY_STETH) { + (address comet, address to, uint stETHAmount) = abi.decode(data, (address, address, uint)); + supplyStEthTo(comet, to, stETHAmount); + } else if (action == ACTION_WITHDRAW_STETH) { + (address comet, address to, uint wstETHAmount) = abi.decode(data, (address, address, uint)); + withdrawStEthTo(comet, to, wstETHAmount); + } else { + revert UnhandledAction(); + } + } + + /** + * @notice Wraps stETH to wstETH and supplies to a user in Comet + * @dev Note: This contract must have permission to manage msg.sender's Comet account + * @dev Note: Supports `stETHAmount` of `uint256.max` to fully repay the wstETH debt + * @dev Note: Only for the cwstETHv3 market + */ + function supplyStEthTo(address comet, address to, uint stETHAmount) internal { + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); + uint256 _stETHAmount = stETHAmount == type(uint256).max + ? IWstETH(wsteth).getStETHByWstETH(CometInterface(comet).borrowBalanceOf(msg.sender)) + : stETHAmount; + doTransferIn(steth, msg.sender, _stETHAmount); + ERC20(steth).approve(wsteth, _stETHAmount); + uint wstETHAmount = IWstETH(wsteth).wrap(_stETHAmount); + ERC20(wsteth).approve(comet, wstETHAmount); + CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount); + } + + /** + * @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user + * @dev Note: This contract must have permission to manage msg.sender's Comet account + * @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet + * @dev Note: Only for the cwstETHv3 market + */ + function withdrawStEthTo(address comet, address to, uint stETHAmount) internal { + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); + uint wstETHAmount = stETHAmount == type(uint256).max + ? CometInterface(comet).balanceOf(msg.sender) + : IWstETH(wsteth).getWstETHByStETH(stETHAmount); + CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount); + uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount); + doTransferOut(steth, to, unwrappedStETHAmount); + } + + /** + * @notice Submits received ether to get stETH and wraps it to wstETH, received wstETH is transferred to Comet + */ + function deposit(address comet) external payable { + if(msg.sender != admin) revert Unauthorized(); + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); + (bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0)); + if(!success) revert TransferOutFailed(); + + uint wstETHAmount = ERC20(wsteth).balanceOf(address(this)); + doTransferOut(wsteth, comet, wstETHAmount); + } +} \ No newline at end of file diff --git a/deployments/mainnet/wsteth/configuration.json b/deployments/mainnet/wsteth/configuration.json new file mode 100644 index 000000000..0bd7b4d37 --- /dev/null +++ b/deployments/mainnet/wsteth/configuration.json @@ -0,0 +1,46 @@ +{ + "name": "Compound wstETH", + "symbol": "cWstETHv3", + "baseToken": "wstETH", + "baseTokenAddress": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "borrowMin": "0.1e18", + "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", + "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", + "storeFrontPriceFactor": 0.7, + "targetReserves": "5000e18", + "rates": { + "supplyBase": 0, + "supplySlopeLow": 0.012, + "supplyKink": 0.85, + "supplySlopeHigh": 1, + "borrowBase": 0.01, + "borrowSlopeLow": 0.014, + "borrowKink": 0.85, + "borrowSlopeHigh": 1.15 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "92592592592e0", + "baseBorrowSpeed": "46296296296e0", + "baseMinForRewards": "10e18" + }, + "rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", + "assets": { + "rsETH": { + "address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.91, + "liquidationFactor": 0.96, + "supplyCap": "10_000e18" + }, + "ezETH": { + "address": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.91, + "liquidationFactor": 0.94, + "supplyCap": "15_000e18" + } + } +} diff --git a/deployments/mainnet/wsteth/deploy.ts b/deployments/mainnet/wsteth/deploy.ts new file mode 100644 index 000000000..dfb9e1977 --- /dev/null +++ b/deployments/mainnet/wsteth/deploy.ts @@ -0,0 +1,77 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet, exp } from '../../../src/deploy'; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'); + const weth = await deploymentManager.existing('weth', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'); + const rsETHToETHPriceFeed = await deploymentManager.fromDep('rsETH:priceFeed', 'mainnet', 'weth'); + const wstETHToETHPriceFeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'weth'); + const ezETHToETHPriceFeed = await deploymentManager.fromDep('ezETH:priceFeed', 'mainnet', 'weth'); + const weETHToETHPriceFeed = await deploymentManager.fromDep('weETH:priceFeed', 'mainnet', 'weth'); + + // Deploy constant price feed for wstETH + const wstETHConstantPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/ConstantPriceFeed.sol', + [ + 8, // decimals + exp(1, 8) // constantPrice + ], + true + ); + + // Deploy reverse multiplicative price feed for rsETH + const rsETHScalingPriceFeed = await deploymentManager.deploy( + 'rsETH:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + rsETHToETHPriceFeed.address, // rsETH / ETH price feed + wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) + 8, // decimals + 'rsETH / wstETH price feed' // description + ], + true + ); + + // Deploy reverse multiplicative price feed for ezETH + const ezETHScalingPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + ezETHToETHPriceFeed.address, // ezETH / ETH price feed + wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) + 8, // decimals + 'ezETH / wstETH price feed' // description + ], + true + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'mainnet', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'mainnet', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'mainnet', 'usdc'); + + // Deploy all Comet-related contracts + const deployed = await deployComet(deploymentManager, deploySpec); + const { comet } = deployed; + + // Deploy Bulker + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/MainnetBulkerWithWstETHSupport.sol', + [ + await comet.governor(), // admin_ + weth.address, // weth_ + wstETH.address // wsteth_ + ], + true + ); + console.log('Bulker deployed at:', bulker.address); + + const bulkerNow = await deploymentManager.contract('bulker'); + console.log('Bulker now at:', bulkerNow? bulkerNow.address: 'N/A'); + + return { ...deployed, bulker }; +} diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts new file mode 100644 index 000000000..6d628e27c --- /dev/null +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -0,0 +1,237 @@ +import { ethers } from 'ethers'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; +import { expect } from 'chai'; +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const wstETHAmount = ethers.BigNumber.from(exp(20, 18)); + +export default migration('1723732097_configurate_and_ens', { + async prepare() { + return {}; + }, + + async enact(deploymentManager: DeploymentManager) { + const trace = deploymentManager.tracer(); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt', true); + const ethToWstETHPriceFeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'weth', true); + const price = (await ethToWstETHPriceFeed.latestRoundData())[1]; + const etherToWstETH = ethers.BigNumber.from(wstETHAmount).mul(price).div(exp(1,8)).toBigInt(); + + const { + comet, + cometAdmin, + configurator, + rewards, + COMP, + governor, + bulker, + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const currentChainId = 1; + const newMarketObject = { baseSymbol: 'wstETH', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + if (officialMarketsJSON[currentChainId]) { + officialMarketsJSON[currentChainId].push(newMarketObject); + } else { + officialMarketsJSON[currentChainId] = [newMarketObject]; + } + + const actions = [ + // 1. Set the Comet factory in configuration + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactory.address], + }, + // 2. Set the Comet configuration + { + 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 Comet and upgrade it to the new implementation + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + // 4. Set the reward configuration + { + contract: rewards, + signature: 'setRewardConfig(address,address)', + args: [comet.address, COMP.address], + }, + // 5. Deposit ether to get wstETH and transfer it to the Comet + { + target: bulker.address, + value: etherToWstETH, + signature: 'deposit(address)', + calldata: ethers.utils.defaultAbiCoder.encode( + ['address'], + [comet.address] + ), + }, + // 6. 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 cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504/4).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/10717773287) and [forum discussion](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth 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 Ethereum Mainnet cwstETHv3 market.'; + 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, + timelock, + COMP, + rsETH, + ezETH + } = await deploymentManager.getContracts(); + + // 1. & 2. & 3. + const rsETHInfo = await comet.getAssetInfoByAddress(rsETH.address); + const ezETHInfo = await comet.getAssetInfoByAddress(ezETH.address); + + expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); + expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); + + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(8 / 86400, 15, 18)); // 92592592592 + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(4 / 86400, 15, 18)); // 46296296296 + + // 4 + 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); + + expect((await comet.pauseGuardian()).toLowerCase()).to.be.eq('0xbbf3f1421d886e9b2c5d716b5192ac998af2012c'); + + // 5. & 6. + // expect reserves to be close to wstETHAmount +- 0.1 + expect(await comet.getReserves()).to.be.closeTo(wstETHAmount, exp(1, 17)); + + // 7. + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const ENSRegistry = await deploymentManager.existing('ENSRegistry', ENSRegistryAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); + expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); + expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); + expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: comet.address, + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + } +}); \ No newline at end of file diff --git a/deployments/mainnet/wsteth/relations.ts b/deployments/mainnet/wsteth/relations.ts new file mode 100644 index 000000000..4211ead18 --- /dev/null +++ b/deployments/mainnet/wsteth/relations.ts @@ -0,0 +1,34 @@ +import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + 'wstETH': { + artifact: 'contracts/bulkers/IWstETH.sol', + relations: { + stETH: { + field: async (wstETH) => wstETH.stETH() + } + } + }, + 'AppProxyUpgradeable': { + artifact: 'contracts/ERC20.sol:ERC20', + }, + 'UUPSProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + 'TransparentUpgradeableProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; + diff --git a/deployments/mainnet/wsteth/roots.json b/deployments/mainnet/wsteth/roots.json new file mode 100644 index 000000000..6d3a235ec --- /dev/null +++ b/deployments/mainnet/wsteth/roots.json @@ -0,0 +1,6 @@ +{ + "comet": "0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3", + "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", + "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", + "bulker": "0x2c776041CCFe903071AF44aa147368a9c8EEA518" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index c532b5ef2..d15407876 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -26,6 +26,7 @@ import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import mainnetUsdtRelationConfigMap from './deployments/mainnet/usdt/relations'; +import mainnetWstETHRelationConfigMap from './deployments/mainnet/wsteth/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import polygonUsdtRelationConfigMap from './deployments/polygon/usdt/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; @@ -353,7 +354,8 @@ const config: HardhatUserConfig = { mainnet: { usdc: mainnetRelationConfigMap, weth: mainnetWethRelationConfigMap, - usdt: mainnetUsdtRelationConfigMap + usdt: mainnetUsdtRelationConfigMap, + wsteth: mainnetWstETHRelationConfigMap }, polygon: { usdc: polygonRelationConfigMap, @@ -413,6 +415,11 @@ const config: HardhatUserConfig = { network: 'mainnet', deployment: 'usdt' }, + { + name: 'mainnet-wsteth', + network: 'mainnet', + deployment: 'wsteth' + }, { name: 'development', network: 'hardhat', diff --git a/plugins/deployment_manager/DeploymentManager.ts b/plugins/deployment_manager/DeploymentManager.ts index f15071978..4c53658b0 100644 --- a/plugins/deployment_manager/DeploymentManager.ts +++ b/plugins/deployment_manager/DeploymentManager.ts @@ -222,10 +222,11 @@ export class DeploymentManager { alias: Alias, network: string, deployment: string, + force?: boolean, otherAlias = alias ): Promise { const maybeExisting = await this.contract(alias); - if (!maybeExisting) { + if (!maybeExisting || force) { const trace = this.tracer(); const spider = await this.spiderOther(network, deployment); const contract = spider.contracts.get(otherAlias) as C; diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 1f4ed579f..e2d55889d 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -1,14 +1,28 @@ -import { scenario } from './context/CometContext'; +import { CometContext, scenario } from './context/CometContext'; import { constants, utils } from 'ethers'; import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; +async function hasWETHAsCollateralOrBase(ctx: CometContext): Promise { + const comet = await ctx.getComet(); + const bulker = await ctx.getBulker(); + const wrappedNativeToken = await bulker.wrappedNativeToken(); + if((await comet.baseToken()).toLowerCase() === wrappedNativeToken.toLowerCase()) return true; + const numAssets = await comet.numAssets(); + for (let i = 0; i < numAssets; i++) { + const { asset } = await comet.getAssetInfo(i); + if (asset.toLowerCase() === wrappedNativeToken.toLowerCase()) { + return true; + } + } +} + // XXX properly handle cases where asset0 is WETH scenario( 'Comet#bulker > (non-WETH base) all non-reward actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), + filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), supplyCaps: { $asset0: 5000, $asset1: 5000, @@ -86,6 +100,91 @@ scenario( } ); +scenario( + 'Comet#bulker > (wstETH base) all non-reward actions in one txn', + { + filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), + supplyCaps: { + $asset0: 5000, + $asset1: 5000, + }, + tokenBalances: { + albert: { $base: '== 0', $asset0: 5000, $asset1: 5000 }, + $comet: { $base: 5000 }, + }, + }, + async ({ comet, actors, bulker }, context) => { + const { albert, betty } = actors; + const wrappedNativeToken = await bulker.wrappedNativeToken(); + const baseAssetAddress = await comet.baseToken(); + const baseAsset = context.getAssetByAddress(baseAssetAddress); + const baseScale = (await comet.baseScale()).toBigInt(); + // if asset 0 is native token we took asset 1 + const { asset: asset0, scale: scale0 } = await comet.getAssetInfo(0); + const { asset: asset1, scale: scale1 } = await comet.getAssetInfo(1); + const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; + const collateralAsset = context.getAssetByAddress(collateralAssetAddress); + const collateralScale = scaleBN.toBigInt(); + const toSupplyCollateral = 5000n * collateralScale; + const toBorrowBase = 1000n * baseScale; + const toTransferBase = 500n * baseScale; + const toSupplyEth = exp(0.01, 18); + const toWithdrawEth = exp(0.005, 18); + + // Approvals + await collateralAsset.approve(albert, comet.address); + await albert.allow(bulker.address, true); + + // Initial expectations + expect(await collateralAsset.balanceOf(albert.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(0n); + expect(await comet.balanceOf(albert.address)).to.be.equal(0n); + + // Albert's actions: + // 1. Supplies 3000 units of collateral + // 2. Borrows 1000 base + // 3. Transfers 500 base to Betty + // 4. Supplies 0.01 ETH + // 5. Withdraws 0.005 ETH + const supplyAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, collateralAsset.address, toSupplyCollateral]); + const withdrawAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, baseAsset.address, toBorrowBase]); + const transferAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, betty.address, baseAsset.address, toTransferBase]); + const supplyEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toSupplyEth]); + const withdrawEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toWithdrawEth]); + const calldata = [ + supplyAssetCalldata, + withdrawAssetCalldata, + transferAssetCalldata + ]; + const actions = [ + await bulker.ACTION_SUPPLY_ASSET(), + await bulker.ACTION_WITHDRAW_ASSET(), + await bulker.ACTION_TRANSFER_ASSET() + ]; + + if(await hasWETHAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); + + // Final expectations + const baseIndexScale = (await comet.baseIndexScale()).toBigInt(); + const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); + const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); + expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); + if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); + expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); + + return txn; // return txn to measure gas + } +); + scenario( 'Comet#bulker > (WETH base) all non-reward actions in one txn', { @@ -165,7 +264,7 @@ scenario( scenario( 'Comet#bulker > (non-WETH base) all actions in one txn', { - filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'linea-goerli' }]), + filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'linea-goerli' }]), supplyCaps: { $asset0: 5000, $asset1: 5000, @@ -261,6 +360,110 @@ scenario( } ); + +scenario( + 'Comet#bulker > (wstETH base) all actions in one txn', + { + filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'linea-goerli' }]), + supplyCaps: { + $asset0: 5000, + $asset1: 5000, + }, + tokenBalances: { + albert: { $base: '== 1000000', $asset0: 5000, $asset1: 5000 }, + $comet: { $base: 5000 }, + } + }, + async ({ comet, actors, rewards, bulker }, context, world) => { + const { albert, betty } = actors; + const wrappedNativeToken = await bulker.wrappedNativeToken(); + const baseAssetAddress = await comet.baseToken(); + const baseAsset = context.getAssetByAddress(baseAssetAddress); + const baseScale = (await comet.baseScale()).toBigInt(); + // if asset 0 is native token we took asset 1 + const { asset: asset0, scale: scale0 } = await comet.getAssetInfo(0); + const { asset: asset1, scale: scale1 } = await comet.getAssetInfo(1); + const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; + const collateralAsset = context.getAssetByAddress(collateralAssetAddress); + const collateralScale = scaleBN.toBigInt(); + const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); + const toSupplyBase = 1_000_000n * baseScale; + const toSupplyCollateral = 5000n * collateralScale; + const toBorrowBase = 1000n * baseScale; + const toTransferBase = 500n * baseScale; + const toSupplyEth = exp(0.01, 18); + const toWithdrawEth = exp(0.005, 18); + + // Approvals + await baseAsset.approve(albert, comet.address); + await collateralAsset.approve(albert, comet.address); + await albert.allow(bulker.address, true); + + // Accrue some rewards to Albert, then transfer away Albert's supplied base + await albert.safeSupplyAsset({ asset: baseAssetAddress, amount: toSupplyBase }); + await world.increaseTime(86400); // fast forward a day + await albert.transferAsset({ dst: constants.AddressZero, asset: baseAssetAddress, amount: constants.MaxUint256 }); // transfer all base away + + // Initial expectations + expect(await collateralAsset.balanceOf(albert.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(0n); + expect(await comet.balanceOf(albert.address)).to.be.equal(0n); + const startingRewardBalance = await albert.getErc20Balance(rewardTokenAddress); + const rewardOwed = ((await rewards.callStatic.getRewardOwed(comet.address, albert.address)).owed).toBigInt(); + const expectedFinalRewardBalance = collateralAssetAddress === rewardTokenAddress ? + startingRewardBalance + rewardOwed - toSupplyCollateral : + startingRewardBalance + rewardOwed; + + // Albert's actions: + // 1. Supplies 3000 units of collateral + // 2. Borrows 1000 base + // 3. Transfers 500 base to Betty + // 4. Supplies 0.01 ETH + // 5. Withdraws 0.005 ETH + // 6. Claim rewards + const supplyAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, collateralAsset.address, toSupplyCollateral]); + const withdrawAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, baseAsset.address, toBorrowBase]); + const transferAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, betty.address, baseAsset.address, toTransferBase]); + const supplyEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toSupplyEth]); + const withdrawEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toWithdrawEth]); + const claimRewardCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'bool'], [comet.address, rewards.address, albert.address, true]); + const calldata = [ + supplyAssetCalldata, + withdrawAssetCalldata, + transferAssetCalldata, + claimRewardCalldata + ]; + const actions = [ + await bulker.ACTION_SUPPLY_ASSET(), + await bulker.ACTION_WITHDRAW_ASSET(), + await bulker.ACTION_TRANSFER_ASSET(), + await bulker.ACTION_CLAIM_REWARD(), + ]; + + if(await hasWETHAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); + + // Final expectations + const baseIndexScale = (await comet.baseIndexScale()).toBigInt(); + const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); + const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); + expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); + if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); + expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); + + return txn; // return txn to measure gas + } +); + scenario( 'Comet#bulker > (WETH base) all actions in one txn', { diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index bf21963b7..8c504b8e1 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -537,7 +537,7 @@ for (let i = 0; i < MAX_ASSETS; i++) { scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when value is beneath liquidationThreshold`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), tokenBalances: { $comet: { $base: 100000 }, }, @@ -648,7 +648,7 @@ scenario( scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when maxAmountToPurchase=0`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), tokenBalances: { $comet: { $base: 100000 }, }, @@ -778,7 +778,7 @@ scenario( upgrade: { targetReserves: exp(20_000, 18) }, - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}]), tokenBalances: async (ctx) => ( { $comet: { diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 03f59d4d8..3fd02d954 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -328,7 +328,7 @@ export async function fetchLogs( } async function redeployRenzoOracle(dm: DeploymentManager){ - if(dm.network === 'mainnet' && dm.deployment === 'weth') { + if(dm.network === 'mainnet') { // renzo admin 0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A const renzoOracle = new Contract( '0x5a12796f7e7EBbbc8a402667d266d2e65A814042', diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 3a03a4e46..7381208a9 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -88,6 +88,7 @@ export const WHALES = { '0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e', '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', + '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' ], polygon: [ diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index f68286910..cf69fdc2a 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -231,3 +231,124 @@ task('migrate', 'Runs migration') } } ); + +task('deploy_and_migrate', 'Runs deploy and migration') + .addPositionalParam('migration', 'name of migration') + .addOptionalParam('impersonate', 'the governor will impersonate the passed account for proposals [only when simulating]') + .addFlag('simulate', 'only simulates the blockchain effects') + .addFlag('noDeploy', 'skip the actual deploy step') + .addFlag('noVerify', 'do not verify any contracts') + .addFlag('noVerifyImpl', 'do not verify the impl contract') + .addFlag('overwrite', 'overwrites cache') + .addFlag('prepare', 'runs preparation [defaults to true if enact not specified]') + .addFlag('enact', 'enacts migration [implies prepare]') + .addFlag('noEnacted', 'do not write enacted to the migration script') + .addParam('deployment', 'The deployment to deploy') + .setAction( + async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate, noDeploy, noVerify, noVerifyImpl }, env) => { + const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env; + const network = env.network.name; + const tag = `${network}/${deployment}`; + const dm = new DeploymentManager( + network, + deployment, + maybeForkEnv, + { + writeCacheToDisk: !simulate || overwrite, // Don't write to disk when simulating, unless overwrite is set + verificationStrategy: 'lazy', + } + ); + + if (noDeploy) { + // Don't run the deploy script + } else { + try { + const overrides = undefined; // TODO: pass through cli args + const delta = await dm.runDeployScript(overrides ?? { allMissing: true }); + console.log(`[${tag}] Deployed ${dm.counter} contracts, spent ${dm.spent} Ξ`); + console.log(`[${tag}]\n${dm.diffDelta(delta)}`); + } catch (e) { + console.log(`[${tag}] Failed to deploy with error: ${e}`); + } + } + + const verify = noVerify ? false : !simulate; + const desc = verify ? 'Verify' : 'Would verify'; + if (noVerify && simulate) { + // Don't even print if --no-verify is set with --simulate + } else { + await dm.verifyContracts(async (address, args) => { + if (args.via === 'buildfile') { + const { contract: _, ...rest } = args; + console.log(`[${tag}] ${desc} ${address}:`, rest); + } else { + console.log(`[${tag}] ${desc} ${address}:`, args); + } + return verify; + }); + + if (noVerifyImpl) { + // Don't even try if --no-verify-impl + } else { + // Maybe verify the comet impl too + const comet = await dm.contract('comet'); + const cometImpl = await dm.contract('comet:implementation'); + const configurator = await dm.contract('configurator'); + const config = await configurator.getConfiguration(comet.address); + const args: VerifyArgs = { + via: 'artifacts', + address: cometImpl.address, + constructorArguments: [config] + }; + console.log(`[${tag}] ${desc} ${cometImpl.address}:`, args); + if (verify) { + await dm.verifyContract(args); + } + } + } + await dm.spider(); + + let governanceDm: DeploymentManager; + const base = env.config.scenario.bases.find(b => b.network === network && b.deployment === deployment); + const isBridgedDeployment = base.auxiliaryBase !== undefined; + const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined; + + if (governanceBase) { + const governanceEnv = hreForBase(governanceBase, simulate); + governanceDm = new DeploymentManager( + governanceBase.network, + governanceBase.deployment, + governanceEnv, + { + writeCacheToDisk: !simulate || overwrite, // Don't write to disk when simulating, unless overwrite is set + verificationStrategy: 'eager', // We use eager here to verify contracts right after they are deployed + } + ); + await governanceDm.spider(); + } else { + governanceDm = dm; + } + + if (impersonate && !simulate) { + throw new Error('Cannot impersonate an address if not simulating a migration. Please specify --simulate to simulate.'); + } else if (impersonate && simulate) { + const signer = await impersonateAddress(governanceDm, impersonate, 10n ** 18n); + governanceDm._signers.unshift(signer); + } + + const migrationPath = `${__dirname}/../../deployments/${network}/${deployment}/migrations/${migrationName}.ts`; + const [migration] = await loadMigrations([migrationPath]); + if (!migration) { + throw new Error(`Unknown migration for network ${network}/${deployment}: \`${migrationName}\`.`); + } + if (!prepare && !enact) { + prepare = true; + } + + await runMigration(dm, governanceDm, prepare, enact, migration, overwrite); + + if (enact && !noEnacted) { + await writeEnacted(migration, dm, true); + } + + }); From 9ac1b0466efc289f34e9ed3bb462f88eadccc848 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 3 Oct 2024 00:14:51 +0300 Subject: [PATCH 10/22] Add cbBTC as collateral to USDC market on Base (#925) Co-authored-by: GitHub Actions Bot <> --- .../1726478432_add_cbbtc_as_collateral.ts | 148 ++++++++++++++++++ scenario/constraints/ProposalConstraint.ts | 6 + 2 files changed, 154 insertions(+) create mode 100644 deployments/base/usdc/migrations/1726478432_add_cbbtc_as_collateral.ts diff --git a/deployments/base/usdc/migrations/1726478432_add_cbbtc_as_collateral.ts b/deployments/base/usdc/migrations/1726478432_add_cbbtc_as_collateral.ts new file mode 100644 index 000000000..f2f472155 --- /dev/null +++ b/deployments/base/usdc/migrations/1726478432_add_cbbtc_as_collateral.ts @@ -0,0 +1,148 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const CBBTC_ADDRESS = '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'; +const CBBTC_USD_PRICE_FEED_ADDRESS = '0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D'; +let newPriceFeedAddress: string; + +export default migration('1726478432_add_cbbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _cbBTCPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + CBBTC_USD_PRICE_FEED_ADDRESS, // BTC / USD price feed + 8, // decimals + ] + ); + return { cbBTCPriceFeedAddress: _cbBTCPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { cbBTCPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const cbBTC = await deploymentManager.existing( + 'cbBTC', + CBBTC_ADDRESS, + 'base', + 'contracts/ERC20.sol:ERC20' + ); + const cbBTCPriceFeed = await deploymentManager.existing( + 'cbBTC:priceFeed', + cbBTCPriceFeedAddress, + 'base' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, baseL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: cbBTC.address, + priceFeed: cbBTCPriceFeed.address, + decimals: await cbBTC.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(45, 8), + }; + + newPriceFeedAddress = cbBTCPriceFeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add cbBTC as collateral into cUSDCv3 on Base\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add cbBTC into cUSDCv3 on Base network. This proposal takes the governance steps recommended and necessary to update a Compound III USDC market on Base. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/925) and [forum discussion](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689).\n\n\n## Proposal Actions\n\nThe first proposal action adds cbBTC to the USDC Comet on Base. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Base.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const cbBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const cbBTCAssetConfig = { + asset: CBBTC_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 8, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(45, 8), + }; + + // 1. Compare proposed asset config with Comet asset info + const cbBTCAssetInfo = await comet.getAssetInfoByAddress(CBBTC_ADDRESS); + expect(cbBTCAssetIndex).to.be.equal(cbBTCAssetInfo.offset); + expect(cbBTCAssetConfig.asset).to.be.equal(cbBTCAssetInfo.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(cbBTCAssetInfo.priceFeed); + expect(exp(1, cbBTCAssetConfig.decimals)).to.be.equal(cbBTCAssetInfo.scale); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(cbBTCAssetInfo.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cbBTCAssetInfo.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(cbBTCAssetInfo.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(cbBTCAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorCbBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[cbBTCAssetIndex]; + expect(cbBTCAssetConfig.asset).to.be.equal(configuratorCbBTCAssetConfig.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(configuratorCbBTCAssetConfig.priceFeed); + expect(cbBTCAssetConfig.decimals).to.be.equal(configuratorCbBTCAssetConfig.decimals); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorCbBTCAssetConfig.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorCbBTCAssetConfig.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(configuratorCbBTCAssetConfig.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(configuratorCbBTCAssetConfig.supplyCap); + }, +}); diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 83c309740..4c0b970d1 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,6 +62,12 @@ export class ProposalConstraint implements StaticConstra ); } + // temporary hack to skip proposal 329 + if (proposal.id.eq(329)) { + console.log('Skipping proposal 329'); + continue; + } + try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); From dc4a29eb9fddf2ec709689165811e38b5fcc59e0 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 3 Oct 2024 02:51:16 +0300 Subject: [PATCH 11/22] Add cbBTC as collateral to USDC market on Mainnet (#921) Co-authored-by: GitHub Actions Bot <> --- .../1726215532_add_cbbtc_as_collateral.ts | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 deployments/mainnet/usdc/migrations/1726215532_add_cbbtc_as_collateral.ts diff --git a/deployments/mainnet/usdc/migrations/1726215532_add_cbbtc_as_collateral.ts b/deployments/mainnet/usdc/migrations/1726215532_add_cbbtc_as_collateral.ts new file mode 100644 index 000000000..0c11923fa --- /dev/null +++ b/deployments/mainnet/usdc/migrations/1726215532_add_cbbtc_as_collateral.ts @@ -0,0 +1,125 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const CBBTC_ADDRESS = '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'; +const CBBTC_USD_PRICE_FEED = '0x2665701293fCbEB223D11A08D826563EDcCE423A'; + +let priceFeedAddress: string; + +export default migration('1726215532_add_cbbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _cbBTCPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + CBBTC_USD_PRICE_FEED, // cbBTC / USD price feed + 8 // decimals + ] + ); + return { cbBTCPriceFeedAddress: _cbBTCPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, _, { cbBTCPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + + const cbBTC = await deploymentManager.existing( + 'cbBTC', + CBBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const cbBTCPriceFeed = await deploymentManager.existing( + 'cbBTC:priceFeed', + cbBTCPriceFeedAddress, + 'mainnet' + ); + priceFeedAddress = cbBTCPriceFeed.address; + const { + governor, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const newAssetConfig = { + asset: cbBTC.address, + priceFeed: cbBTCPriceFeed.address, + decimals: await cbBTC.decimals(), + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + const mainnetActions = [ + // 1. Add cbBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, newAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add cbBTC as collateral into cUSDCv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add cbBTC into cUSDCv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDC market on Ethereum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/921) and [forum discussion](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689).\n\n\n## Proposal Actions\n\nThe first proposal action adds cbBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const cbBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const cbBTCAssetConfig = { + asset: CBBTC_ADDRESS, + priceFeed: priceFeedAddress, + decimals: 8, + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + // 1. Compare proposed asset config with Comet asset info + const cbBTCAssetInfo = await comet.getAssetInfoByAddress(CBBTC_ADDRESS); + expect(cbBTCAssetIndex).to.be.equal(cbBTCAssetInfo.offset); + expect(cbBTCAssetConfig.asset).to.be.equal(cbBTCAssetInfo.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(cbBTCAssetInfo.priceFeed); + expect(exp(1, cbBTCAssetConfig.decimals)).to.be.equal(cbBTCAssetInfo.scale); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(cbBTCAssetInfo.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cbBTCAssetInfo.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(cbBTCAssetInfo.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(cbBTCAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorcbBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[cbBTCAssetIndex]; + expect(cbBTCAssetConfig.asset).to.be.equal(configuratorcbBTCAssetConfig.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(configuratorcbBTCAssetConfig.priceFeed); + expect(cbBTCAssetConfig.decimals).to.be.equal(configuratorcbBTCAssetConfig.decimals); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(configuratorcbBTCAssetConfig.supplyCap); + }, +}); From 29181b1747b7c0e4ab21697cf7f74994e35a4e5c Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 3 Oct 2024 22:24:55 +0300 Subject: [PATCH 12/22] Add cbBTC as collateral to USDT market on Mainnet (#922) --- .../1726218459_add_cbbtc_as_collateral.ts | 125 ++++++++++++++++++ scenario/SupplyScenario.ts | 2 +- scenario/constraints/SupplyCapConstraint.ts | 5 +- 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 deployments/mainnet/usdt/migrations/1726218459_add_cbbtc_as_collateral.ts diff --git a/deployments/mainnet/usdt/migrations/1726218459_add_cbbtc_as_collateral.ts b/deployments/mainnet/usdt/migrations/1726218459_add_cbbtc_as_collateral.ts new file mode 100644 index 000000000..c5ed9978c --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1726218459_add_cbbtc_as_collateral.ts @@ -0,0 +1,125 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const CBBTC_ADDRESS = '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'; +const CBBTC_USD_PRICE_FEED = '0x2665701293fCbEB223D11A08D826563EDcCE423A'; + +let priceFeedAddress: string; + +export default migration('1726218459_add_cbbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _cbBTCPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + CBBTC_USD_PRICE_FEED, // cbBTC / USD price feed + 8 // decimals + ] + ); + return { cbBTCPriceFeedAddress: _cbBTCPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, _, { cbBTCPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + + const cbBTC = await deploymentManager.existing( + 'cbBTC', + CBBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const cbBTCPriceFeed = await deploymentManager.existing( + 'cbBTC:priceFeed', + cbBTCPriceFeedAddress, + 'mainnet' + ); + priceFeedAddress = cbBTCPriceFeed.address; + const { + governor, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const newAssetConfig = { + asset: cbBTC.address, + priceFeed: cbBTCPriceFeed.address, + decimals: await cbBTC.decimals(), + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + const mainnetActions = [ + // 1. Add cbBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, newAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add cbBTC as collateral into cUSDTv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add cbBTC into cUSDTv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDT market on Ethereum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/922) and [forum discussion](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689).\n\n\n## Proposal Actions\n\nThe first proposal action adds cbBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const cbBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const cbBTCAssetConfig = { + asset: CBBTC_ADDRESS, + priceFeed: priceFeedAddress, + decimals: 8, + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + // 1. Compare proposed asset config with Comet asset info + const cbBTCAssetInfo = await comet.getAssetInfoByAddress(CBBTC_ADDRESS); + expect(cbBTCAssetIndex).to.be.equal(cbBTCAssetInfo.offset); + expect(cbBTCAssetConfig.asset).to.be.equal(cbBTCAssetInfo.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(cbBTCAssetInfo.priceFeed); + expect(exp(1, cbBTCAssetConfig.decimals)).to.be.equal(cbBTCAssetInfo.scale); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(cbBTCAssetInfo.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cbBTCAssetInfo.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(cbBTCAssetInfo.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(cbBTCAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorcbBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[cbBTCAssetIndex]; + expect(cbBTCAssetConfig.asset).to.be.equal(configuratorcbBTCAssetConfig.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(configuratorcbBTCAssetConfig.priceFeed); + expect(cbBTCAssetConfig.decimals).to.be.equal(configuratorcbBTCAssetConfig.decimals); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(configuratorcbBTCAssetConfig.supplyCap); + }, +}); diff --git a/scenario/SupplyScenario.ts b/scenario/SupplyScenario.ts index f9075208b..7082f87a6 100644 --- a/scenario/SupplyScenario.ts +++ b/scenario/SupplyScenario.ts @@ -300,7 +300,7 @@ scenario( const utilization = await comet.getUtilization(); const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); - expectApproximately(await albert.getCometBaseBalance(), -999n * scale, getInterest(999n * scale, borrowRate, 1n) + 1n); + expectApproximately(await albert.getCometBaseBalance(), -999n * scale, getInterest(999n * scale, borrowRate, 1n) + 2n); // Albert repays 1000 units of base borrow await baseAsset.approve(albert, comet.address); diff --git a/scenario/constraints/SupplyCapConstraint.ts b/scenario/constraints/SupplyCapConstraint.ts index 8a71312af..1842b73f0 100644 --- a/scenario/constraints/SupplyCapConstraint.ts +++ b/scenario/constraints/SupplyCapConstraint.ts @@ -7,7 +7,10 @@ import { ComparisonOp, getAssetFromName, parseAmount } from '../utils'; export class SupplyCapConstraint implements Constraint { async solve(requirements: R, _initialContext: T) { - const supplyCaps = requirements.supplyCaps; + let supplyCaps = requirements.supplyCaps; + if (typeof supplyCaps === 'function') { + supplyCaps = await supplyCaps(_initialContext); + } if (supplyCaps !== undefined) { const solutions: Solution[] = []; solutions.push(async function barelyMeet(context: T) { From 4ae5f8d9b9f715f0d862d79cfa8fb531379f6f1a Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 3 Oct 2024 22:25:19 +0300 Subject: [PATCH 13/22] Add cbBTC as collateral to WETH market on Mainnet (#923) --- .../1726228702_add_cbbtc_as_collateral.ts | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 deployments/mainnet/weth/migrations/1726228702_add_cbbtc_as_collateral.ts diff --git a/deployments/mainnet/weth/migrations/1726228702_add_cbbtc_as_collateral.ts b/deployments/mainnet/weth/migrations/1726228702_add_cbbtc_as_collateral.ts new file mode 100644 index 000000000..c84bae952 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1726228702_add_cbbtc_as_collateral.ts @@ -0,0 +1,128 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const CBBTC_ADDRESS = '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'; +const CBBTC_USD_PRICE_FEED = '0x2665701293fCbEB223D11A08D826563EDcCE423A'; +const ETH_USD_PRICE_FEED_ADDRESS = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'; + +let priceFeedAddress: string; + +export default migration('1726228702_add_cbbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _cbBTCPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + CBBTC_USD_PRICE_FEED, // cbBTC / USD price feed + ETH_USD_PRICE_FEED_ADDRESS, // USD / ETH price feed + 8, // decimals + 'cbBTC / ETH price feed', // description + ] + ); + return { cbBTCPriceFeedAddress: _cbBTCPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, _, { cbBTCPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + + const cbBTC = await deploymentManager.existing( + 'cbBTC', + CBBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const cbBTCPriceFeed = await deploymentManager.existing( + 'cbBTC:priceFeed', + cbBTCPriceFeedAddress, + 'mainnet' + ); + priceFeedAddress = cbBTCPriceFeed.address; + const { + governor, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const newAssetConfig = { + asset: cbBTC.address, + priceFeed: cbBTCPriceFeed.address, + decimals: await cbBTC.decimals(), + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + const mainnetActions = [ + // 1. Add cbBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, newAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add cbBTC as collateral into cWETHv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add cbBTC into cWETHv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Ethereum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/923) and [forum discussion](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689).\n\n\n## Proposal Actions\n\nThe first proposal action adds cbBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const cbBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const cbBTCAssetConfig = { + asset: CBBTC_ADDRESS, + priceFeed: priceFeedAddress, + decimals: 8, + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(93, 8), + }; + + // 1. Compare proposed asset config with Comet asset info + const cbBTCAssetInfo = await comet.getAssetInfoByAddress(CBBTC_ADDRESS); + expect(cbBTCAssetIndex).to.be.equal(cbBTCAssetInfo.offset); + expect(cbBTCAssetConfig.asset).to.be.equal(cbBTCAssetInfo.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(cbBTCAssetInfo.priceFeed); + expect(exp(1, cbBTCAssetConfig.decimals)).to.be.equal(cbBTCAssetInfo.scale); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(cbBTCAssetInfo.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cbBTCAssetInfo.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(cbBTCAssetInfo.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(cbBTCAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorcbBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[cbBTCAssetIndex]; + expect(cbBTCAssetConfig.asset).to.be.equal(configuratorcbBTCAssetConfig.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(configuratorcbBTCAssetConfig.priceFeed); + expect(cbBTCAssetConfig.decimals).to.be.equal(configuratorcbBTCAssetConfig.decimals); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(configuratorcbBTCAssetConfig.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(configuratorcbBTCAssetConfig.supplyCap); + }, +}); From ddfdf2dfd49176430e6cd89fef666dc3c515249d Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:26:06 -0400 Subject: [PATCH 14/22] Add cbBTC as collateral to WETH market on Base (#938) Co-authored-by: Mikhailo Shabodyash Co-authored-by: GitHub Actions Bot <> --- .../1726480777_add_cbbtc_as_collateral.ts | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 deployments/base/weth/migrations/1726480777_add_cbbtc_as_collateral.ts diff --git a/deployments/base/weth/migrations/1726480777_add_cbbtc_as_collateral.ts b/deployments/base/weth/migrations/1726480777_add_cbbtc_as_collateral.ts new file mode 100644 index 000000000..d89512145 --- /dev/null +++ b/deployments/base/weth/migrations/1726480777_add_cbbtc_as_collateral.ts @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; + +const CBBTC_ADDRESS = '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'; +const CBBTC_USD_PRICE_FEED_ADDRESS = '0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D'; +const ETH_USD_PRICE_FEED_ADDRESS = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70'; +let newPriceFeedAddress: string; + +export default migration('1726480777_add_cbbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _cbBTCPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + CBBTC_USD_PRICE_FEED_ADDRESS, // cbBTC / USD price feed + ETH_USD_PRICE_FEED_ADDRESS, // USD / ETH price feed + 8, // decimals + 'cbBTC / ETH price feed', // description + ] + ); + + return { cbBTCPriceFeedAddress: _cbBTCPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { cbBTCPriceFeedAddress } + ) => { + const trace = deploymentManager.tracer(); + + const cbBTC = await deploymentManager.existing( + 'cbBTC', + CBBTC_ADDRESS, + 'base', + 'contracts/ERC20.sol:ERC20' + ); + const cbBTCPriceFeed = await deploymentManager.existing( + 'cbBTC:priceFeed', + cbBTCPriceFeedAddress, + 'base' + ); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const { governor, baseL1CrossDomainMessenger } = await govDeploymentManager.getContracts(); + + const newAssetConfig = { + asset: cbBTC.address, + priceFeed: cbBTCPriceFeed.address, + decimals: await cbBTC.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(45, 8), + }; + + newPriceFeedAddress = cbBTCPriceFeed.address; + + const addAssetCalldata = await calldata( + configurator.populateTransaction.addAsset(comet.address, newAssetConfig) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + [ + 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + 'deployAndUpgradeTo(address,address)', + ], + [addAssetCalldata, deployAndUpgradeToCalldata], + ] + ); + + const mainnetActions = [ + // Send the proposal to the L2 bridge + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000] + }, + ]; + + const description = '# Add cbBTC as collateral into cWETHv3 on Base\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add cbBTC into cWETHv3 on Base network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Base. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/926) and [forum discussion](https://www.comp.xyz/t/add-collateral-cbbtc-to-weth-market-on-base-and-mainnet/5689).\n\n\n## Proposal Actions\n\nThe first proposal action adds cbBTC to the WETH Comet on Base. This sends the encoded `addAsset` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Base.'; + 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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const cbBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const cbBTCAssetConfig = { + asset: CBBTC_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 8, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(45, 8), + }; + + // 1. Compare proposed asset config with Comet asset info + const cbBTCAssetInfo = await comet.getAssetInfoByAddress(CBBTC_ADDRESS); + expect(cbBTCAssetIndex).to.be.equal(cbBTCAssetInfo.offset); + expect(cbBTCAssetConfig.asset).to.be.equal(cbBTCAssetInfo.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(cbBTCAssetInfo.priceFeed); + expect(exp(1, cbBTCAssetConfig.decimals)).to.be.equal(cbBTCAssetInfo.scale); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(cbBTCAssetInfo.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cbBTCAssetInfo.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(cbBTCAssetInfo.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(cbBTCAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorCbBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[cbBTCAssetIndex]; + expect(cbBTCAssetConfig.asset).to.be.equal(configuratorCbBTCAssetConfig.asset); + expect(cbBTCAssetConfig.priceFeed).to.be.equal(configuratorCbBTCAssetConfig.priceFeed); + expect(cbBTCAssetConfig.decimals).to.be.equal(configuratorCbBTCAssetConfig.decimals); + expect(cbBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorCbBTCAssetConfig.borrowCollateralFactor); + expect(cbBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorCbBTCAssetConfig.liquidateCollateralFactor); + expect(cbBTCAssetConfig.liquidationFactor).to.be.equal(configuratorCbBTCAssetConfig.liquidationFactor); + expect(cbBTCAssetConfig.supplyCap).to.be.equal(configuratorCbBTCAssetConfig.supplyCap); + }, +}); From c94c1aac66876d300b2dfa51557f2bba4e0e5c57 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 18 Oct 2024 20:20:06 +0300 Subject: [PATCH 15/22] Add tBTC as collateral to USDC market on Mainnet (#935) Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> --- .../1728053491_add_tbtc_collateral.ts | 133 ++++++++++++++++++ scenario/MainnetBulkerScenario.ts | 6 +- scenario/constraints/ProposalConstraint.ts | 12 +- 3 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 deployments/mainnet/usdc/migrations/1728053491_add_tbtc_collateral.ts diff --git a/deployments/mainnet/usdc/migrations/1728053491_add_tbtc_collateral.ts b/deployments/mainnet/usdc/migrations/1728053491_add_tbtc_collateral.ts new file mode 100644 index 000000000..9f27b3887 --- /dev/null +++ b/deployments/mainnet/usdc/migrations/1728053491_add_tbtc_collateral.ts @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const TBTC_ADDRESS = '0x18084fbA666a33d37592fA2633fD49a74DD93a88'; +const TBTC_TO_USD_PRICE_FEED = '0x8350b7De6a6a2C1368E7D4Bd968190e13E354297'; + +let newPriceFeedAddress: string; + +export default migration('1728053491_add_tbtc_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const tBTCMultiplicativePriceFeed = await deploymentManager.deploy( + 'tBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + TBTC_TO_USD_PRICE_FEED, // tBTC / USD price feed + 8, // decimals + ] + ); + return { tBTCPriceFeedAddress: tBTCMultiplicativePriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { tBTCPriceFeedAddress }) { + + const trace = deploymentManager.tracer(); + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCPriceFeed = await deploymentManager.existing( + 'tBTC:priceFeed', + tBTCPriceFeedAddress, + 'mainnet' + ); + + newPriceFeedAddress = tBTCPriceFeedAddress; + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: tBTCPriceFeed.address, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(285, 18), + }; + + const mainnetActions = [ + // 1. Add tBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, tBTCAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add tBTC as collateral into cUSDCv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add tBTC into cUSDCv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDC market on Ethereum. 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). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399/12).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/935) and [forum discussion](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399).\n\n\n## Proposal Actions\n\nThe first action adds tBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const tBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: newPriceFeedAddress, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(285, 18), + }; + + // 1. Compare tBTC asset config with Comet and Configurator asset info + const cometTBTCAssetInfo = await comet.getAssetInfoByAddress(TBTC_ADDRESS); + expect(tBTCAssetIndex).to.be.equal(cometTBTCAssetInfo.offset); + expect(tBTCAssetConfig.asset).to.be.equal(cometTBTCAssetInfo.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(cometTBTCAssetInfo.priceFeed); + expect(exp(1, tBTCAssetConfig.decimals)).to.be.equal(cometTBTCAssetInfo.scale); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(cometTBTCAssetInfo.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cometTBTCAssetInfo.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(cometTBTCAssetInfo.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(cometTBTCAssetInfo.supplyCap); + + const configuratorTBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[tBTCAssetIndex]; + expect(tBTCAssetConfig.asset).to.be.equal(configuratorTBTCAssetConfig.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(configuratorTBTCAssetConfig.priceFeed); + expect(tBTCAssetConfig.decimals).to.be.equal(configuratorTBTCAssetConfig.decimals); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(configuratorTBTCAssetConfig.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(configuratorTBTCAssetConfig.supplyCap); + }, +}); \ No newline at end of file diff --git a/scenario/MainnetBulkerScenario.ts b/scenario/MainnetBulkerScenario.ts index 997600cb2..590aacaba 100644 --- a/scenario/MainnetBulkerScenario.ts +++ b/scenario/MainnetBulkerScenario.ts @@ -52,9 +52,9 @@ scenario( const toSupplyStEth = exp(.1, 18); - await context.sourceTokens(toSupplyStEth, new CometAsset(stETH), albert); + await context.sourceTokens(toSupplyStEth + 2n, new CometAsset(stETH), albert); - expect(await stETH.balanceOf(albert.address)).to.be.approximately(toSupplyStEth, 2); + expect(await stETH.balanceOf(albert.address)).to.be.greaterThanOrEqual(toSupplyStEth); // approve bulker as albert await stETH.connect(albert.signer).approve(bulker.address, toSupplyStEth); @@ -68,7 +68,7 @@ scenario( await albert.invoke({ actions, calldata }); - expect(await stETH.balanceOf(albert.address)).to.be.equal(0n); + expect(await stETH.balanceOf(albert.address)).to.be.approximately(0n, 1n); expectApproximately( (await comet.collateralBalanceOf(albert.address, wstETH.address)).toBigInt(), (await wstETH.getWstETHByStETH(toSupplyStEth)).toBigInt(), diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 4c0b970d1..da4899ea3 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,9 +62,15 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 329 - if (proposal.id.eq(329)) { - console.log('Skipping proposal 329'); + // temporary hack to skip proposal 339 + if (proposal.id.eq(339)) { + console.log('Skipping proposal 339'); + continue; + } + + // temporary hack to skip proposal 340 + if (proposal.id.eq(340)) { + console.log('Skipping proposal 340'); continue; } From a156e257cf889480cc4124c8ae9f64ff44ea2880 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 18 Oct 2024 20:27:15 +0300 Subject: [PATCH 16/22] Add tBTC as collateral to USDT market on Mainnet (#936) Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: dmitriy-woof-software --- .../1728054806_add_tbtc_collateral.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 deployments/mainnet/usdt/migrations/1728054806_add_tbtc_collateral.ts diff --git a/deployments/mainnet/usdt/migrations/1728054806_add_tbtc_collateral.ts b/deployments/mainnet/usdt/migrations/1728054806_add_tbtc_collateral.ts new file mode 100644 index 000000000..095dd9734 --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1728054806_add_tbtc_collateral.ts @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const TBTC_ADDRESS = '0x18084fbA666a33d37592fA2633fD49a74DD93a88'; +const TBTC_TO_USD_PRICE_FEED = '0x8350b7De6a6a2C1368E7D4Bd968190e13E354297'; + +let newPriceFeedAddress: string; + +export default migration('1728054806_add_tbtc_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const tBTCMultiplicativePriceFeed = await deploymentManager.deploy( + 'tBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + TBTC_TO_USD_PRICE_FEED, // tBTC / USD price feed + 8, // decimals + ] + ); + return { tBTCPriceFeedAddress: tBTCMultiplicativePriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { tBTCPriceFeedAddress }) { + + const trace = deploymentManager.tracer(); + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCPriceFeed = await deploymentManager.existing( + 'tBTC:priceFeed', + tBTCPriceFeedAddress, + 'mainnet' + ); + + newPriceFeedAddress = tBTCPriceFeedAddress; + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: tBTCPriceFeed.address, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(285, 18), + }; + + const mainnetActions = [ + // 1. Add tBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, tBTCAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add tBTC as collateral into cUSDTv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add tBTC into cUSDTv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDT market on Ethereum. 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). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399/12).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/936) and [forum discussion](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399).\n\n\n## Proposal Actions\n\nThe first action adds tBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const tBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: newPriceFeedAddress, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(285, 18), + }; + + // 1. Compare tBTC asset config with Comet and Configurator asset info + const cometTBTCAssetInfo = await comet.getAssetInfoByAddress(TBTC_ADDRESS); + expect(tBTCAssetIndex).to.be.equal(cometTBTCAssetInfo.offset); + expect(tBTCAssetConfig.asset).to.be.equal(cometTBTCAssetInfo.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(cometTBTCAssetInfo.priceFeed); + expect(exp(1, tBTCAssetConfig.decimals)).to.be.equal(cometTBTCAssetInfo.scale); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(cometTBTCAssetInfo.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cometTBTCAssetInfo.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(cometTBTCAssetInfo.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(cometTBTCAssetInfo.supplyCap); + + const configuratorTBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[tBTCAssetIndex]; + expect(tBTCAssetConfig.asset).to.be.equal(configuratorTBTCAssetConfig.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(configuratorTBTCAssetConfig.priceFeed); + expect(tBTCAssetConfig.decimals).to.be.equal(configuratorTBTCAssetConfig.decimals); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(configuratorTBTCAssetConfig.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(configuratorTBTCAssetConfig.supplyCap); + }, +}); \ No newline at end of file From b19a198812cea7ae322c5061b67944eb9b95329f Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 18 Oct 2024 20:28:51 +0300 Subject: [PATCH 17/22] Add tBTC as collateral to WETH market on Mainnet (#934) Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .../1727880771_add_tbtc_collateral.ts | 136 ++++++++++++++++++ scenario/constraints/NativeTokenConstraint.ts | 2 +- 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 deployments/mainnet/weth/migrations/1727880771_add_tbtc_collateral.ts diff --git a/deployments/mainnet/weth/migrations/1727880771_add_tbtc_collateral.ts b/deployments/mainnet/weth/migrations/1727880771_add_tbtc_collateral.ts new file mode 100644 index 000000000..a60516a20 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1727880771_add_tbtc_collateral.ts @@ -0,0 +1,136 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const TBTC_ADDRESS = '0x18084fbA666a33d37592fA2633fD49a74DD93a88'; +const TBTC_TO_USD_PRICE_FEED = '0x8350b7De6a6a2C1368E7D4Bd968190e13E354297'; +const ETH_TO_USD_PRICE_FEED = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'; + +let newPriceFeedAddress: string; + +export default migration('1727880771_add_tbtc_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const tBTCMultiplicativePriceFeed = await deploymentManager.deploy( + 'tBTC:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + TBTC_TO_USD_PRICE_FEED, // tBTC / USD price feed + ETH_TO_USD_PRICE_FEED, // ETH / USD (reversed) price feed + 8, // decimals + 'tBTC / ETH price feed' // description + ] + ); + return { tBTCPriceFeedAddress: tBTCMultiplicativePriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { tBTCPriceFeedAddress }) { + + const trace = deploymentManager.tracer(); + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCPriceFeed = await deploymentManager.existing( + 'tBTC:priceFeed', + tBTCPriceFeedAddress, + 'mainnet' + ); + + newPriceFeedAddress = tBTCPriceFeedAddress; + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: tBTCPriceFeed.address, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(315, 18), + }; + + const mainnetActions = [ + // 1. Add tBTC as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, tBTCAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add tBTC as collateral into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add tBTC into cWETHv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Ethereum. 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). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399/12).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/934) and [forum discussion](https://www.comp.xyz/t/add-collateral-tbtc-to-eth-market-on-mainnet/5399).\n\n\n## Proposal Actions\n\nThe first action adds tBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const tBTCAssetIndex = Number(await comet.numAssets()) - 1; + + const tBTC = await deploymentManager.existing( + 'tBTC', + TBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const tBTCAssetConfig = { + asset: tBTC.address, + priceFeed: newPriceFeedAddress, + decimals: await tBTC.decimals(), + borrowCollateralFactor: exp(0.76, 18), + liquidateCollateralFactor: exp(0.81, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(315, 18), + }; + + // 1. Compare tBTC asset config with Comet and Configurator asset info + const cometTBTCAssetInfo = await comet.getAssetInfoByAddress(TBTC_ADDRESS); + expect(tBTCAssetIndex).to.be.equal(cometTBTCAssetInfo.offset); + expect(tBTCAssetConfig.asset).to.be.equal(cometTBTCAssetInfo.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(cometTBTCAssetInfo.priceFeed); + expect(exp(1, tBTCAssetConfig.decimals)).to.be.equal(cometTBTCAssetInfo.scale); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(cometTBTCAssetInfo.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cometTBTCAssetInfo.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(cometTBTCAssetInfo.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(cometTBTCAssetInfo.supplyCap); + + const configuratorTBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[tBTCAssetIndex]; + expect(tBTCAssetConfig.asset).to.be.equal(configuratorTBTCAssetConfig.asset); + expect(tBTCAssetConfig.priceFeed).to.be.equal(configuratorTBTCAssetConfig.priceFeed); + expect(tBTCAssetConfig.decimals).to.be.equal(configuratorTBTCAssetConfig.decimals); + expect(tBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.borrowCollateralFactor); + expect(tBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorTBTCAssetConfig.liquidateCollateralFactor); + expect(tBTCAssetConfig.liquidationFactor).to.be.equal(configuratorTBTCAssetConfig.liquidationFactor); + expect(tBTCAssetConfig.supplyCap).to.be.equal(configuratorTBTCAssetConfig.supplyCap); + }, +}); diff --git a/scenario/constraints/NativeTokenConstraint.ts b/scenario/constraints/NativeTokenConstraint.ts index b3eed1869..21b1dcbca 100644 --- a/scenario/constraints/NativeTokenConstraint.ts +++ b/scenario/constraints/NativeTokenConstraint.ts @@ -8,7 +8,7 @@ export class NativeTokenConstraint implements StaticCons async function (ctx: T): Promise { for (const symbol in ctx.assets) { const contract = await ctx.world.deploymentManager.contract(symbol); - if (contract && contract['deposit()']) { + if (contract && contract['deposit()'] && contract['withdraw(uint256)']) { const [whale]= await ctx.getWhales(); if (!whale) { throw new Error(`NativeTokenConstraint: no whale found for ${ctx.world.deploymentManager.network}`); From 01ce0c400f30fd55b3ccfc0d985bddedfe2213ca Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:48:54 -0500 Subject: [PATCH 18/22] Init AERO on Base market (#937) Co-authored-by: Mikhailo Shabodyash Co-authored-by: GitHub Actions Bot <> --- .github/workflows/run-scenarios.yaml | 2 +- deployments/base/aero/configuration.json | 60 ++++ deployments/base/aero/deploy.ts | 119 ++++++++ .../1728096598_configurate_and_ens.ts | 266 ++++++++++++++++++ deployments/base/aero/relations.ts | 52 ++++ deployments/base/aero/roots.json | 10 + .../1713012100_configurate_and_ens.ts | 10 +- hardhat.config.ts | 14 +- scenario/TransferScenario.ts | 13 +- scenario/utils/scenarioHelper.ts | 49 ++++ 10 files changed, 580 insertions(+), 15 deletions(-) create mode 100644 deployments/base/aero/configuration.json create mode 100644 deployments/base/aero/deploy.ts create mode 100644 deployments/base/aero/migrations/1728096598_configurate_and_ens.ts create mode 100644 deployments/base/aero/relations.ts create mode 100644 deployments/base/aero/roots.json create mode 100644 scenario/utils/scenarioHelper.ts diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index fcff8271e..7f2020873 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, mainnet-usdt, mainnet-wsteth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/base/aero/configuration.json b/deployments/base/aero/configuration.json new file mode 100644 index 000000000..1cf9ebd97 --- /dev/null +++ b/deployments/base/aero/configuration.json @@ -0,0 +1,60 @@ +{ + "name": "Compound AERO", + "symbol": "cAEROv3", + "baseToken": "AERO", + "baseTokenAddress": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", + "borrowMin": "1e18", + "pauseGuardian": "0x3cb4653f3b45f448d9100b118b75a1503281d2ee", + "storeFrontPriceFactor": 0.6, + "targetReserves": "50000000e18", + "rates": { + "supplyKink": 0.85, + "supplySlopeLow": 0.08, + "supplySlopeHigh": 11, + "supplyBase": 0, + "borrowKink": 0.85, + "borrowSlopeLow": 0.0706, + "borrowSlopeHigh": 15, + "borrowBase": 0.04 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "173611111111e0", + "baseBorrowSpeed": "173611111111e0", + "baseMinForRewards": "1000e18" + }, + "assets": { + "WETH": { + "address": "0x4200000000000000000000000000000000000006", + "decimals": "18", + "borrowCF": 0.6, + "liquidateCF": 0.65, + "liquidationFactor": 0.75, + "supplyCap": "7500e18" + }, + "USDC": { + "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "decimals": "6", + "borrowCF": 0.65, + "liquidateCF": 0.7, + "liquidationFactor": 0.8, + "supplyCap": "30000000e6" + }, + "wstETH": { + "address": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + "decimals": "18", + "borrowCF": 0.6, + "liquidateCF": 0.65, + "liquidationFactor": 0.75, + "supplyCap": "5000e18" + }, + "cbBTC": { + "address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "decimals": "8", + "borrowCF": 0.6, + "liquidateCF": 0.65, + "liquidationFactor": 0.75, + "supplyCap": "150e8" + } + } +} \ No newline at end of file diff --git a/deployments/base/aero/deploy.ts b/deployments/base/aero/deploy.ts new file mode 100644 index 000000000..0213e98ba --- /dev/null +++ b/deployments/base/aero/deploy.ts @@ -0,0 +1,119 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet, exp } from '../../../src/deploy'; + +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 { + const aero = await deploymentManager.existing( + 'AERO', + '0x940181a94A35A4569E4529A3CDfB74e38FD98631', + 'base' + ); + const weth = await deploymentManager.existing( + 'WETH', + '0x4200000000000000000000000000000000000006', + 'base' + ); + const usdc = await deploymentManager.existing( + 'USDC', + '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + 'base' + ); + const wstETH = await deploymentManager.existing( + 'wstETH', + '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', + 'base' + ); + const cbETH = await deploymentManager.existing( + 'cbETH', + '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', + 'base' + ); + // AERO + // AERO -> USD + const aeroUsdPriceFeed = await deploymentManager.deploy( + 'AERO:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x4EC5970fC728C5f65ba413992CD5fF6FD70fcfF0', + 8 + ] + ); + // USDC + // USDC -> USD + const usdcToUsdPriceFeed = await deploymentManager.deploy( + 'USDC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x7e860098F58bBFC8648a4311b374B1D669a2bc6B', // USDC -> USD + 8, + ], + true + ); + // WETH + // WETH -> USD + const ethToUsdPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // ETH -> USD + 8, // decimals + ], + true + ); + // wstETH + // wstETH -> USD + const wstETHToUsdPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', + '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', + 8, // decimals + 'wstETH / stETH ETH / USD' + ], + true + ); + // cbBTC + // cbBTC -> USD + const cbBTCToUsdPriceFeed = await deploymentManager.deploy( + 'cbBTC:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D', // cbBTC -> USD + 8, // decimals + ], + true + ); + + // Import shared contracts from cUSDbCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'base', 'usdbc'); + // new comet factory + 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'); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + // XXX We will need to deploy a new bulker only if need to support wstETH + + return { + ...deployed, + bridgeReceiver, + l2CrossDomainMessenger, // TODO: don't have to part of roots. can be pulled via relations + l2StandardBridge, + bulker + }; +} diff --git a/deployments/base/aero/migrations/1728096598_configurate_and_ens.ts b/deployments/base/aero/migrations/1728096598_configurate_and_ens.ts new file mode 100644 index 000000000..ba8a47678 --- /dev/null +++ b/deployments/base/aero/migrations/1728096598_configurate_and_ens.ts @@ -0,0 +1,266 @@ +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'; +import { ethers } from 'ethers'; + +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; +const baseCOMPAddress = '0x9e1028F5F1D5eDE59748FFceE5532509976840E0'; + + +export default migration('1728096598_configurate_and_ens', { + prepare: async (deploymentManager: DeploymentManager) => { + const cometFactory = await deploymentManager.deploy('cometFactory', 'CometFactory.sol', [], true); + return { newFactoryAddress: cometFactory.address }; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { newFactoryAddress }) => { + const trace = deploymentManager.tracer(); + const { utils } = ethers; + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + rewards, + } = await deploymentManager.getContracts(); + + const { + baseL1CrossDomainMessenger, + governor, + } = 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 = 8453; + const newMarketObject = { baseSymbol: 'AERO', 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, newFactoryAddress) + ); + 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, set reward config on Base, wrap ETH to WETH and transfer to Comet as reserves. + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_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)] + ) + }, + ]; + + const description = '# Initialize cAEROv3 on Base network\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Base network. This proposal takes the governance steps recommended and necessary to initialize a Compound III AERO market on Base; upon execution, cAEROv3 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-base-aero-comet-recommendations/5790).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/937), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/11259792818) and [forum discussion](https://www.comp.xyz/t/gauntlet-base-aero-comet-recommendations/5790).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nTODO: Seed reserves.\n\nThe sixth 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 Ethereum Mainnet cwstETHv3 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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { + + const { + comet, + rewards, + COMP, + } = await deploymentManager.getContracts(); + + + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + + // uncomment on on-chain proposal PR + expect(stateChanges).to.deep.equal({ + USDC: { + supplyCap: exp(30000000, 6) + }, + WETH: { + supplyCap: exp(7500, 18) + }, + wstETH: { + supplyCap: exp(5000, 18) + }, + cbBTC: { + supplyCap: exp(150, 8) + }, + baseTrackingSupplySpeed: exp(15 / 86400, 15, 18), // 173611111111 + baseTrackingBorrowSpeed: exp(15 / 86400, 15, 18), // 173611111111 + }); + + 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); + + + // 5. + 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', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3', + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + { + baseSymbol: 'AERO', + cometAddress: comet.address, + }, + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + } +}); \ No newline at end of file diff --git a/deployments/base/aero/relations.ts b/deployments/base/aero/relations.ts new file mode 100644 index 000000000..ac825da26 --- /dev/null +++ b/deployments/base/aero/relations.ts @@ -0,0 +1,52 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver' + }, + + Proxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + l2StandardBridge: { + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + OssifiableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + +}; \ No newline at end of file diff --git a/deployments/base/aero/roots.json b/deployments/base/aero/roots.json new file mode 100644 index 000000000..fc5de7d9f --- /dev/null +++ b/deployments/base/aero/roots.json @@ -0,0 +1,10 @@ +{ + "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", + "comet": "0x784efeB622244d2348d4F2522f8860B96fbEcE89", + "configurator": "0x45939657d1CA34A8FA39A924B71D28Fe8431e581", + "rewards": "0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1", + "bridgeReceiver": "0x18281dfC4d00905DA1aaA6731414EABa843c468A", + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "bulker": "0x78D0677032A35c63D142a48A2037048871212a8C" +} \ No newline at end of file diff --git a/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts b/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts index d457597e7..86dcad052 100644 --- a/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts +++ b/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts @@ -126,7 +126,7 @@ export default migration('1713012100_configurate_and_ens', { mainnetUSDTAddress, [ 'function balanceOf(address account) external view returns (uint256)', - 'function approve(address,uint256) external' + 'function approve(address,uint256) external' ], govDeploymentManager.hre.ethers.provider ); @@ -184,10 +184,10 @@ export default migration('1713012100_configurate_and_ens', { ), }, ]; - + // the description has speeds. speeds will be set up on on-chain proposal const description = "# Initialize cUSDTv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Optimism network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDT market on Optimism; upon execution, cUSDTv3 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). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975/6).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/848) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration and deploys a new Comet implementation on Optimism. This sends the encoded `setFactory`, `setConfiguration` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism. It also calls `setRewardConfig` on the Optimism rewards contract, to establish Optimism’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 5 COMP/day and borrow speed to be 5 COMP/day.\n\nThe second action reduces Compound [cUSDT](https://etherscan.io/address/0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9) reserves to Timelock, in order to seed the market reserves through the Optimism L1StandardBridge.\n\nThe third action approves Optimism’s [L1StandardBridge](https://etherscan.io/address/0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1) to take Timelock's USDT, in order to seed the reserves through the bridge.\n\nThe fourth action deposits 10K USDT from mainnet to the Optimism L1StandardBridge contract to bridge to Comet.\n\nThe fifth 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 Optimism cUSDTv3 market"; - const txn = await govDeploymentManager.retry(async () =>{ + const txn = await govDeploymentManager.retry(async () => { return trace(await governor.propose(...(await proposal(actions, description)))); } ); @@ -199,7 +199,7 @@ export default migration('1713012100_configurate_and_ens', { }, async enacted(deploymentManager: DeploymentManager): Promise { - return false; + return true; }, async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { @@ -219,7 +219,7 @@ export default migration('1713012100_configurate_and_ens', { } = await govDeploymentManager.getContracts(); const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); - + // uncomment on on-chain proposal PR // expect(stateChanges).to.deep.equal({ // WBTC: { diff --git a/hardhat.config.ts b/hardhat.config.ts index d15407876..411476a38 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -38,6 +38,7 @@ import arbitrumUsdtRelationConfigMap from './deployments/arbitrum/usdt/relations import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations'; import baseWethRelationConfigMap from './deployments/base/weth/relations'; import baseUsdcRelationConfigMap from './deployments/base/usdc/relations'; +import baseAeroRelationConfigMap from './deployments/base/aero/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'; @@ -72,7 +73,7 @@ const { REMOTE_ACCOUNTS = '' } = process.env; -function *deriveAccounts(pk: string, n: number = 10) { +function* deriveAccounts(pk: string, n: number = 10) { for (let i = 0; i < n; i++) yield (BigInt('0x' + pk) + BigInt(i)).toString(16); } @@ -188,7 +189,7 @@ function setupDefaultNetworkProviders(hardhatConfig: HardhatUserConfig) { getDefaultProviderURL(netConfig.network), gas: netConfig.gas || 'auto', gasPrice: netConfig.gasPrice || 'auto', - accounts: REMOTE_ACCOUNTS ? 'remote' : ( ETH_PK ? [...deriveAccounts(ETH_PK)] : { mnemonic: MNEMONIC } ), + accounts: REMOTE_ACCOUNTS ? 'remote' : (ETH_PK ? [...deriveAccounts(ETH_PK)] : { mnemonic: MNEMONIC }), }; } } @@ -374,7 +375,8 @@ const config: HardhatUserConfig = { 'base': { usdbc: baseUsdbcRelationConfigMap, weth: baseWethRelationConfigMap, - usdc: baseUsdcRelationConfigMap + usdc: baseUsdcRelationConfigMap, + aero: baseAeroRelationConfigMap }, 'base-goerli': { usdc: baseGoerliRelationConfigMap, @@ -522,6 +524,12 @@ const config: HardhatUserConfig = { deployment: 'usdc', auxiliaryBase: 'mainnet' }, + { + name: 'base-aero', + network: 'base', + deployment: 'aero', + auxiliaryBase: 'mainnet' + }, { name: 'base-goerli', network: 'base-goerli', diff --git a/scenario/TransferScenario.ts b/scenario/TransferScenario.ts index 06d7151c0..796446453 100644 --- a/scenario/TransferScenario.ts +++ b/scenario/TransferScenario.ts @@ -2,6 +2,7 @@ import { CometContext, scenario } from './context/CometContext'; import { expect } from 'chai'; import { expectApproximately, expectBase, expectRevertCustom, getInterest, hasMinBorrowGreaterThanOne, isTriviallySourceable, isValidAssetIndex, MAX_ASSETS } from './utils'; import { ContractReceipt } from 'ethers'; +import { getConfigForScenario } from './utils/scenarioHelper'; async function testTransferCollateral(context: CometContext, assetNum: number): Promise { const comet = await context.getComet(); @@ -181,8 +182,8 @@ scenario( const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 70 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); + expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); await albert.allow(betty, true); @@ -190,8 +191,8 @@ scenario( const toTransfer = 999n * scale; // XXX cannot withdraw 1000 (to ~0) const txn = await betty.transferAssetFrom({ src: albert.address, dst: betty.address, asset: baseAsset.address, amount: toTransfer }); - expectApproximately(await albert.getCometBaseBalance(), scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); + expectApproximately(await albert.getCometBaseBalance(), scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); return txn; // return txn to measure gas } @@ -249,8 +250,8 @@ scenario( const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 70 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, 70n) + 2n); + expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); await albert.allow(betty, true); diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts new file mode 100644 index 000000000..9ef7b4d12 --- /dev/null +++ b/scenario/utils/scenarioHelper.ts @@ -0,0 +1,49 @@ +import { CometContext } from '../context/CometContext'; + +const config = { + bulkerBase: 1000000, + bulkerAsset: 5000, + bulkerComet: 5000, + bulkerBorrowBase: 1000, + bulkerBorrowAsset: 500, + liquidationBase: 100000, + liquidationBase1: 1000, + liquidationAsset: 200, + liquidationDenominator: 90, + rewardsAsset: 10000, + rewardsBase: 1000, + transferBase: 1000, + transferAsset: 5000, + interestSeconds: 110 +}; + +export function getConfigForScenario(ctx: CometContext) { + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'wbtc') { + config.bulkerBase = 5000; + config.bulkerAsset = 200; + config.bulkerComet = 200; + config.bulkerBorrowBase = 100; + config.bulkerBorrowAsset = 50; + config.liquidationBase = 1000; + config.liquidationBase1 = 500; + config.liquidationAsset = 100; + config.rewardsAsset = 1000; + config.rewardsBase = 100; + config.transferBase = 100; + config.transferAsset = 500; + config.interestSeconds = 70; + } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'wsteth') { + config.liquidationBase = 10000; + config.liquidationBase1 = 1000; + config.liquidationAsset = 100; + config.liquidationDenominator = 84; + config.interestSeconds = 70; + } + + if (ctx.world.base.network === 'base' && ctx.world.base.deployment === 'aero') { + config.interestSeconds = 110; + } + + return config; +} \ No newline at end of file From 9085b8a046535d40c41b1158652851d234e8fee3 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 24 Oct 2024 19:58:08 +0300 Subject: [PATCH 19/22] Proposal for Mainnet USDS market (#942) Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> --- .github/workflows/run-scenarios.yaml | 2 +- deployments/mainnet/usds/configuration.json | 75 +++++ deployments/mainnet/usds/deploy.ts | 40 +++ .../1729069465_configurate_and_ens.ts | 279 ++++++++++++++++++ deployments/mainnet/usds/relations.ts | 25 ++ deployments/mainnet/usds/roots.json | 7 + hardhat.config.ts | 9 +- scenario/LiquidationBotScenario.ts | 61 ++-- scenario/MainnetBulkerScenario.ts | 4 +- scenario/constraints/ProposalConstraint.ts | 12 +- scenario/utils/scenarioHelper.ts | 5 + .../liquidateUnderwaterBorrowers.ts | 16 + src/deploy/index.ts | 2 + 13 files changed, 502 insertions(+), 35 deletions(-) create mode 100644 deployments/mainnet/usds/configuration.json create mode 100644 deployments/mainnet/usds/deploy.ts create mode 100644 deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts create mode 100644 deployments/mainnet/usds/relations.ts create mode 100644 deployments/mainnet/usds/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 7f2020873..5da35d811 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, mainnet-usdt, mainnet-wsteth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/mainnet/usds/configuration.json b/deployments/mainnet/usds/configuration.json new file mode 100644 index 000000000..08c9c2de6 --- /dev/null +++ b/deployments/mainnet/usds/configuration.json @@ -0,0 +1,75 @@ +{ + "name": "Compound USDS", + "symbol": "cUSDSv3", + "baseToken": "USDS", + "baseTokenAddress": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + "borrowMin": "10e18", + "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", + "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", + "baseTokenPriceFeed": "0xfF30586cD0F29eD462364C7e81375FC0C71219b1", + "storeFrontPriceFactor": 0.6, + "targetReserves": "20_000_000e18", + "rates": { + "borrowBase": 0.015, + "borrowSlopeLow": 0.0333, + "borrowKink": 0.9, + "borrowSlopeHigh": 4.0, + "supplyBase": 0, + "supplySlopeLow": 0.039, + "supplyKink": 0.9, + "supplySlopeHigh": 3.6 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "289351851851e0", + "baseBorrowSpeed": "289351851851e0", + "baseMinForRewards": "10000e18" + }, + "rewardTokenAddress": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "assets": { + "WETH": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "decimals": "18", + "borrowCF": 0.83, + "liquidateCF": 0.9, + "liquidationFactor": 0.95, + "supplyCap": "50_000e18" + }, + "USDe": { + "address": "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", + "priceFeed": "0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961", + "decimals": "18", + "borrowCF": 0.7, + "liquidateCF": 0.75, + "liquidationFactor": 0.85, + "supplyCap": "50_000_000e18" + }, + "cbBTC": { + "address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "priceFeed": "0x2665701293fCbEB223D11A08D826563EDcCE423A", + "decimals": "8", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "150e8" + }, + "tBTC": { + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "priceFeed": "0x8350b7De6a6a2C1368E7D4Bd968190e13E354297", + "decimals": "18", + "borrowCF": 0.76, + "liquidateCF": 0.81, + "liquidationFactor": 0.9, + "supplyCap": "285e18" + }, + "wstETH": { + "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "decimals": "18", + "borrowCF": 0.82, + "liquidateCF": 0.87, + "liquidationFactor": 0.92, + "supplyCap": "10_000e18" + } + } +} \ No newline at end of file diff --git a/deployments/mainnet/usds/deploy.ts b/deployments/mainnet/usds/deploy.ts new file mode 100644 index 000000000..82bbcdbc0 --- /dev/null +++ b/deployments/mainnet/usds/deploy.ts @@ -0,0 +1,40 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const WETH = await deploymentManager.existing('WETH', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); + const COMP = await deploymentManager.existing('COMP', '0xc00e94Cb662C3520282E6f5717214004A7f26888'); + const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'); + const cbBTC = await deploymentManager.existing('cbBTC', '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'); + const tBTC = await deploymentManager.existing('tBTC', '0x18084fbA666a33d37592fA2633fD49a74DD93a88'); + const USDe = await deploymentManager.existing('USDe', '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3'); + const USDS = await deploymentManager.existing('USDS', '0xdC035D45d973E3EC169d2276DDab16f1e407384F'); + + const wstETHtoUsdPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/WstETHPriceFeed.sol', + [ + '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', // ETH / USD price feed + wstETH.address, // wstETH token + 8, // decimals + ], + true + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'mainnet', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'mainnet', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'mainnet', 'usdc'); + const bulker = await deploymentManager.fromDep('bulker', 'mainnet', 'weth'); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bulker, + rewards, + COMP + }; +} \ No newline at end of file diff --git a/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts b/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts new file mode 100644 index 000000000..0512a7db6 --- /dev/null +++ b/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts @@ -0,0 +1,279 @@ +import { ethers, utils } from 'ethers'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; +import { expect } from 'chai'; + +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const USDSAmount = ethers.BigNumber.from(exp(300_000, 18)); +const cDAIAddress = '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643'; +const DaiToUsdsConverterAddress = '0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A'; +const DAIAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + +export default migration('1729069465_configurate_and_ens', { + async prepare() { + return {}; + }, + + async enact(deploymentManager: DeploymentManager, _) { + const trace = deploymentManager.tracer(); + + const { + comet, + cometAdmin, + configurator, + rewards, + COMP, + governor + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt', true); + + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const currentChainId = 1; + const newMarketObject = { baseSymbol: 'USDS', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + if (officialMarketsJSON[currentChainId]) { + officialMarketsJSON[currentChainId].push(newMarketObject); + } else { + officialMarketsJSON[currentChainId] = [newMarketObject]; + } + + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [USDSAmount] + ); + + const approveCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [DaiToUsdsConverterAddress, USDSAmount] + ); + + const convertCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [comet.address, USDSAmount] + ); + + const actions = [ + // 1. Set the Comet factory in configuration + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactory.address], + }, + // 2. Set the Comet configuration + { + 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 Comet and upgrade it to the new implementation + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + // 4. Set the reward configuration + { + contract: rewards, + signature: 'setRewardConfig(address,address)', + args: [comet.address, COMP.address], + }, + + // 5. Get DAI reserves from cDAI contract + { + target: cDAIAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 6. Approve DAI to the Comet contract + { + target: DAIAddress, + signature: 'approve(address,uint256)', + calldata: approveCalldata, + }, + // 7. Convert DAI to USDS + { + target: DaiToUsdsConverterAddress, + signature: 'daiToUsds(address,uint256)', + calldata: convertCalldata + }, + // 8. 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 cUSDSv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDS market on Mainnet; upon execution, cUSDSv3 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-collateral-usds-market-on-eth-mainnet/5781/5).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/942), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/11392132048) and [forum discussion](https://www.comp.xyz/t/add-collateral-usds-market-on-eth-mainnet/5781).\n\n\n## wstETH price feed\n\nFor LSTs, the goal is to use full exchange rate price feeds. Thus, we treat that stETH:ETH is 1:1.\n\n## sUSDS and sUSDe collaterals\n\nGauntlet suggests having sUDSS and sUSDe collaterals. This proposal does not include them, because the price feed is under the audit. We suggest to start bootstrapping the liquidity without these collaterals and as the price feed is audited, we will add these collaterals. We discussed it with Gauntlet, and we received the approval to have such an approach.\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action reduces Compound’s [cDAI](https://etherscan.io/address/0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643) reserves and transfers it to Timelock, in order to convert it to USDS to then seed the market reserves for the cUSDSv3 Comet.\n\nThe sixth action approves DAI to DAI-to-USDS native converter.\n\nThe seventh action converts DAI to USDS with 1:1 ratio and transfers USDS to cUSDSv3 Comet.\n\nThe eight 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 Ethereum Mainnet cUSDSv3 market.'; + 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, + timelock, + WETH, + wstETH, + cbBTC, + tBTC, + USDe, + COMP + } = await deploymentManager.getContracts(); + + // 1. & 2. & 3. + const cbbtcInfo = await comet.getAssetInfoByAddress(cbBTC.address); + const tbtcInfo = await comet.getAssetInfoByAddress(tBTC.address); + const wethInfo = await comet.getAssetInfoByAddress(WETH.address); + const usdeInfo = await comet.getAssetInfoByAddress(USDe.address); + const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); + + expect(cbbtcInfo.supplyCap).to.be.eq(exp(150, 8)); + expect(tbtcInfo.supplyCap).to.be.eq(exp(285, 18)); + expect(wethInfo.supplyCap).to.be.eq(exp(50_000, 18)); + expect(usdeInfo.supplyCap).to.be.eq(exp(50_000_000, 18)); + expect(wstETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); + + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(25 / 86400, 15, 18)); // 289351851851 + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(25 / 86400, 15, 18)); // 289351851851 + + // 4 + 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); + + expect((await comet.pauseGuardian()).toLowerCase()).to.be.eq('0xbbf3f1421d886e9b2c5d716b5192ac998af2012c'); + + // 5. & 6. + expect(await comet.getReserves()).to.be.equal(USDSAmount); + + // 7. + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const ENSRegistry = await deploymentManager.existing('ENSRegistry', ENSRegistryAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); + expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); + expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); + expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); + expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); + + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3', + }, + { + baseSymbol: 'USDS', + cometAddress: comet.address, + } + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + { + baseSymbol: 'AERO', + cometAddress: '0x784efeB622244d2348d4F2522f8860B96fbEcE89' + } + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + }, +}); \ No newline at end of file diff --git a/deployments/mainnet/usds/relations.ts b/deployments/mainnet/usds/relations.ts new file mode 100644 index 000000000..7b0cc418d --- /dev/null +++ b/deployments/mainnet/usds/relations.ts @@ -0,0 +1,25 @@ +import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + 'wstETH': { + artifact: 'contracts/bulkers/IWstETH.sol', + relations: { + stETH: { + field: async (wstETH) => wstETH.stETH() + } + } + }, + 'AppProxyUpgradeable': { + artifact: 'contracts/ERC20.sol:ERC20', + }, + 'ERC1967Proxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; \ No newline at end of file diff --git a/deployments/mainnet/usds/roots.json b/deployments/mainnet/usds/roots.json new file mode 100644 index 000000000..565665908 --- /dev/null +++ b/deployments/mainnet/usds/roots.json @@ -0,0 +1,7 @@ +{ + "comet": "0x5D409e56D886231aDAf00c8775665AD0f9897b56", + "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", + "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", + "bulker": "0xa397a8C2086C554B531c02E29f3291c9704B00c7", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 411476a38..f2cf294fc 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -27,6 +27,7 @@ import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import mainnetUsdtRelationConfigMap from './deployments/mainnet/usdt/relations'; import mainnetWstETHRelationConfigMap from './deployments/mainnet/wsteth/relations'; +import mainnetUsdsRelationConfigMap from './deployments/mainnet/usds/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import polygonUsdtRelationConfigMap from './deployments/polygon/usdt/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; @@ -356,7 +357,8 @@ const config: HardhatUserConfig = { usdc: mainnetRelationConfigMap, weth: mainnetWethRelationConfigMap, usdt: mainnetUsdtRelationConfigMap, - wsteth: mainnetWstETHRelationConfigMap + wsteth: mainnetWstETHRelationConfigMap, + usds: mainnetUsdsRelationConfigMap, }, polygon: { usdc: polygonRelationConfigMap, @@ -422,6 +424,11 @@ const config: HardhatUserConfig = { network: 'mainnet', deployment: 'wsteth' }, + { + name: 'mainnet-usds', + network: 'mainnet', + deployment: 'usds' + }, { name: 'development', network: 'hardhat', diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 8c504b8e1..45dd6922c 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -5,6 +5,7 @@ import { ethers, event, exp, wait } from '../test/helpers'; import CometActor from './context/CometActor'; import { CometInterface, OnChainLiquidator } from '../build/types'; import { getPoolConfig, flashLoanPools } from '../scripts/liquidation_bot/liquidateUnderwaterBorrowers'; +import { getConfigForScenario } from './utils/scenarioHelper'; interface LiquidationAddresses { balancerVault: string; @@ -537,16 +538,20 @@ for (let i = 0; i < MAX_ASSETS; i++) { scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when value is beneath liquidationThreshold`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), - tokenBalances: { - $comet: { $base: 100000 }, - }, - cometBalances: { - albert: { - $asset0: ' == 200', - }, - betty: { $base: 1000 }, - }, + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + tokenBalances: async (ctx) => ( + { + $comet: { $base: getConfigForScenario(ctx).liquidationBase }, + } + ), + cometBalances: async (ctx) => ( + { + albert: { + $asset0: ` == ${getConfigForScenario(ctx).liquidationAsset}`, + }, + betty: { $base: getConfigForScenario(ctx).liquidationBase1 }, + } + ) }, async ({ comet, actors }, _context, world) => { const { albert, betty } = actors; @@ -583,7 +588,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, @@ -648,16 +653,20 @@ scenario( scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when maxAmountToPurchase=0`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), - tokenBalances: { - $comet: { $base: 100000 }, - }, - cometBalances: { - albert: { - $asset0: ' == 200', - }, - betty: { $base: 1000 }, - }, + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + tokenBalances: async (ctx) => ( + { + $comet: { $base: getConfigForScenario(ctx).liquidationBase }, + } + ), + cometBalances: async (ctx) => ( + { + albert: { + $asset0: ` == ${getConfigForScenario(ctx).liquidationAsset}}`, + }, + betty: { $base: getConfigForScenario(ctx).liquidationBase1 }, + } + ) }, async ({ comet, actors }, _context, world) => { const { albert, betty } = actors; @@ -694,7 +703,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, @@ -761,7 +770,8 @@ scenario( mainnet: { usdc: 2250000, weth: 20, - usdt: 2250000 + usdt: 2250000, + usds: 225000, }, }; const assetAmounts = { @@ -769,6 +779,7 @@ scenario( usdc: ' == 5000', // COMP weth: ' == 7000', // CB_ETH usdt: ' == 5000', // COMP + usds: ' == 850', // WETH }, }; @@ -778,7 +789,7 @@ scenario( upgrade: { targetReserves: exp(20_000, 18) }, - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}, {deployment: 'usds'}]), tokenBalances: async (ctx) => ( { $comet: { @@ -829,7 +840,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, diff --git a/scenario/MainnetBulkerScenario.ts b/scenario/MainnetBulkerScenario.ts index 590aacaba..fdfa20a5e 100644 --- a/scenario/MainnetBulkerScenario.ts +++ b/scenario/MainnetBulkerScenario.ts @@ -52,7 +52,7 @@ scenario( const toSupplyStEth = exp(.1, 18); - await context.sourceTokens(toSupplyStEth + 2n, new CometAsset(stETH), albert); + await context.sourceTokens(toSupplyStEth + 3n, new CometAsset(stETH), albert); expect(await stETH.balanceOf(albert.address)).to.be.greaterThanOrEqual(toSupplyStEth); @@ -68,7 +68,7 @@ scenario( await albert.invoke({ actions, calldata }); - expect(await stETH.balanceOf(albert.address)).to.be.approximately(0n, 1n); + expectApproximately((await stETH.balanceOf(albert.address)).toBigInt(), 0n, 2n); expectApproximately( (await comet.collateralBalanceOf(albert.address, wstETH.address)).toBigInt(), (await wstETH.getWstETHByStETH(toSupplyStEth)).toBigInt(), diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index da4899ea3..5c507707f 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,15 +62,15 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 339 - if (proposal.id.eq(339)) { - console.log('Skipping proposal 339'); + // temporary hack to skip proposal 348 + if (proposal.id.eq(348)) { + console.log('Skipping proposal 348'); continue; } - // temporary hack to skip proposal 340 - if (proposal.id.eq(340)) { - console.log('Skipping proposal 340'); + // temporary hack to skip proposal 349 + if (proposal.id.eq(349)) { + console.log('Skipping proposal 349'); continue; } diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 9ef7b4d12..5d63f4599 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -33,6 +33,7 @@ export function getConfigForScenario(ctx: CometContext) { config.transferAsset = 500; config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'wsteth') { config.liquidationBase = 10000; config.liquidationBase1 = 1000; @@ -41,6 +42,10 @@ export function getConfigForScenario(ctx: CometContext) { config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'usds') { + config.liquidationAsset = 100; + } + if (ctx.world.base.network === 'base' && ctx.world.base.deployment === 'aero') { config.interestSeconds = 110; } diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index b2d5d4e0b..29a70303e 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -39,6 +39,7 @@ const addresses = { WETH9: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', CB_ETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', WST_ETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + RS_ETH: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, goerli: { @@ -94,6 +95,14 @@ export const flashLoanPools = { usdt: { tokenAddress: addresses.mainnet.DAI, poolFee: 100 + }, + wsteth: { + tokenAddress: addresses.mainnet.WETH9, + poolFee: 100 + }, + usds: { + tokenAddress: addresses.mainnet.DAI, + poolFee: 3000, } }, goerli: { @@ -189,6 +198,13 @@ export function getPoolConfig(tokenAddress: string) { uniswapPoolFee: 500 } }, + [addresses.mainnet.RS_ETH.toLowerCase()]: { + ...defaultPoolConfig, + ...{ + exchange: Exchange.Balancer, + balancerPoolId: '0x58aadfb1afac0ad7fca1148f3cde6aedf5236b6d00000000000000000000067f' + } + }, [addresses.mainnet.WST_ETH.toLowerCase()]: { ...defaultPoolConfig, ...{ diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 7381208a9..8719843f9 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -89,6 +89,8 @@ export const WHALES = { '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', + '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale + '0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' ], polygon: [ From 7574deedf0bf56c7b48c478a8e28462990975c95 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 15 Nov 2024 21:20:12 +0200 Subject: [PATCH 20/22] Proposal for Mantle USDe market (#939) Co-authored-by: dmitriy-woof-software Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .github/workflows/deploy-market.yaml | 4 +- .github/workflows/enact-migration.yaml | 14 +- .github/workflows/prepare-migration.yaml | 4 +- .github/workflows/run-contract-linter.yaml | 1 + .github/workflows/run-coverage.yaml | 1 + .github/workflows/run-eslint.yaml | 1 + .github/workflows/run-forge-tests.yaml | 1 + .github/workflows/run-gas-profiler.yaml | 1 + .github/workflows/run-scenarios.yaml | 3 +- .github/workflows/run-semgrep.yaml | 1 + .github/workflows/run-unit-tests.yaml | 1 + contracts/pricefeeds/ScalingPriceFeed.sol | 12 +- .../ScalingPriceFeedWithCustomDescription.sol | 86 ++++ contracts/test/MockRedstoneOracle.sol | 48 ++ .../{MockOracle.sol => MockRenzoOracle.sol} | 2 +- deployments/mainnet/usdc/relations.ts | 13 + deployments/mainnet/usdc/roots.json | 2 + deployments/mantle/usde/configuration.json | 52 +++ deployments/mantle/usde/deploy.ts | 183 ++++++++ .../1727774346_configurate_and_ens.ts | 413 ++++++++++++++++++ deployments/mantle/usde/relations.ts | 36 ++ deployments/mantle/usde/roots.json | 10 + hardhat.config.ts | 39 +- package.json | 2 +- plugins/import/etherscan.ts | 3 + scenario/BulkerScenario.ts | 38 +- scenario/constraints/ProposalConstraint.ts | 12 +- scenario/context/CometContext.ts | 2 +- scenario/utils/index.ts | 86 +++- scenario/utils/isBridgeProposal.ts | 15 + scenario/utils/relayMantleMessage.ts | 117 +++++ scenario/utils/relayMessage.ts | 8 + src/deploy/index.ts | 9 +- tasks/deployment_manager/task.ts | 8 +- yarn.lock | 2 +- 35 files changed, 1181 insertions(+), 49 deletions(-) create mode 100644 contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol create mode 100644 contracts/test/MockRedstoneOracle.sol rename contracts/test/{MockOracle.sol => MockRenzoOracle.sol} (98%) create mode 100644 deployments/mantle/usde/configuration.json create mode 100644 deployments/mantle/usde/deploy.ts create mode 100644 deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts create mode 100644 deployments/mantle/usde/relations.ts create mode 100644 deployments/mantle/usde/roots.json create mode 100644 scenario/utils/relayMantleMessage.ts diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index fe6e35024..2c53ea6ae 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - optimism + - mantle - scroll-goerli - scroll deployment: @@ -42,13 +43,14 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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 d2d62842e..3caba53c8 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - optimism + - mantle - scroll-goerli - scroll deployment: @@ -59,11 +60,12 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Get governance network run: | case ${{ github.event.inputs.network }} in - polygon | arbitrum | base | optimism) + polygon | arbitrum | base | optimism | mantle) echo "GOV_NETWORK=mainnet" >> $GITHUB_ENV ;; mumbai | arbitrum-goerli | base-goerli | linea-goerli | scroll-goerli | scroll) echo "GOV_NETWORK=goerli" >> $GITHUB_ENV ;; @@ -72,22 +74,22 @@ jobs: esac - name: Seacrest - uses: hayesgm/seacrest@0cab0fa2a2a8bf5b005956d70e3dad697d9fe013 + uses: hayesgm/seacrest@5d8e5e3023669e93d197963273ae159ecda9d2b2 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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 == '' - name: Seacrest (governance network) - uses: hayesgm/seacrest@0cab0fa2a2a8bf5b005956d70e3dad697d9fe013 + uses: hayesgm/seacrest@5d8e5e3023669e93d197963273ae159ecda9d2b2 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ env.GOV_NETWORK }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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\"}')[env.GOV_NETWORK] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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\"}')[env.GOV_NETWORK] }}" port: 8685 - if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' + if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' && github.event.inputs.impersonateAccount == '' - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index 9d6206a18..7aca68122 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -17,6 +17,7 @@ on: - base - base-goerli - optimism + - mantle deployment: description: Deployment Name (e.g. "usdc") required: true @@ -42,13 +43,14 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"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://fluent-prettiest-scion.base-mainnet.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-contract-linter.yaml b/.github/workflows/run-contract-linter.yaml index 2513cc18c..40c54d449 100644 --- a/.github/workflows/run-contract-linter.yaml +++ b/.github/workflows/run-contract-linter.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/run-coverage.yaml b/.github/workflows/run-coverage.yaml index b28829b56..ee1e186af 100644 --- a/.github/workflows/run-coverage.yaml +++ b/.github/workflows/run-coverage.yaml @@ -16,6 +16,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-eslint.yaml b/.github/workflows/run-eslint.yaml index 1961fec4c..ee7d27cef 100644 --- a/.github/workflows/run-eslint.yaml +++ b/.github/workflows/run-eslint.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-forge-tests.yaml b/.github/workflows/run-forge-tests.yaml index 6a10f7523..42b4dcbed 100644 --- a/.github/workflows/run-forge-tests.yaml +++ b/.github/workflows/run-forge-tests.yaml @@ -30,6 +30,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} - name: Build Comet with older solc versions run: | diff --git a/.github/workflows/run-gas-profiler.yaml b/.github/workflows/run-gas-profiler.yaml index d49ba9152..224914654 100644 --- a/.github/workflows/run-gas-profiler.yaml +++ b/.github/workflows/run-gas-profiler.yaml @@ -15,6 +15,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 5da35d811..29f7e42d5 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, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, mantle-usde, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} @@ -19,6 +19,7 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/run-semgrep.yaml b/.github/workflows/run-semgrep.yaml index dad4eb51a..1781c4897 100644 --- a/.github/workflows/run-semgrep.yaml +++ b/.github/workflows/run-semgrep.yaml @@ -19,6 +19,7 @@ jobs: INFURA_KEY: ${{ secrets.INFURA_KEY }} POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} container: # A Docker image with Semgrep installed. Do not change this. image: returntocorp/semgrep diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index 49779a40f..2a3af9fe3 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/contracts/pricefeeds/ScalingPriceFeed.sol b/contracts/pricefeeds/ScalingPriceFeed.sol index 6f4cbb1bc..06283f9cb 100644 --- a/contracts/pricefeeds/ScalingPriceFeed.sol +++ b/contracts/pricefeeds/ScalingPriceFeed.sol @@ -6,7 +6,7 @@ import "../IPriceFeed.sol"; /** * @title Scaling price feed - * @notice A custom price feed that scales up or down the price received from an underlying Chainlink price feed and returns the result + * @notice A custom price feed that scales up or down the price received from an underlying price feed and returns the result * @author Compound */ contract ScalingPriceFeed is IPriceFeed { @@ -22,7 +22,7 @@ contract ScalingPriceFeed is IPriceFeed { /// @notice Number of decimals for returned prices uint8 public immutable override decimals; - /// @notice Underlying Chainlink price feed where prices are fetched from + /// @notice Underlying price feed where prices are fetched from address public immutable underlyingPriceFeed; /// @notice Whether or not the price should be upscaled @@ -41,12 +41,12 @@ contract ScalingPriceFeed is IPriceFeed { decimals = decimals_; description = AggregatorV3Interface(underlyingPriceFeed_).description(); - uint8 chainlinkPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + uint8 underlyingPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); // Note: Solidity does not allow setting immutables in if/else statements - shouldUpscale = chainlinkPriceFeedDecimals < decimals_ ? true : false; + shouldUpscale = underlyingPriceFeedDecimals < decimals_ ? true : false; rescaleFactor = (shouldUpscale - ? signed256(10 ** (decimals_ - chainlinkPriceFeedDecimals)) - : signed256(10 ** (chainlinkPriceFeedDecimals - decimals_)) + ? signed256(10 ** (decimals_ - underlyingPriceFeedDecimals)) + : signed256(10 ** (underlyingPriceFeedDecimals - decimals_)) ); } diff --git a/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol b/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol new file mode 100644 index 000000000..366f0fb33 --- /dev/null +++ b/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../IPriceFeed.sol"; + +/** + * @title Scaling price feed + * @notice A custom price feed that scales up or down the price received from an underlying price feed and returns the result + * @author Compound + */ +contract ScalingPriceFeedWithCustomDescription is IPriceFeed { + /** Custom errors **/ + error InvalidInt256(); + + /// @notice Version of the price feed + uint public constant override version = 1; + + /// @notice Description of the price feed + string public description; + + /// @notice Number of decimals for returned prices + uint8 public immutable override decimals; + + /// @notice Underlying price feed where prices are fetched from + address public immutable underlyingPriceFeed; + + /// @notice Whether or not the price should be upscaled + bool internal immutable shouldUpscale; + + /// @notice The amount to upscale or downscale the price by + int256 internal immutable rescaleFactor; + + /** + * @notice Construct a new scaling price feed + * @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from + * @param decimals_ The number of decimals for the returned prices + **/ + constructor(address underlyingPriceFeed_, uint8 decimals_, string memory description_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = decimals_; + description = description_; + + uint8 underlyingPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = underlyingPriceFeedDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ - underlyingPriceFeedDecimals)) + : signed256(10 ** (underlyingPriceFeedDecimals - decimals_)) + ); + } + + /** + * @notice Price for the latest round + * @return roundId Round id from the underlying price feed + * @return answer Latest price for the asset in terms of ETH + * @return startedAt Timestamp when the round was started; passed on from underlying price feed + * @return updatedAt Timestamp when the round was last updated; passed on from underlying price feed + * @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed + **/ + function latestRoundData() override external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + (uint80 roundId_, int256 price, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData(); + return (roundId_, scalePrice(price), startedAt_, updatedAt_, answeredInRound_); + } + + function signed256(uint256 n) internal pure returns (int256) { + if (n > uint256(type(int256).max)) revert InvalidInt256(); + return int256(n); + } + + function scalePrice(int256 price) internal view returns (int256) { + int256 scaledPrice; + if (shouldUpscale) { + scaledPrice = price * rescaleFactor; + } else { + scaledPrice = price / rescaleFactor; + } + return scaledPrice; + } +} \ No newline at end of file diff --git a/contracts/test/MockRedstoneOracle.sol b/contracts/test/MockRedstoneOracle.sol new file mode 100644 index 000000000..fca7f7922 --- /dev/null +++ b/contracts/test/MockRedstoneOracle.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +/** + * @title Mock oracle + * @notice Mock oracle to test the scaling price feed with updated update time + * @author Compound + */ +contract MockRedstoneOracle { + + /// @notice Number of decimals for returned prices + uint8 public immutable decimals; + + /// @notice Underlying Chainlink price feed where prices are fetched from + address public immutable underlyingPriceFeed; + uint256 public immutable lastPrice; + + /** + * @notice Construct a new scaling price feed + * @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from + **/ + constructor(address underlyingPriceFeed_, uint256 lastPrice_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + lastPrice = lastPrice_; + } + + + /** + * @notice Price for the latest round + * @return roundId Round id from the underlying price feed + * @return answer Latest price for the asset in terms of ETH + * @return startedAt Timestamp when the round was started; passed on from underlying price feed + * @return updatedAt Current timestamp + * @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed + **/ + function latestRoundData() external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + return (1, int256(lastPrice), block.timestamp, block.timestamp, 1); + } +} diff --git a/contracts/test/MockOracle.sol b/contracts/test/MockRenzoOracle.sol similarity index 98% rename from contracts/test/MockOracle.sol rename to contracts/test/MockRenzoOracle.sol index 9576720c5..00922d949 100644 --- a/contracts/test/MockOracle.sol +++ b/contracts/test/MockRenzoOracle.sol @@ -8,7 +8,7 @@ import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface * @notice Mock oracle to test the scaling price feed with updated update time * @author Compound */ -contract MockOracle { +contract MockRenzoOracle { /// @notice Number of decimals for returned prices uint8 public immutable decimals; diff --git a/deployments/mainnet/usdc/relations.ts b/deployments/mainnet/usdc/relations.ts index 4f242ef3d..2387c8daa 100644 --- a/deployments/mainnet/usdc/relations.ts +++ b/deployments/mainnet/usdc/relations.ts @@ -66,6 +66,19 @@ export default { } } }, + mantleL1CrossDomainMessenger: { + delegates: { + field: async () => '0xb8DE82551fA4BA3bE4B3d9097763EDBeED541308' + } + }, + mantleL1StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, scrollMessenger: { delegates: { field: { diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index d25e3fbcd..de52796a2 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -13,6 +13,8 @@ "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", "opL1CrossDomainMessenger": "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", "opL1StandardBridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", + "mantleL1CrossDomainMessenger": "0x676A795fe6E43C17c668de16730c3F690FEB7120", + "mantleL1StandardBridge": "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", "scrollMessenger": "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367", "scrollL1USDCGateway": "0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B" } \ No newline at end of file diff --git a/deployments/mantle/usde/configuration.json b/deployments/mantle/usde/configuration.json new file mode 100644 index 000000000..d98ebf5d1 --- /dev/null +++ b/deployments/mantle/usde/configuration.json @@ -0,0 +1,52 @@ +{ + "name": "Compound USDe", + "symbol": "cUSDev3", + "baseToken": "USDe", + "baseTokenAddress": "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", + "borrowMin": "100e18", + "storeFrontPriceFactor": 0.6, + "targetReserves": "5_000_000e18", + "pauseGuardian": "0x2127338F0ff71Ecc779dce407D95C7D32f7C5F45", + "rates": { + "borrowBase": 0.015, + "borrowSlopeLow": 0.0333, + "borrowKink": 0.9, + "borrowSlopeHigh": 4.0, + "supplyBase": 0, + "supplySlopeLow": 0.039, + "supplyKink": 0.9, + "supplySlopeHigh": 3.6 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "46296296296e0", + "baseBorrowSpeed": "46296296296e0", + "baseMinForRewards": "1000e18" + }, + "assets": { + "mETH": { + "address": "0xcDA86A272531e8640cD7F1a92c01839911B90bb0", + "decimals": "18", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.90, + "supplyCap": "3000e18" + }, + "WETH": { + "address": "0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111", + "decimals": "18", + "borrowCF": 0.82, + "liquidateCF": 0.87, + "liquidationFactor": 0.93, + "supplyCap": "2800e18" + }, + "FBTC": { + "address": "0xC96dE26018A54D51c097160568752c4E3BD6C364", + "decimals": "8", + "borrowCF": 0.78, + "liquidateCF": 0.83, + "liquidationFactor": 0.88, + "supplyCap": "120e8" + } + } +} diff --git a/deployments/mantle/usde/deploy.ts b/deployments/mantle/usde/deploy.ts new file mode 100644 index 000000000..808c51d89 --- /dev/null +++ b/deployments/mantle/usde/deploy.ts @@ -0,0 +1,183 @@ +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'; +const USDE_TO_USD_PRICE_FEED_ADDRESS = '0xc49E06B50FCA57751155DA78803DCa691AfcDB22'; +const METH_TO_ETH_PRICE_FEED_ADDRESS = '0xBeaa52edFeB12da4F026b38eD6203938a9936EDF'; +const ETH_TO_USD_PRICE_FEED_ADDRESS = '0x61A31634B4Bb4B9C2556611f563Ed86cE2D4643B'; +const FBTC_TO_USD_PRICE_FEED_ADDRESS = '0x7e19d187d7B3Be8dDEF2fD0A3b4df6Ed0b8E62ee'; + +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 { + const trace = deploymentManager.tracer(); + + // Pull in existing assets + const USDe = await deploymentManager.existing( + 'USDe', + '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34', + 'mantle' + ); + const mETH = await deploymentManager.existing( + 'mETH', + '0xcDA86A272531e8640cD7F1a92c01839911B90bb0', + 'mantle' + ); + const WETH = await deploymentManager.existing( + 'WETH', + '0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111', + 'mantle' + ); + const WMANTLE = await deploymentManager.existing( + 'WMNT', + '0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8', + 'mantle' + ); + const FBTC = await deploymentManager.existing( + 'FBTC', + '0xC96dE26018A54D51c097160568752c4E3BD6C364', + 'mantle' + ); + + // pre-deployed OptimismMintableERC20 + const COMP = await deploymentManager.existing( + 'COMP', + '0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f', + 'mantle' + ); + + const usdePriceFeed = await deploymentManager.deploy( + 'USDe:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + USDE_TO_USD_PRICE_FEED_ADDRESS, // USDe / USD price feed + 8, // decimals + 'USDe / USD price feed by API3' // description + ], + true + ); + + const wethPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + ETH_TO_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'WETH / USD price feed by API3' // description + ], + true + ); + + const methPriceFeed = await deploymentManager.deploy( + 'mETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + METH_TO_ETH_PRICE_FEED_ADDRESS, // mETH / ETH price feed + ETH_TO_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'mETH / USD price feed by API3' // description + ], + true + ); + + const fbtcPriceFeed = await deploymentManager.deploy( + 'FBTC:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + FBTC_TO_USD_PRICE_FEED_ADDRESS, // FBTC / USD price feed + 8, // decimals + 'FBTC / USD price feed by API3' // description + ], + true + ); + + const l2CrossDomainMessenger = await deploymentManager.existing( + 'l2CrossDomainMessenger', + [ + '0xC0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007', + '0x4200000000000000000000000000000000000007', + ], + 'mantle' + ); + + const l2StandardBridge = await deploymentManager.existing( + 'l2StandardBridge', + [ + '0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010', + '0x4200000000000000000000000000000000000010', + ], + 'mantle' + ); + + // Deploy OptimismBridgeReceiver + const bridgeReceiver = await deploymentManager.deploy( + 'bridgeReceiver', + 'bridges/optimism/OptimismBridgeReceiver.sol', + [l2CrossDomainMessenger.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 + ] + ); + + // Initialize OptimismBridgeReceiver + 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 + // It won't be used, as we do not have MNT as a base and as a collateral + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/BaseBulker.sol', + [ + await comet.governor(), // admin + WMANTLE.address, // wrapped native token + ] + ); + + return { + ...deployed, + bridgeReceiver, + l2CrossDomainMessenger, + l2StandardBridge, + bulker, + COMP, + }; +} diff --git a/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts b/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts new file mode 100644 index 000000000..8401cf926 --- /dev/null +++ b/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts @@ -0,0 +1,413 @@ +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { + diffState, + getCometConfig, +} from '../../../../plugins/deployment_manager/DiffState'; +import { + calldata, + exp, + getConfigurationStruct, + proposal, +} from '../../../../src/deploy'; +import { expect } from 'chai'; +import { utils } from 'ethers'; +import { Contract } from 'ethers'; + +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const USDT_MAINNET = '0xdac17f958d2ee523a2206206994597c13d831ec7'; +const cUSDTAddress = '0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9'; + +const USDT_MANTLE = '0x201EBa5CC46D216Ce6DC03F6a759e8E766e956aE'; +const MANTLE_USDT_USDE_SWAP_POOL = '0x7ccD8a769d466340Fff36c6e10fFA8cf9077D988'; +const MANTLE_SWAP_ROUTER = '0xAFb85a12Babfafabfe1a518594492d5a830e782a'; + +const COMPAmountToBridge = exp(3_600, 18); +const USDeAmountToSeed = exp(75_000, 18); + +let mantleCOMP: string; + +export default migration('1727774346_configurate_and_ens', { + prepare: async () => { + return {}; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager + ) => { + const trace = deploymentManager.tracer(); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + rewards, + COMP, + timelock, + USDe, + } = + await deploymentManager.getContracts(); + + const { + mantleL1CrossDomainMessenger, + mantleL1StandardBridge, + 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 = utils.namehash(ENSSubdomain); + const baseChainId = 5000; + const newMarketObject = { + baseSymbol: 'USDe', + 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 swapRouter = new Contract( + MANTLE_SWAP_ROUTER, + [ + 'function getSwapIn(address pair, uint128 amountOut, bool swapForY) external view returns(uint128 amountIn, uint128 amountOutLeft, uint128 fee)', + 'function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, tuple(uint256[] pairBinSteps, uint8[] versions, address[] tokenPath), address to, uint256 deadline) external returns(uint256[] memory amountsIn)', + ], + deploymentManager.hre.ethers.provider + ); + + const amountToSwap = ((await swapRouter.getSwapIn(MANTLE_USDT_USDE_SWAP_POOL, USDeAmountToSeed * 105n / 100n, false)).amountIn).toBigInt(); + + 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, COMP.address] + ); + + const approveUSDTCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [MANTLE_SWAP_ROUTER, amountToSwap] + ); + + const swapCalldata = utils.defaultAbiCoder.encode( + ['uint256', 'uint256', 'tuple(uint256[],uint8[],address[])', 'address', 'uint256'], + [ + USDeAmountToSeed, + amountToSwap, + [ + [1], // magic number to define which pool to use + [2], // magic number to define which pool to use + [USDT_MANTLE, USDe.address], + ], + comet.address, + exp(1, 18), // deadline + ] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + cometAdmin.address, + rewards.address, + USDT_MANTLE, + MANTLE_SWAP_ROUTER, + ], + [ + 0, + 0, + 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)', + 'approve(address,uint256)', + 'swapTokensForExactTokens(uint256,uint256,(uint256[],uint8[],address[]),address,uint256)', + ], + [ + setConfigurationCalldata, + deployAndUpgradeToCalldata, + setRewardConfigCalldata, + approveUSDTCalldata, + swapCalldata, + ], + ] + ); + + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [amountToSwap] + ); + + const approveCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [mantleL1StandardBridge.address, amountToSwap] + ); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Mantle. + { + contract: mantleL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 2_500_000], + }, + // 2. Get USDT reserves from cUSDT contract + { + target: cUSDTAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 3. Approve USDT to L1StandardBridge + { + target: USDT_MAINNET, + signature: 'approve(address,uint256)', + calldata: approveCalldata, + }, + // 4. Bridge USDT from Ethereum to Mantle Timelock using L1StandardBridge + { + contract: mantleL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + 'depositERC20To(address,address,address,uint256,uint32,bytes)', + args: [ + USDT_MAINNET, + USDT_MANTLE, + timelock.address, + amountToSwap, + 200_000, + '0x', + ], + }, + // 5. Approve Ethereum's L1StandardBridge to take Timelock's COMP (for bridging) + { + contract: mainnetCOMP, + signature: 'approve(address,uint256)', + args: [mantleL1StandardBridge.address, COMPAmountToBridge], + }, + // 6. Bridge COMP from Ethereum to Mantle Rewards using L1StandardBridge + { + contract: mantleL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + 'depositERC20To(address,address,address,uint256,uint32,bytes)', + args: [ + mainnetCOMP.address, + COMP.address, + rewards.address, + COMPAmountToBridge, + 200_000, + '0x', + ], + }, + // 7. Update the list of official markets + { + target: ENSResolverAddress, + signature: 'setText(bytes32,string,string)', + calldata: utils.defaultAbiCoder.encode( + ['bytes32', 'string', 'string'], + [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] + ), + }, + ]; + + const description = `# Initialize cUSDEv3 on Mantle\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes deployment of Compound III to Mantle network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDe market on Mantle; upon execution, cUSDEv3 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-mantle-network/5774/6).\n\nFurther detailed information can be found on the corresponding [pull request](https://github.com/compound-finance/comet/pull/939), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/11485970843/job/31967241848) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-mantle-network/5774).\n\n\n## COMP token on Mantle\n\nFor creating a COMP token we used the same approach that we used before in the first Optimism USDC deployment. It uses OptimismMintableERC20 standard. The deployment COMP [transaction](https://mantlescan.xyz/address/0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f#internaltx). [COMP token on Mantle](https://mantlescan.xyz/address/0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f).\n\n## Pause Guardian\n\nWe deployed Safe pauseGuardian using [clone-multisig.ts](https://github.com/woof-software/comet/blob/main/scripts/clone-multisig.ts). [Deployment transaction](https://explorer.mantle.xyz/tx/0x3ff939d38b84add47f5bca1bd731d83cc030f8f1de6147d28197361ec2dc5ea9). [Address of pauseGuardian](https://explorer.mantle.xyz/address/0x2127338F0ff71Ecc779dce407D95C7D32f7C5F45)\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration, deploys a new Comet implementation on Mantle and swaps received USDT for USDe. This sends the encoded 'setConfiguration', 'deployAndUpgradeTo', 'transfer' and 'swap' calls across the bridge to the governance receiver on Mantle. It also calls 'setRewardConfig' on the Mantle rewards contract, to establish Mantle’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 4 COMP/day and borrow speed to be 4 COMP/day.\n\nThe second action reduces Compound’s [cUSDT](https://etherscan.io/address/0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9) reserves and transfers it to Timelock, in order to swap it for USDe and then seed the market reserves for the cUSDEv3 Comet.\n\nThe third action approves Mantle’s [L1StandardBridge](https://etherscan.io/address/0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012) to take USDT, in order to then swap it for USDe./n/nThe fourth action deposits USDT from mainnet to the Mantle L1StandardBridge contract to bridge to Timelock which will swap it for USDe to seed the reserves.\n\nThe fifth action approves Mantle’s [L1StandardBridge](https://etherscan.io/address/0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012) to take Timelock's COMP, in order to seed the rewards contract through the bridge.\n\nThe sixth action deposits 3.6K COMP from mainnet to the Mantle L1StandardBridge contract to bridge to CometRewards.\n\nThe seventh 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 Mantle cUSDEv3 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 + ) { + await deploymentManager.spider(); + + const { comet, rewards, USDe } = await deploymentManager.getContracts(); + + const COMP = await deploymentManager.existing( + 'COMP', + mantleCOMP, + 'mantle', + 'contracts/ERC20.sol:ERC20' + ); + + // 1. + const stateChanges = await diffState( + comet, + getCometConfig, + preMigrationBlockNumber + ); + expect(stateChanges).to.deep.equal({ + mETH: { + supplyCap: exp(3000, 18) + }, + WETH: { + supplyCap: exp(2800, 18) + }, + FBTC: { + supplyCap: exp(120, 8) + }, + baseTrackingSupplySpeed: exp(4 / 86400, 15, 18), // 46296296296 + baseTrackingBorrowSpeed: exp(4 / 86400, 15, 18), // 46296296296 + }); + + 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 USDe.balanceOf(comet.address)).to.be.greaterThanOrEqual(USDeAmountToSeed); + + // 4. & 5. + expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(3_600, 18)); + + // 6. + const ENSResolver = await govDeploymentManager.existing( + 'ENSResolver', + ENSResolverAddress + ); + const subdomainHash = 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', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3', + }, + { + baseSymbol: 'USDS', + cometAddress: '0x5D409e56D886231aDAf00c8775665AD0f9897b56' + } + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 5000: [ + { + baseSymbol: 'USDe', + cometAddress: comet.address, + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + { + baseSymbol: 'AERO', + cometAddress: '0x784efeB622244d2348d4F2522f8860B96fbEcE89' + } + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + }, +}); diff --git a/deployments/mantle/usde/relations.ts b/deployments/mantle/usde/relations.ts new file mode 100644 index 000000000..69b0686ba --- /dev/null +++ b/deployments/mantle/usde/relations.ts @@ -0,0 +1,36 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: + 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver', + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + l2StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; diff --git a/deployments/mantle/usde/roots.json b/deployments/mantle/usde/roots.json new file mode 100644 index 000000000..f60566272 --- /dev/null +++ b/deployments/mantle/usde/roots.json @@ -0,0 +1,10 @@ +{ + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "COMP": "0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f", + "comet": "0x606174f62cd968d8e684c645080fa694c1D7786E", + "configurator": "0xb77Cd4cD000957283D8BAf53cD782ECf029cF7DB", + "rewards": "0xCd83CbBFCE149d141A5171C3D6a0F0fCCeE225Ab", + "bridgeReceiver": "0xc91EcA15747E73d6dd7f616C49dAFF37b9F1B604", + "bulker": "0x67DFCa85CcEEFA2C5B1dB4DEe3BEa716A28B9baa" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index f2cf294fc..0255752d3 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -46,6 +46,7 @@ import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relati import optimismRelationConfigMap from './deployments/optimism/usdc/relations'; import optimismUsdtRelationConfigMap from './deployments/optimism/usdt/relations'; import optimismWethRelationConfigMap from './deployments/optimism/weth/relations'; +import mantleRelationConfigMap from './deployments/mantle/usde/relations'; import scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; @@ -64,6 +65,7 @@ const { BASESCAN_KEY, LINEASCAN_KEY, OPTIMISMSCAN_KEY, + MANTLESCAN_KEY, INFURA_KEY, QUICKNODE_KEY, MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect', @@ -95,7 +97,8 @@ export function requireEnv(varName, msg?: string): string { 'POLYGONSCAN_KEY', 'ARBISCAN_KEY', 'LINEASCAN_KEY', - 'OPTIMISMSCAN_KEY' + 'OPTIMISMSCAN_KEY', + 'MANTLESCAN_KEY', ].map((v) => requireEnv(v)); // Networks @@ -123,6 +126,14 @@ const networkConfigs: NetworkConfig[] = [ chainId: 10, url: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`, }, + { + network: 'mantle', + chainId: 5000, + // link for scenarios + url: `https://mantle-mainnet.infura.io/v3/${INFURA_KEY}`, + // link for deployment + // url: `https://rpc.mantle.xyz`, + }, { network: 'base', chainId: 8453, @@ -263,8 +274,10 @@ const config: HardhatUserConfig = { 'base-goerli': BASESCAN_KEY, // Linea 'linea-goerli': LINEASCAN_KEY, - optimism: OPTIMISMSCAN_KEY, + // optimism: OPTIMISMSCAN_KEY, optimisticEthereum: OPTIMISMSCAN_KEY, + // Mantle + mantle: MANTLESCAN_KEY, // Scroll Testnet 'scroll-goerli': ETHERSCAN_KEY, // Scroll @@ -330,6 +343,19 @@ const config: HardhatUserConfig = { apiURL: 'https://api.scrollscan.com/api', browserURL: 'https://scrollscan.com/' } + }, + { + network: 'mantle', + chainId: 5000, + urls: { + // apiURL: 'https://rpc.mantle.xyz', + // links for scenarios + apiURL: 'https://explorer.mantle.xyz/api', + browserURL: 'https://explorer.mantle.xyz/' + // links for deployment + // apiURL: 'https://api.mantlescan.xyz/api', + // browserURL: 'https://mantlescan.xyz/' + } } ] }, @@ -392,6 +418,9 @@ const config: HardhatUserConfig = { usdt: optimismUsdtRelationConfigMap, weth: optimismWethRelationConfigMap }, + 'mantle': { + 'usde': mantleRelationConfigMap + }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap }, @@ -573,6 +602,12 @@ const config: HardhatUserConfig = { deployment: 'weth', auxiliaryBase: 'mainnet' }, + { + name: 'mantle-usde', + network: 'mantle', + deployment: 'usde', + auxiliaryBase: 'mainnet' + }, { name: 'scroll-goerli', network: 'scroll-goerli', diff --git a/package.json b/package.json index c5a99c696..cb4495ca8 100644 --- a/package.json +++ b/package.json @@ -95,4 +95,4 @@ "resolutions": { "mocha": "^9.1.3" } -} +} \ No newline at end of file diff --git a/plugins/import/etherscan.ts b/plugins/import/etherscan.ts index 034c53042..332c7fe2c 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', optimism: 'api-optimistic.etherscan.io', + mantle: 'api.mantlescan.xyz', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'api.scrollscan.com' }[network]; @@ -51,6 +52,7 @@ export function getEtherscanUrl(network: string): string { 'base-goerli': 'goerli.basescan.org', 'linea-goerli': 'goerli.lineascan.build', optimism: 'optimistic.etherscan.io', + mantle: 'mantlescan.xyz', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'scrollscan.com' }[network]; @@ -79,6 +81,7 @@ export function getEtherscanApiKey(network: string): string { 'base-goerli': process.env.BASESCAN_KEY, 'linea-goerli': process.env.LINEASCAN_KEY, optimism: process.env.OPTIMISMSCAN_KEY, + mantle: process.env.MANTLESCAN_KEY, 'scroll-goerli': process.env.ETHERSCAN_KEY, scroll: process.env.ETHERSCAN_KEY }[network]; diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index e2d55889d..c1ddf3209 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; -async function hasWETHAsCollateralOrBase(ctx: CometContext): Promise { +async function hasNativeAsCollateralOrBase(ctx: CometContext): Promise { const comet = await ctx.getComet(); const bulker = await ctx.getBulker(); const wrappedNativeToken = await bulker.wrappedNativeToken(); @@ -74,16 +74,20 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), ]; + + if(await hasNativeAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -91,7 +95,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -162,7 +166,7 @@ scenario( await bulker.ACTION_TRANSFER_ASSET() ]; - if(await hasWETHAsCollateralOrBase(context)){ + if(await hasNativeAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -176,7 +180,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -331,18 +335,22 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata, claimRewardCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), await bulker.ACTION_CLAIM_REWARD(), ]; + + if(await hasNativeAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -351,7 +359,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -440,7 +448,7 @@ scenario( await bulker.ACTION_CLAIM_REWARD(), ]; - if(await hasWETHAsCollateralOrBase(context)){ + if(await hasNativeAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -455,7 +463,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 5c507707f..6fbdd095d 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,18 +62,18 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 348 - if (proposal.id.eq(348)) { - console.log('Skipping proposal 348'); - continue; - } - // temporary hack to skip proposal 349 if (proposal.id.eq(349)) { console.log('Skipping proposal 349'); continue; } + // temporary hack to skip proposal 353 + if (proposal.id.eq(353)) { + console.log('Skipping proposal 353'); + continue; + } + try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); diff --git a/scenario/context/CometContext.ts b/scenario/context/CometContext.ts index 8354fbb7b..9687cdd1c 100644 --- a/scenario/context/CometContext.ts +++ b/scenario/context/CometContext.ts @@ -74,7 +74,7 @@ export class CometContext { } async getCompWhales(): Promise { - const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism'].includes(this.world.base.network); + const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism', 'mantle'].includes(this.world.base.network); return COMP_WHALES[useMainnetComp ? 'mainnet' : 'testnet']; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 3fd02d954..aede8553a 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -346,8 +346,8 @@ async function redeployRenzoOracle(dm: DeploymentManager){ ]); const newOracle = await dm.deploy( - 'stETH:Oracle', - 'test/MockOracle.sol', + 'renzo:Oracle', + 'test/MockRenzoOracle.sol', [ '0x86392dC19c0b719886221c78AB11eb8Cf5c52812', // stETH / ETH oracle address ] @@ -357,6 +357,72 @@ async function redeployRenzoOracle(dm: DeploymentManager){ } } +const REDSTONE_FEEDS = { + mantle: [ + '0x3DFA26B9A15D37190bB8e50aE093730DcA88973E', // USDe / USD + '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', // mETH / ETH + '0xFc34806fbD673c21c1AEC26d69AA247F1e69a2C6', // ETH / USD + ], +}; + +async function getProxyAdmin(dm: DeploymentManager, proxyAddress: string): Promise { + // Retrieve the proxy admin address + const admin = await dm.hre.ethers.provider.getStorageAt(proxyAddress, '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103'); + // Convert the admin address to a checksum address + const adminAddress = dm.hre.ethers.utils.getAddress('0x' + admin.substring(26)); + return adminAddress; +} + +async function mockAllRedstoneOracles(dm: DeploymentManager){ + const feeds = REDSTONE_FEEDS[dm.network]; + if (!Array.isArray(feeds)) { + debug(`No redstone feeds found for network: ${dm.network}`); + return; + } + for (const feed of feeds) { + try{ + await dm.fromDep(`MockRedstoneOracle:${feed}`, dm.network, dm.deployment); + } + catch (_) { + await mockRedstoneOracle(dm, feed); + } + } +} + +async function mockRedstoneOracle(dm: DeploymentManager, feed: string){ + const feedContract = new Contract( + feed, + [ + 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', + ], + dm.hre.ethers.provider + ); + const proxyAdminAddress = await getProxyAdmin(dm, feed); + const proxyAdmin = new Contract( + proxyAdminAddress, + [ + 'function upgrade(address proxy, address newImplementation) external', + 'function owner() external view returns (address)', + ], + dm.hre.ethers.provider + ); + const ownerAddress = await proxyAdmin.owner(); + const owner = await impersonateAddress(dm, ownerAddress); + // set balance + await dm.hre.ethers.provider.send('hardhat_setBalance', [ + owner.address, + dm.hre.ethers.utils.hexStripZeros(dm.hre.ethers.utils.parseUnits('100', 'ether').toHexString()), + ]); + const price = (await feedContract.latestRoundData()).answer; + const newImplementation = await dm.deploy( + `MockRedstoneOracle:${feed}`, + 'test/MockRedstoneOracle.sol', + [feed, price] + ); + await proxyAdmin.connect(owner).upgrade(feed, newImplementation.address); +} + + export async function executeOpenProposal( dm: DeploymentManager, { id, startBlock, endBlock }: OpenProposal @@ -401,6 +467,7 @@ export async function executeOpenProposal( await governor.execute(id, { gasPrice: 0, gasLimit: 12000000 }); } await redeployRenzoOracle(dm); + await mockAllRedstoneOracles(dm); } // Instantly executes some actions through the governance proposal process @@ -550,6 +617,20 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } + case 'mantle': { + const sendMessageCalldata = utils.defaultAbiCoder.encode( + ['address', 'bytes', 'uint256'], + [bridgeReceiver.address, l2ProposalData, 2_500_000] + ); + const mantleL1CrossDomainMessenger = await govDeploymentManager.getContractOrThrow( + 'mantleL1CrossDomainMessenger' + ); + targets.push(mantleL1CrossDomainMessenger.address); + values.push(0); + signatures.push('sendMessage(address,bytes,uint32)'); + calldata.push(sendMessageCalldata); + break; + } case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( @@ -589,6 +670,7 @@ export async function executeOpenProposalAndRelay( ) { const startingBlockNumber = await governanceDeploymentManager.hre.ethers.provider.getBlockNumber(); await executeOpenProposal(governanceDeploymentManager, openProposal); + await mockAllRedstoneOracles(bridgeDeploymentManager); if (await isBridgeProposal(governanceDeploymentManager, bridgeDeploymentManager, openProposal)) { await relayMessage(governanceDeploymentManager, bridgeDeploymentManager, startingBlockNumber); diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 39f46bef3..8bad488e8 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -64,6 +64,21 @@ export async function isBridgeProposal( const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; return targets.some(t => bridgeContracts.includes(t)); } + case 'mantle': { + const governor = await governanceDeploymentManager.getContractOrThrow('governor'); + const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + 'mantleL1CrossDomainMessenger' + ); + const mantleL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + 'mantleL1StandardBridge' + ); + const { targets } = await governor.getActions(openProposal.id); + const bridgeContracts = [ + mantleL1CrossDomainMessenger.address, + mantleL1StandardBridge.address + ]; + return targets.some(t => bridgeContracts.includes(t)); + } case 'scroll': case 'scroll-goerli': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); diff --git a/scenario/utils/relayMantleMessage.ts b/scenario/utils/relayMantleMessage.ts new file mode 100644 index 000000000..a4881415f --- /dev/null +++ b/scenario/utils/relayMantleMessage.ts @@ -0,0 +1,117 @@ +import { DeploymentManager } from '../../plugins/deployment_manager'; +import { impersonateAddress } from '../../plugins/scenario/utils'; +import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; +import { BigNumber, ethers } from 'ethers'; +import { Log } from '@ethersproject/abstract-provider'; +import { OpenBridgedProposal } from '../context/Gov'; + +function applyL1ToL2Alias(address: string) { + const offset = BigInt('0x1111000000000000000000000000000000001111'); + return `0x${(BigInt(address) + offset).toString(16)}`; +} + +export default async function relayMantleMessage( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + startingBlockNumber: number +) { + const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow('mantleL1CrossDomainMessenger'); + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + const l2CrossDomainMessenger = await bridgeDeploymentManager.getContractOrThrow('l2CrossDomainMessenger'); + const l2StandardBridge = await bridgeDeploymentManager.getContractOrThrow('l2StandardBridge'); + + const openBridgedProposals: OpenBridgedProposal[] = []; + + // Grab all events on the L1CrossDomainMessenger contract since the `startingBlockNumber` + const filter = mantleL1CrossDomainMessenger.filters.SentMessage(); + const sentMessageEvents: Log[] = await governanceDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: startingBlockNumber, + toBlock: 'latest', + address: mantleL1CrossDomainMessenger.address, + topics: filter.topics! + }); + + for (let sentMessageEvent of sentMessageEvents) { + const { args: { target, sender, message, messageNonce, gasLimit } } = mantleL1CrossDomainMessenger.interface.parseLog(sentMessageEvent); + const aliasedSigner = await impersonateAddress( + bridgeDeploymentManager, + applyL1ToL2Alias(mantleL1CrossDomainMessenger.address) + ); + + await setNextBaseFeeToZero(bridgeDeploymentManager); + const relayMessageTxn = await ( + await l2CrossDomainMessenger.connect(aliasedSigner).relayMessage( + messageNonce, + sender, + target, + 0, + 0, + gasLimit, + message, + { gasPrice: 0, gasLimit: 7_500_000 } + ) + ).wait(); + + // Try to decode the SentMessage data to determine what type of cross-chain activity this is. So far, + // there are two types: + // 1. Bridging ERC20 token or ETH + // 2. Cross-chain message passing + if (target === l2StandardBridge.address) { + // Bridging ERC20 token + const messageWithoutPrefix = message.slice(2); // strip out the 0x prefix + const messageWithoutSigHash = '0x' + messageWithoutPrefix.slice(8); + try { + // 1a. Bridging ERC20 token + 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 ${l1Token} to user ${to}` + ); + } catch (e) { + // 1a. Bridging ETH + const { _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + ['address from', 'address to', 'uint256 amount', 'bytes data'], + messageWithoutSigHash + ); + + const oldBalance = await bridgeDeploymentManager.hre.ethers.provider.getBalance(to); + const newBalance = oldBalance.add(BigNumber.from(amount)); + // This is our best attempt to mimic the deposit transaction type (not supported in Hardhat) that Mantle uses to deposit ETH to an L2 address + await bridgeDeploymentManager.hre.ethers.provider.send('hardhat_setBalance', [ + to, + ethers.utils.hexStripZeros(newBalance.toHexString()), + ]); + + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ETH to user ${to}` + ); + } + } else if (target === bridgeReceiver.address) { + // Cross-chain message passing + const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address === bridgeReceiver.address); + const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); + + // Add the proposal to the list of open bridged proposals to be executed after all the messages have been relayed + openBridgedProposals.push({ id, eta }); + } else { + throw new Error(`[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Unrecognized target for cross-chain message`); + } + } + + // Execute open bridged proposals now that all messages have been bridged + for (let proposal of openBridgedProposals) { + const { eta, id } = proposal; + // Fast forward l2 time + await setNextBlockTimestamp(bridgeDeploymentManager, eta.toNumber() + 1); + + // Execute queued proposal + await setNextBaseFeeToZero(bridgeDeploymentManager); + await bridgeReceiver.executeProposal(id, { gasPrice: 0 }); + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Executed bridged proposal ${id}` + ); + } +} \ No newline at end of file diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index b3a6f75d6..eb7db32e0 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -4,6 +4,7 @@ import { relayArbitrumMessage, relayCCTPMint } from './relayArbitrumMessage'; import relayBaseMessage from './relayBaseMessage'; import relayLineaMessage from './relayLineaMessage'; import relayOptimismMessage from './relayOptimismMessage'; +import relayMantleMessage from './relayMantleMessage'; import relayScrollMessage from './relayScrollMessage'; export default async function relayMessage( @@ -28,6 +29,13 @@ export default async function relayMessage( startingBlockNumber ); break; + case 'mantle': + await relayMantleMessage( + governanceDeploymentManager, + bridgeDeploymentManager, + startingBlockNumber + ); + break; case 'mumbai': case 'polygon': await relayPolygonMessage( diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 8719843f9..02200dc53 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -144,7 +144,14 @@ export const WHALES = { '0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26', // OP whale '0x8af3827a41c26c7f32c81e93bb66e837e0210d5c', // USDC whale '0xc45A479877e1e9Dfe9FcD4056c699575a1045dAA', // wstETH whale - ] + ], + mantle: [ + '0x588846213A30fd36244e0ae0eBB2374516dA836C', // USDe whale + '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // mETH whale + '0x651C9D1F9da787688225f49d63ad1623ba89A8D5', // FBTC whale + '0xC455fE28a76da80022d4C35A37eB08FF405Eb78f', // FBTC whale + '0x524db930F0886CdE7B5FFFc920Aae85e98C2abfb', // FBTC whale + ], }; export async function calldata(req: Promise): Promise { diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index cf69fdc2a..5e08b7e37 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -260,7 +260,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') ); if (noDeploy) { - // Don't run the deploy script + // Don't run the deploy script } else { try { const overrides = undefined; // TODO: pass through cli args @@ -275,7 +275,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') const verify = noVerify ? false : !simulate; const desc = verify ? 'Verify' : 'Would verify'; if (noVerify && simulate) { - // Don't even print if --no-verify is set with --simulate + // Don't even print if --no-verify is set with --simulate } else { await dm.verifyContracts(async (address, args) => { if (args.via === 'buildfile') { @@ -288,9 +288,9 @@ task('deploy_and_migrate', 'Runs deploy and migration') }); if (noVerifyImpl) { - // Don't even try if --no-verify-impl + // Don't even try if --no-verify-impl } else { - // Maybe verify the comet impl too + // Maybe verify the comet impl too const comet = await dm.contract('comet'); const cometImpl = await dm.contract('comet:implementation'); const configurator = await dm.contract('configurator'); diff --git a/yarn.lock b/yarn.lock index eb9d46f58..6de081cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5803,4 +5803,4 @@ yn@3.1.1: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== \ No newline at end of file From 9ee48e502efbf794aceb3d5236c1e2c5bff9b87c Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 15 Nov 2024 21:24:48 +0200 Subject: [PATCH 21/22] Add wUSDM as collateral to USDT market on Mainnet (#930) --- contracts/IERC20.sol | 79 ++++++ contracts/IERC20Metadata.sol | 26 ++ contracts/IERC4626.sol | 230 ++++++++++++++++++ .../pricefeeds/PriceFeedWith4626Support.sol | 95 ++++++++ .../1730374657_add_wusdm_collateral.ts | 129 ++++++++++ deployments/mainnet/usdt/relations.ts | 10 +- scenario/SupplyScenario.ts | 2 +- src/deploy/index.ts | 1 + 8 files changed, 570 insertions(+), 2 deletions(-) create mode 100644 contracts/IERC20.sol create mode 100644 contracts/IERC20Metadata.sol create mode 100644 contracts/IERC4626.sol create mode 100644 contracts/pricefeeds/PriceFeedWith4626Support.sol create mode 100644 deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts diff --git a/contracts/IERC20.sol b/contracts/IERC20.sol new file mode 100644 index 000000000..cb6edc109 --- /dev/null +++ b/contracts/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.15; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} \ No newline at end of file diff --git a/contracts/IERC20Metadata.sol b/contracts/IERC20Metadata.sol new file mode 100644 index 000000000..441d193e4 --- /dev/null +++ b/contracts/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.15; + +import {IERC20} from "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC-20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} \ No newline at end of file diff --git a/contracts/IERC4626.sol b/contracts/IERC4626.sol new file mode 100644 index 000000000..a92c33583 --- /dev/null +++ b/contracts/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.15; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} \ No newline at end of file diff --git a/contracts/pricefeeds/PriceFeedWith4626Support.sol b/contracts/pricefeeds/PriceFeedWith4626Support.sol new file mode 100644 index 000000000..3103d9a7d --- /dev/null +++ b/contracts/pricefeeds/PriceFeedWith4626Support.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../IERC4626.sol"; +import "../IPriceFeed.sol"; + +/** + * @title Price feed for ERC4626 assets + * @notice A custom price feed that calculates the price for an ERC4626 asset + * @author Compound + */ +contract PriceFeedWith4626Support is IPriceFeed { + /** Custom errors **/ + error BadDecimals(); + error InvalidInt256(); + + /// @notice Version of the price feed + uint public constant VERSION = 1; + + /// @notice Description of the price feed + string public override description; + + /// @notice Number of decimals for returned prices + uint8 public immutable override decimals; + + /// @notice Number of decimals for the 4626 rate provider + uint8 internal immutable rateProviderDecimals; + + /// @notice Number of decimals for the underlying asset + uint8 internal immutable underlyingDecimals; + + /// @notice 4626 rate provider + address public immutable rateProvider; + + /// @notice Chainlink oracle for the underlying asset + address public immutable underlyingPriceFeed; + + /// @notice Combined scale of the two underlying price feeds + int public immutable combinedScale; + + /// @notice Scale of this price feed + int public immutable priceFeedScale; + + /** + * @notice Construct a new 4626 price feed + * @param rateProvider_ The address of the 4626 rate provider + * @param underlyingPriceFeed_ The address of the underlying asset price feed to fetch prices from + * @param decimals_ The number of decimals for the returned prices + * @param description_ The description of the price feed + **/ + constructor(address rateProvider_, address underlyingPriceFeed_, uint8 decimals_, string memory description_) { + rateProvider = rateProvider_; + underlyingPriceFeed = underlyingPriceFeed_; + rateProviderDecimals = IERC4626(rateProvider_).decimals(); + underlyingDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + combinedScale = signed256(10 ** (rateProviderDecimals + underlyingDecimals)); + description = description_; + + if (decimals_ > 18) revert BadDecimals(); + decimals = decimals_; + priceFeedScale = int256(10 ** decimals); + } + + /** + * @notice Get the latest price for the underlying asset + * @return roundId Round id from the underlying asset price feed + * @return answer Latest price for the underlying asset + * @return startedAt Timestamp when the round was started; passed on from the underlying asset price feed + * @return updatedAt Timestamp when the round was last updated; passed on from the underlying asset price feed + * @return answeredInRound Round id in which the answer was computed; passed on from the underlying asset price feed + **/ + function latestRoundData() override external view returns (uint80, int256, uint256, uint256, uint80) { + uint256 rate = IERC4626(rateProvider).convertToAssets(10**rateProviderDecimals); + (uint80 roundId_, int256 underlyingPrice, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData(); + + if (rate <= 0 || underlyingPrice <= 0) return (roundId_, 0, startedAt_, updatedAt_, answeredInRound_); + + int256 price = signed256(rate) * underlyingPrice * priceFeedScale / combinedScale; + return (roundId_, price, startedAt_, updatedAt_, answeredInRound_); + } + + function signed256(uint256 n) internal pure returns (int256) { + if (n > uint256(type(int256).max)) revert InvalidInt256(); + return int256(n); + } + + /** + * @notice Price for the latest round + * @return The version of the price feed contract + **/ + function version() external pure returns (uint256) { + return VERSION; + } +} \ No newline at end of file diff --git a/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts b/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts new file mode 100644 index 000000000..db03f7a48 --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const WUSDM_ADDRESS = '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812'; +const WUSDM_TO_USDM_PRICE_FEED_ADDRESS = '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812'; +const USDM_TO_USD_PRICE_FEED_ADDRESS = '0x079674468Fee6ab45aBfE986737A440701c49BdB'; + +let priceFeedAddress: string; + +export default migration('1730374657_add_wusdm_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wUSDMPriceFeed = await deploymentManager.deploy( + 'wUSDM:priceFeed', + 'pricefeeds/PriceFeedWith4626Support.sol', + [ + WUSDM_TO_USDM_PRICE_FEED_ADDRESS, // wUSDM / USDM price feed + USDM_TO_USD_PRICE_FEED_ADDRESS, // USDM / USD price feed + 8, // decimals + 'wUSDM / USD price feed', // description + ], + true + ); + return { wUSDMPriceFeedAddress: _wUSDMPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, _, { wUSDMPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + + const wUSDM = await deploymentManager.existing( + 'wUSDM', + WUSDM_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const wUSDMPriceFeed = await deploymentManager.existing( + 'wUSDM:priceFeed', + wUSDMPriceFeedAddress, + 'mainnet' + ); + priceFeedAddress = wUSDMPriceFeed.address; + const { + governor, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const newAssetConfig = { + asset: wUSDM.address, + priceFeed: wUSDMPriceFeed.address, + decimals: await wUSDM.decimals(), + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(6_500_000, 18), + }; + + const mainnetActions = [ + // 1. Add wUSDM as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, newAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add wUSDM as collateral into cUSDTv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wUSDM into cUSDTv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDT market on Ethereum. 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). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/list-wusdm-as-a-collateral-on-usdc-usdt-markets-on-arbitrum-and-ethereum/5590/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/930) and [forum discussion](https://www.comp.xyz/t/list-wusdm-as-a-collateral-on-usdc-usdt-markets-on-arbitrum-and-ethereum/5590).\n\n\n## Proposal Actions\n\nThe first proposal action adds wUSDM asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const wUSDMAssetIndex = Number(await comet.numAssets()) - 1; + + const wUSDMAssetConfig = { + asset: WUSDM_ADDRESS, + priceFeed: priceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(6_500_000, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const wUSDMAssetInfo = await comet.getAssetInfoByAddress(WUSDM_ADDRESS); + expect(wUSDMAssetIndex).to.be.equal(wUSDMAssetInfo.offset); + expect(wUSDMAssetConfig.asset).to.be.equal(wUSDMAssetInfo.asset); + expect(wUSDMAssetConfig.priceFeed).to.be.equal(wUSDMAssetInfo.priceFeed); + expect(exp(1, wUSDMAssetConfig.decimals)).to.be.equal(wUSDMAssetInfo.scale); + expect(wUSDMAssetConfig.borrowCollateralFactor).to.be.equal(wUSDMAssetInfo.borrowCollateralFactor); + expect(wUSDMAssetConfig.liquidateCollateralFactor).to.be.equal(wUSDMAssetInfo.liquidateCollateralFactor); + expect(wUSDMAssetConfig.liquidationFactor).to.be.equal(wUSDMAssetInfo.liquidationFactor); + expect(wUSDMAssetConfig.supplyCap).to.be.equal(wUSDMAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWUSDMAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[wUSDMAssetIndex]; + expect(wUSDMAssetConfig.asset).to.be.equal(configuratorWUSDMAssetConfig.asset); + expect(wUSDMAssetConfig.priceFeed).to.be.equal(configuratorWUSDMAssetConfig.priceFeed); + expect(wUSDMAssetConfig.decimals).to.be.equal(configuratorWUSDMAssetConfig.decimals); + expect(wUSDMAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWUSDMAssetConfig.borrowCollateralFactor); + expect(wUSDMAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWUSDMAssetConfig.liquidateCollateralFactor); + expect(wUSDMAssetConfig.liquidationFactor).to.be.equal(configuratorWUSDMAssetConfig.liquidationFactor); + expect(wUSDMAssetConfig.supplyCap).to.be.equal(configuratorWUSDMAssetConfig.supplyCap); + }, +}); diff --git a/deployments/mainnet/usdt/relations.ts b/deployments/mainnet/usdt/relations.ts index 7d7c02b2a..8036ed523 100644 --- a/deployments/mainnet/usdt/relations.ts +++ b/deployments/mainnet/usdt/relations.ts @@ -19,5 +19,13 @@ export default { }, 'AppProxyUpgradeable': { artifact: 'contracts/ERC20.sol:ERC20', - } + }, + 'ERC1967Proxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; \ No newline at end of file diff --git a/scenario/SupplyScenario.ts b/scenario/SupplyScenario.ts index 7082f87a6..a1b012390 100644 --- a/scenario/SupplyScenario.ts +++ b/scenario/SupplyScenario.ts @@ -300,7 +300,7 @@ scenario( const utilization = await comet.getUtilization(); const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); - expectApproximately(await albert.getCometBaseBalance(), -999n * scale, getInterest(999n * scale, borrowRate, 1n) + 2n); + expectApproximately(await albert.getCometBaseBalance(), -999n * scale, getInterest(999n * scale, borrowRate, 4n) + 2n); // Albert repays 1000 units of base borrow await baseAsset.approve(albert, comet.address); diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 02200dc53..fde711912 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -89,6 +89,7 @@ export const WHALES = { '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', + '0x426c4966fC76Bf782A663203c023578B744e4C5E', // wUSDM whale '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale '0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' From 5714ef96166a97659e82f7c6be62414d2925ca29 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Fri, 15 Nov 2024 21:29:20 +0200 Subject: [PATCH 22/22] Add ETHx as collateral to WETH market on Mainnet (#901) Co-authored-by: dmitriy-woof-software Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .../1730466050_add_ethx_as_collaterals.ts | 129 ++++++++++++++++++ scenario/LiquidationBotScenario.ts | 2 +- scenario/utils/scenarioHelper.ts | 7 +- 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts diff --git a/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts b/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts new file mode 100644 index 000000000..9ed630370 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const ETHX_ADDRESS = '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b'; +const ETHX_PRICE_FEED_ADDRESS = '0xdd487947c579af433AeeF038Bf1573FdBB68d2d3'; + +export default migration('1730466050_add_ethx_as_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _ETHxScalingPriceFeed = await deploymentManager.deploy( + 'ETHx:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + ETHX_PRICE_FEED_ADDRESS, // ETHx / ETH price feed + 8 // decimals + ], + true + ); + + return { ETHxScalingPriceFeed: _ETHxScalingPriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { ETHxScalingPriceFeed }) { + + const trace = deploymentManager.tracer(); + + const ETHx = await deploymentManager.existing( + 'ETHx', + ETHX_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const ETHxPricefeed = await deploymentManager.existing( + 'ETHx:priceFeed', + ETHxScalingPriceFeed, + 'mainnet' + ); + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const ETHxAssetConfig = { + asset: ETHx.address, + priceFeed: ETHxPricefeed.address, + decimals: await ETHx.decimals(), + borrowCollateralFactor: exp(0.85, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(2_100, 18), + }; + + const mainnetActions = [ + // 1. Add ETHx as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, ETHxAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add ETHx as collaterals into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ETHx into cWETHv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Ethereum. 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). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/listing-ethx-on-compound/4730/21).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/901) and [forum discussion](https://www.comp.xyz/t/listing-ethx-on-compound/4730).\n\n\n## Price feed\n\nExchange rate price feed of ETHx/ETH was provided by Chainlink team. The address of pricefeed that is used is 0xdd487947c579af433AeeF038Bf1573FdBB68d2d3\n\n\n## Proposal Actions\n\nThe first proposal action adds ETHx asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.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) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const ETHxAssetIndex = Number(await comet.numAssets()) - 1; + + const ETHx = await deploymentManager.existing( + 'ETHx', + ETHX_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const ETHxAssetConfig = { + asset: ETHx.address, + priceFeed: '', + decimals: await ETHx.decimals(), + borrowCollateralFactor: exp(0.85, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(2_100, 18), + }; + + // 1. Compare ETHx asset config with Comet and Configurator asset info + const cometETHxAssetInfo = await comet.getAssetInfo(ETHxAssetIndex); + expect(ETHxAssetIndex).to.be.equal(cometETHxAssetInfo.offset); + expect(ETHxAssetConfig.asset).to.be.equal(cometETHxAssetInfo.asset); + expect(exp(1, ETHxAssetConfig.decimals)).to.be.equal(cometETHxAssetInfo.scale); + expect(ETHxAssetConfig.borrowCollateralFactor).to.be.equal(cometETHxAssetInfo.borrowCollateralFactor); + expect(ETHxAssetConfig.liquidateCollateralFactor).to.be.equal(cometETHxAssetInfo.liquidateCollateralFactor); + expect(ETHxAssetConfig.liquidationFactor).to.be.equal(cometETHxAssetInfo.liquidationFactor); + expect(ETHxAssetConfig.supplyCap).to.be.equal(cometETHxAssetInfo.supplyCap); + + const configuratorETHxAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[ETHxAssetIndex]; + expect(ETHxAssetConfig.asset).to.be.equal(configuratorETHxAssetConfig.asset); + expect(ETHxAssetConfig.decimals).to.be.equal(configuratorETHxAssetConfig.decimals); + expect(ETHxAssetConfig.borrowCollateralFactor).to.be.equal(configuratorETHxAssetConfig.borrowCollateralFactor); + expect(ETHxAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorETHxAssetConfig.liquidateCollateralFactor); + expect(ETHxAssetConfig.liquidationFactor).to.be.equal(configuratorETHxAssetConfig.liquidationFactor); + expect(ETHxAssetConfig.supplyCap).to.be.equal(configuratorETHxAssetConfig.supplyCap); + }, +}); \ No newline at end of file diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 45dd6922c..ad6ab7069 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -840,7 +840,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationNumerator)).div(100n); await albert.withdrawAsset({ asset: baseToken, diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 5d63f4599..80134f6e7 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -10,11 +10,12 @@ const config = { liquidationBase1: 1000, liquidationAsset: 200, liquidationDenominator: 90, + liquidationNumerator: 90, rewardsAsset: 10000, rewardsBase: 1000, transferBase: 1000, transferAsset: 5000, - interestSeconds: 110 + interestSeconds: 110, }; export function getConfigForScenario(ctx: CometContext) { @@ -42,6 +43,10 @@ export function getConfigForScenario(ctx: CometContext) { config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'weth') { + config.liquidationNumerator = 60; + } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'usds') { config.liquidationAsset = 100; }