From 6e0e989ad731b417bebcc7f06cd7405fe7b1cfed Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 9 Jul 2024 18:34:29 +0300 Subject: [PATCH 1/5] On-chain proposal to add WBTC in WETH Mainnet market (#868) Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> --- .../1718698838_add_wbtc_as_collateral.ts | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 deployments/mainnet/weth/migrations/1718698838_add_wbtc_as_collateral.ts diff --git a/deployments/mainnet/weth/migrations/1718698838_add_wbtc_as_collateral.ts b/deployments/mainnet/weth/migrations/1718698838_add_wbtc_as_collateral.ts new file mode 100644 index 000000000..375ad1b78 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1718698838_add_wbtc_as_collateral.ts @@ -0,0 +1,158 @@ +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 WBTC_ADDRESS = '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'; +const WBTC_BTC_PRICE_FEED_ADDRESS = '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23'; +const BTC_ETH_PRICE_FEED_ADDRESS = '0xdeb288F737066589598e9214E782fa5A8eD689e8'; + +export default migration('1718698838_add_wbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const wbtcMultiplicativePriceFeed = await deploymentManager.deploy( + 'WBTC:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + WBTC_BTC_PRICE_FEED_ADDRESS, // WBTC / BTC price feed + BTC_ETH_PRICE_FEED_ADDRESS, // BTC / ETH price feed + 8, // decimals + 'WBTC / USD price feed' + ] + ); + return { wbtcPriceFeedAddress: wbtcMultiplicativePriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { wbtcPriceFeedAddress }) { + const trace = deploymentManager.tracer(); + + const WBTC = await deploymentManager.existing( + 'WBTC', + WBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const wbtcPricefeed = await deploymentManager.existing( + 'WBTC:priceFeed', + wbtcPriceFeedAddress, + 'mainnet' + ); + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const wbtcAssetConfig = { + asset: WBTC.address, + priceFeed: wbtcPricefeed.address, + decimals: await WBTC.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(1_000, 8), + }; + + const mainnetActions = [ + // 1. Add ezETH as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, wbtcAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add WBTC as collateral into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add WBTC 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-wbtc-to-weth-comets-on-ethereum-and-arbitrum/5332/1).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/868) and [forum discussion](https://www.comp.xyz/t/add-wbtc-to-weth-comets-on-ethereum-and-arbitrum/5332).\n\n\n## Proposal Actions\n\nThe first proposal action adds WBTC asset as collateral with the corresponding configuration.\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 wbtcAssetIndex = Number(await comet.numAssets()) - 1; + + const WBTC = await deploymentManager.existing( + 'WBTC', + WBTC_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const wbtcAssetConfig = { + asset: WBTC.address, + priceFeed: '', + decimals: await WBTC.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(1_000, 8), + }; + + // 1. & 2. Compare WBTC asset config with Comet and Configurator asset info + const cometWBTCHAssetInfo = await comet.getAssetInfoByAddress( + WBTC_ADDRESS + ); + + expect(wbtcAssetIndex).to.be.equal(cometWBTCHAssetInfo.offset); + expect(wbtcAssetConfig.asset).to.be.equal(cometWBTCHAssetInfo.asset); + expect(exp(1, wbtcAssetConfig.decimals)).to.be.equal( + cometWBTCHAssetInfo.scale + ); + expect(wbtcAssetConfig.borrowCollateralFactor).to.be.equal( + cometWBTCHAssetInfo.borrowCollateralFactor + ); + expect(wbtcAssetConfig.liquidateCollateralFactor).to.be.equal( + cometWBTCHAssetInfo.liquidateCollateralFactor + ); + expect(wbtcAssetConfig.liquidationFactor).to.be.equal( + cometWBTCHAssetInfo.liquidationFactor + ); + expect(wbtcAssetConfig.supplyCap).to.be.equal( + cometWBTCHAssetInfo.supplyCap + ); + const configuratorEsETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[wbtcAssetIndex]; + expect(wbtcAssetConfig.asset).to.be.equal( + configuratorEsETHAssetConfig.asset + ); + expect(wbtcAssetConfig.decimals).to.be.equal( + configuratorEsETHAssetConfig.decimals + ); + expect(wbtcAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.borrowCollateralFactor + ); + expect(wbtcAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidateCollateralFactor + ); + expect(wbtcAssetConfig.liquidationFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidationFactor + ); + expect(wbtcAssetConfig.supplyCap).to.be.equal( + configuratorEsETHAssetConfig.supplyCap + ); + }, +}); \ No newline at end of file From 4ffcdb50dcf0cf2f688f37ff44b893eb8baf8e3f Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Tue, 9 Jul 2024 18:34:48 +0300 Subject: [PATCH 2/5] Add WBTC to WETH market on Arbitrum (#880) --- .../1718791267_add_wbtc_as_collateral.ts | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 deployments/arbitrum/weth/migrations/1718791267_add_wbtc_as_collateral.ts diff --git a/deployments/arbitrum/weth/migrations/1718791267_add_wbtc_as_collateral.ts b/deployments/arbitrum/weth/migrations/1718791267_add_wbtc_as_collateral.ts new file mode 100644 index 000000000..60f8ffef7 --- /dev/null +++ b/deployments/arbitrum/weth/migrations/1718791267_add_wbtc_as_collateral.ts @@ -0,0 +1,219 @@ +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 WBTC_ADDRESS = '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f'; +const WBTC_BTC_PRICE_FEED_ADDRESS = '0x0017abAc5b6f291F9164e35B1234CA1D697f9CF4'; +const BTC_ETH_PRICE_FEED_ADDRESS = '0xc5a90A6d7e4Af242dA238FFe279e9f2BA0c64B2e'; + +export default migration('1718791267_add_wbtc_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wbtcScalingPriceFeed = await deploymentManager.deploy( + 'WBTC:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + WBTC_BTC_PRICE_FEED_ADDRESS, // WBTC / BTC price feed + BTC_ETH_PRICE_FEED_ADDRESS, // BTC / ETH price feed + 8, // decimals + 'WBTC / BTC BTC / ETH', // description + ] + ); + + return { wbtcScalingPriceFeed: _wbtcScalingPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { wbtcScalingPriceFeed }) => { + const trace = deploymentManager.tracer(); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const { + arbitrumInbox, + timelock, + governor + } = await govDeploymentManager.getContracts(); + + const WBTC = await deploymentManager.existing( + 'WBTC', + WBTC_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const wbtcPricefeed = await deploymentManager.existing( + 'WBTC:priceFeed', + wbtcScalingPriceFeed, + 'arbitrum' + ); + + const wbtcAssetConfig = { + asset: WBTC.address, + priceFeed: wbtcPricefeed.address, + decimals: 8n, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(300, 8), + }; + + const addAssetCalldata = ethers.utils.defaultAbiCoder.encode( + ['address', 'tuple(address,address,uint8,uint64,uint64,uint64,uint128)'], + [comet.address, + [ + wbtcAssetConfig.asset, + wbtcAssetConfig.priceFeed, + wbtcAssetConfig.decimals, + wbtcAssetConfig.borrowCollateralFactor, + wbtcAssetConfig.liquidateCollateralFactor, + wbtcAssetConfig.liquidationFactor, + wbtcAssetConfig.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 WBTC as collateral into cWETHv3 on Arbitrum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add WBTC 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 WBTC](https://www.comp.xyz/t/add-wbtc-to-weth-comets-on-ethereum-and-arbitrum/5332/1).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/880) and [forum discussion](https://www.comp.xyz/t/add-wbtc-to-weth-comets-on-ethereum-and-arbitrum/5332).\n\n\n## Proposal Actions\n\nThe first proposal action adds WBTC 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(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const wbtcAssetIndex = Number(await comet.numAssets()) - 1; + + const WBTC = await deploymentManager.existing( + 'WBTC', + WBTC_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const wbtcAssetConfig = { + asset: WBTC.address, + priceFeed: '', + decimals: 8n, + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(300, 8), + }; + + // 1. & 2. Compare WBTC asset config with Comet and Configurator asset info + const cometWBTCHAssetInfo = await comet.getAssetInfoByAddress( + WBTC_ADDRESS + ); + expect(wbtcAssetIndex).to.be.equal(cometWBTCHAssetInfo.offset); + expect(wbtcAssetConfig.asset).to.be.equal(cometWBTCHAssetInfo.asset); + expect(exp(1, wbtcAssetConfig.decimals)).to.be.equal( + cometWBTCHAssetInfo.scale + ); + expect(wbtcAssetConfig.borrowCollateralFactor).to.be.equal( + cometWBTCHAssetInfo.borrowCollateralFactor + ); + expect(wbtcAssetConfig.liquidateCollateralFactor).to.be.equal( + cometWBTCHAssetInfo.liquidateCollateralFactor + ); + expect(wbtcAssetConfig.liquidationFactor).to.be.equal( + cometWBTCHAssetInfo.liquidationFactor + ); + expect(wbtcAssetConfig.supplyCap).to.be.equal( + cometWBTCHAssetInfo.supplyCap + ); + const configuratorEsETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[wbtcAssetIndex]; + expect(wbtcAssetConfig.asset).to.be.equal( + configuratorEsETHAssetConfig.asset + ); + expect(wbtcAssetConfig.decimals).to.be.equal( + configuratorEsETHAssetConfig.decimals + ); + expect(wbtcAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.borrowCollateralFactor + ); + expect(wbtcAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidateCollateralFactor + ); + expect(wbtcAssetConfig.liquidationFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidationFactor + ); + expect(wbtcAssetConfig.supplyCap).to.be.equal( + configuratorEsETHAssetConfig.supplyCap + ); + }, +}); From 74a59c7adea881359e6d4accb4df1913f9ecbddd Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Mon, 15 Jul 2024 20:55:15 +0300 Subject: [PATCH 3/5] On-chain proposal to add ezETH in WETH Mainnet market (#874) Co-authored-by: dmitriy-woof-software Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> --- .../pricefeeds/EzETHExchangeRatePriceFeed.sol | 98 +++++++++++ .../ReverseMultiplicativePriceFeed.sol | 95 +++++++++++ contracts/test/MockOracle.sol | 46 ++++++ .../vendor/renzo/IBalancerRateProvider.sol | 6 + .../1718352598_add_ezeth_as_collateral.ts | 155 ++++++++++++++++++ deployments/mainnet/weth/relations.ts | 7 +- scenario/utils/index.ts | 33 +++- 7 files changed, 436 insertions(+), 4 deletions(-) create mode 100644 contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol create mode 100644 contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol create mode 100644 contracts/test/MockOracle.sol create mode 100644 contracts/vendor/renzo/IBalancerRateProvider.sol create mode 100644 deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts diff --git a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol new file mode 100644 index 000000000..f3419a696 --- /dev/null +++ b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/renzo/IBalancerRateProvider.sol"; +import "../IPriceFeed.sol"; + +/** + * @title ezETH Scaling price feed + * @notice A custom price feed that scales up or down the price received from an underlying Renzo ezETH / ETH exchange rate price feed and returns the result + * @author Compound + */ +contract EzETHExchangeRatePriceFeed is IPriceFeed { + /** Custom errors **/ + error InvalidInt256(); + error BadDecimals(); + + /// @notice Version of the price feed + uint public constant VERSION = 1; + + /// @notice Description of the price feed + string public description; + + /// @notice Number of decimals for returned prices + uint8 public immutable override decimals; + + /// @notice ezETH 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 ezETH scaling price feed + * @param ezETHRateProvider The address of the underlying price feed to fetch prices from + * @param decimals_ The number of decimals for the returned prices + **/ + constructor(address ezETHRateProvider, uint8 decimals_, string memory description_) { + underlyingPriceFeed = ezETHRateProvider; + if (decimals_ > 18) revert BadDecimals(); + decimals = decimals_; + description = description_; + + uint8 ezETHRateProviderDecimals = 18; + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = ezETHRateProviderDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ - ezETHRateProviderDecimals)) + : signed256(10 ** (ezETHRateProviderDecimals - 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 + ) { + uint256 rate = IBalancerRateProvider(underlyingPriceFeed).getRate(); + // protocol uses only the answer value. Other data fields are not provided by the underlying pricefeed and are not used in Comet protocol + // https://etherscan.io/address/0x387dBc0fB00b26fb085aa658527D5BE98302c84C#readProxyContract + return (1, scalePrice(signed256(rate)), block.timestamp, block.timestamp, 1); + } + + 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; + } + + /** + * @notice Price for the latest round + * @return The version of the price feed contract + **/ + function version() external pure returns (uint256) { + return VERSION; + } +} diff --git a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol new file mode 100644 index 000000000..b79b1a2c0 --- /dev/null +++ b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.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 "../IPriceFeed.sol"; + +/** + * @title Reverse multiplicative price feed + * @notice A custom price feed that multiplies the price from one price feed and the inverse price from another price feed and returns the result + * @dev for example if we need tokenX to eth, but there is only tokenX to usd, we can use this price feed to get tokenX to eth: tokenX to usd * reversed(eth to usd) + * @author Compound + */ +contract ReverseMultiplicativePriceFeed 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 Chainlink price feed A + address public immutable priceFeedA; + + /// @notice Chainlink price feed B + address public immutable priceFeedB; + + /// @notice Price feed A scale + int public immutable priceFeedAScale; + + /// @notice Price feed B scale + int public immutable priceFeedBScale; + + /// @notice Scale of this price feed + int public immutable priceFeedScale; + + /** + * @notice Construct a new reverse multiplicative price feed + * @param priceFeedA_ The address of the first price feed to fetch prices from + * @param priceFeedB_ The address of the second price feed to fetch prices from that should be reversed + * @param decimals_ The number of decimals for the returned prices + * @param description_ The description of the price feed + **/ + constructor(address priceFeedA_, address priceFeedB_, uint8 decimals_, string memory description_) { + priceFeedA = priceFeedA_; + priceFeedB = priceFeedB_; + uint8 priceFeedADecimals = AggregatorV3Interface(priceFeedA_).decimals(); + uint8 priceFeedBDecimals = AggregatorV3Interface(priceFeedB_).decimals(); + priceFeedAScale = signed256(10 ** (priceFeedADecimals)); + priceFeedBScale = signed256(10 ** (priceFeedBDecimals)); + + if (decimals_ > 18) revert BadDecimals(); + decimals = decimals_; + description = description_; + priceFeedScale = int256(10 ** decimals); + } + + /** + * @notice Calculates the latest round data using data from the two price feeds + * @return roundId Round id from price feed B + * @return answer Latest price + * @return startedAt Timestamp when the round was started; passed on from price feed B + * @return updatedAt Timestamp when the round was last updated; passed on from price feed B + * @return answeredInRound Round id in which the answer was computed; passed on from price feed B + * @dev Note: Only the `answer` really matters for downstream contracts that use this price feed (e.g. Comet) + **/ + function latestRoundData() override external view returns (uint80, int256, uint256, uint256, uint80) { + (, int256 priceA, , , ) = AggregatorV3Interface(priceFeedA).latestRoundData(); + (uint80 roundId_, int256 priceB, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(priceFeedB).latestRoundData(); + + if (priceA <= 0 || priceB <= 0) return (roundId_, 0, startedAt_, updatedAt_, answeredInRound_); + + // int256 price = priceA * (priceFeedBScale/priceB) * priceFeedScale / priceFeedAScale; + int256 price = priceA * priceFeedBScale * priceFeedScale / priceB / priceFeedAScale; + 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; + } +} diff --git a/contracts/test/MockOracle.sol b/contracts/test/MockOracle.sol new file mode 100644 index 000000000..9576720c5 --- /dev/null +++ b/contracts/test/MockOracle.sol @@ -0,0 +1,46 @@ +// 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 MockOracle { + + /// @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; + + /** + * @notice Construct a new scaling price feed + * @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from + **/ + constructor(address underlyingPriceFeed_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = AggregatorV3Interface(underlyingPriceFeed_).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 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 + ) { + (uint80 roundId_, int256 price, uint256 startedAt_, , uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData(); + return (roundId_, price, startedAt_, block.timestamp, answeredInRound_); + } +} diff --git a/contracts/vendor/renzo/IBalancerRateProvider.sol b/contracts/vendor/renzo/IBalancerRateProvider.sol new file mode 100644 index 000000000..bea3f6feb --- /dev/null +++ b/contracts/vendor/renzo/IBalancerRateProvider.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IBalancerRateProvider { + function getRate() external view returns (uint256); +} diff --git a/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts new file mode 100644 index 000000000..02e75ab6c --- /dev/null +++ b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts @@ -0,0 +1,155 @@ +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 EZETH_ADDRESS = '0xbf5495Efe5DB9ce00f80364C8B423567e58d2110'; +const EZETH_PRICE_FEED_ADDRESS = '0x387dBc0fB00b26fb085aa658527D5BE98302c84C'; + +export default migration('1718352598_add_ezeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _ezETHScalingPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/EzETHExchangeRatePriceFeed.sol', + [ + EZETH_PRICE_FEED_ADDRESS, // ezETH / ETH exchange rate price feed + 8, // decimals + 'ezETH / ETH exchange rate', // description + ] + ); + return { ezETHScalingPriceFeed: _ezETHScalingPriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { ezETHScalingPriceFeed }) { + const trace = deploymentManager.tracer(); + + const ezETH = await deploymentManager.existing( + 'ezETH', + EZETH_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const ezEthPricefeed = await deploymentManager.existing( + 'ezETH:priceFeed', + ezETHScalingPriceFeed, + 'mainnet' + ); + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: ezEthPricefeed.address, + decimals: await ezETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.90, 18), + supplyCap: exp(2_900, 18), + }; + + const mainnetActions = [ + // 1. Add ezETH as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, ezETHAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add ezETH as collateral into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ezETH 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-market-ezeth-on-eth-mainnet/5062/7).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/874) and [forum discussion](https://www.comp.xyz/t/add-market-ezeth-on-eth-mainnet/5062).\n\n# [Yield Risk](https://www.comp.xyz/t/add-market-ezeth-on-eth-mainnet/5062/7#yield-risk-7)\n\nCurrently LRTs such as ezETH have elevated yields due to points program. EigenLayer maturity and AVS launch will cause yield shocks and consequentially elevate slippage magnitude and liquidity on DEXs. Gauntlet flags this potential risk to the community.\n\n\n## Proposal Actions\n\nThe first proposal action adds ezETH asset as collateral with the corresponding configuration.\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 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, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const ezETHAssetConfig = { + asset: ezETH.address, + priceFeed: '', + decimals: await ezETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(2_900, 18), // 2_900 + }; + + // 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 configuratorEsETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[ezETHAssetIndex]; + expect(ezETHAssetConfig.asset).to.be.equal( + configuratorEsETHAssetConfig.asset + ); + expect(ezETHAssetConfig.decimals).to.be.equal( + configuratorEsETHAssetConfig.decimals + ); + expect(ezETHAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.borrowCollateralFactor + ); + expect(ezETHAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidateCollateralFactor + ); + expect(ezETHAssetConfig.liquidationFactor).to.be.equal( + configuratorEsETHAssetConfig.liquidationFactor + ); + expect(ezETHAssetConfig.supplyCap).to.be.equal( + configuratorEsETHAssetConfig.supplyCap + ); + }, +}); diff --git a/deployments/mainnet/weth/relations.ts b/deployments/mainnet/weth/relations.ts index 40c0368ee..4211ead18 100644 --- a/deployments/mainnet/weth/relations.ts +++ b/deployments/mainnet/weth/relations.ts @@ -14,7 +14,7 @@ export default { 'AppProxyUpgradeable': { artifact: 'contracts/ERC20.sol:ERC20', }, - UUPSProxy: { + 'UUPSProxy': { artifact: 'contracts/ERC20.sol:ERC20', delegates: { field: { @@ -22,7 +22,7 @@ export default { } } }, - TransparentUpgradeableProxy: { + 'TransparentUpgradeableProxy': { artifact: 'contracts/ERC20.sol:ERC20', delegates: { field: { @@ -30,4 +30,5 @@ export default { } } }, -}; \ No newline at end of file +}; + diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 492c0ab8e..03f59d4d8 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -327,6 +327,36 @@ export async function fetchLogs( } } +async function redeployRenzoOracle(dm: DeploymentManager){ + if(dm.network === 'mainnet' && dm.deployment === 'weth') { + // renzo admin 0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A + const renzoOracle = new Contract( + '0x5a12796f7e7EBbbc8a402667d266d2e65A814042', + [ + 'function setOracleAddress(address _token, address _oracleAddress) external', + ], + dm.hre.ethers.provider + ); + + const admin = await impersonateAddress(dm, '0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A'); + // set balance + await dm.hre.ethers.provider.send('hardhat_setBalance', [ + admin.address, + dm.hre.ethers.utils.hexStripZeros(dm.hre.ethers.utils.parseUnits('100', 'ether').toHexString()), + ]); + + const newOracle = await dm.deploy( + 'stETH:Oracle', + 'test/MockOracle.sol', + [ + '0x86392dC19c0b719886221c78AB11eb8Cf5c52812', // stETH / ETH oracle address + ] + ); + + await renzoOracle.connect(admin).setOracleAddress('0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', newOracle.address); + } +} + export async function executeOpenProposal( dm: DeploymentManager, { id, startBlock, endBlock }: OpenProposal @@ -370,6 +400,7 @@ export async function executeOpenProposal( await setNextBaseFeeToZero(dm); await governor.execute(id, { gasPrice: 0, gasLimit: 12000000 }); } + await redeployRenzoOracle(dm); } // Instantly executes some actions through the governance proposal process @@ -606,4 +637,4 @@ export async function timeUntilUnderwater({ comet, actor, fudgeFactor = 0n }: { // XXX throw error if baseBalanceOf is positive and liquidationMargin is positive return Number((-liquidationMargin * factorScale / baseLiquidity / borrowRate) + fudgeFactor); -} +} \ No newline at end of file From ad5d9b5b6221acd81350f3d7a29fa7280d568609 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Mon, 22 Jul 2024 19:23:45 +0300 Subject: [PATCH 4/5] Add wstETH as collateral to USDC market on Arbitrum (#883) Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> --- .../1720603419_add_wsteth_as_collateral.ts | 228 ++++++++++++++++++ deployments/arbitrum/usdc/relations.ts | 12 + scenario/BulkerScenario.ts | 16 +- 3 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 deployments/arbitrum/usdc/migrations/1720603419_add_wsteth_as_collateral.ts diff --git a/deployments/arbitrum/usdc/migrations/1720603419_add_wsteth_as_collateral.ts b/deployments/arbitrum/usdc/migrations/1720603419_add_wsteth_as_collateral.ts new file mode 100644 index 000000000..1e5673ff4 --- /dev/null +++ b/deployments/arbitrum/usdc/migrations/1720603419_add_wsteth_as_collateral.ts @@ -0,0 +1,228 @@ +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'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +const WSTETH_ADDRESS = '0x5979D7b546E38E414F7E9822514be443A4800529'; +const WSTETH_STETH_PRICE_FEED_ADDRESS = '0xB1552C5e96B312d0Bf8b554186F846C40614a540'; +const STETH_ETH_PRICE_FEED_ADDRESS = '0xded2c52b75B24732e9107377B7Ba93eC1fFa4BAf'; +const ETH_USD_PRICE_FEED_ADDRESS = '0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612'; + +let newPriceFeedAddress: string; +let existingPriceFeedAddress: string; + +export default migration('1720603419_add_wsteth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wstETHToEthScalingPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + WSTETH_STETH_PRICE_FEED_ADDRESS, // wstETH / stETH price feed + STETH_ETH_PRICE_FEED_ADDRESS, // stETH / ETH price feed + 8, // decimals + 'wstETH / ETH price feed' // description + ] + ); + + const _wstETHToUsdScalingPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + _wstETHToEthScalingPriceFeed.address, // wstETH / stETH / ETH price feed + ETH_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'wstETH / USD price feed' // description + ], + true + ); + + return { wstETHToUsdScalingPriceFeed: _wstETHToUsdScalingPriceFeed.address }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { wstETHToUsdScalingPriceFeed } + ) => { + const trace = deploymentManager.tracer(); + + const wstETH = await deploymentManager.existing( + 'wstETH', + WSTETH_ADDRESS, + 'arbitrum', + 'contracts/ERC20.sol:ERC20' + ); + + const wstETHPricefeed = await deploymentManager.existing( + 'wstETH:priceFeed', + wstETHToUsdScalingPriceFeed, + 'arbitrum' + ); + + newPriceFeedAddress = wstETHToUsdScalingPriceFeed; + existingPriceFeedAddress = wstETHPricefeed.address; + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + timelock: l2Timelock, + } = await deploymentManager.getContracts(); + + const { governor, + arbitrumInbox, + timelock, + } = await govDeploymentManager.getContracts(); + + const refundAddress = l2Timelock.address; + + const newAssetConfig = { + asset: wstETH.address, + priceFeed: wstETHPricefeed.address, + decimals: await wstETH.decimals(), + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.90, 18), + supplyCap: exp(1_500, 18), + }; + + 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 createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo new Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = '# Add wstETH as collateral into cUSDCv3 on Arbitrum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wstETH 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).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/883) and [forum discussion](https://www.comp.xyz/t/temp-check-add-wsteth-as-a-collateral-on-base-eth-market-usdc-market-on-arbitrum-and-ethereum-mainnet/4867/).\n\n\n## Proposal Actions\n\nThe first proposal action adds wstETH 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 wstETHAssetIndex = Number(await comet.numAssets()) - 1; + + const wstETHAssetConfig = { + asset: WSTETH_ADDRESS, + priceFeed: newPriceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.8, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.90, 18), + supplyCap: exp(1_500, 18), + }; + + // check that we set up correct new deployed price feed + expect(newPriceFeedAddress).to.be.equal(existingPriceFeedAddress) + + // 1. Compare proposed asset config with Comet asset info + const wstETHAssetInfo = await comet.getAssetInfoByAddress( + WSTETH_ADDRESS + ); + expect(wstETHAssetIndex).to.be.equal(wstETHAssetInfo.offset); + expect(wstETHAssetConfig.asset).to.be.equal(wstETHAssetInfo.asset); + expect(wstETHAssetConfig.priceFeed).to.be.equal( + wstETHAssetInfo.priceFeed + ); + expect(exp(1, wstETHAssetConfig.decimals)).to.be.equal( + wstETHAssetInfo.scale + ); + expect(wstETHAssetConfig.borrowCollateralFactor).to.be.equal( + wstETHAssetInfo.borrowCollateralFactor + ); + expect(wstETHAssetConfig.liquidateCollateralFactor).to.be.equal( + wstETHAssetInfo.liquidateCollateralFactor + ); + expect(wstETHAssetConfig.liquidationFactor).to.be.equal( + wstETHAssetInfo.liquidationFactor + ); + expect(wstETHAssetConfig.supplyCap).to.be.equal( + wstETHAssetInfo.supplyCap + ); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWstETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[wstETHAssetIndex]; + expect(wstETHAssetConfig.asset).to.be.equal( + configuratorWstETHAssetConfig.asset + ); + expect(wstETHAssetConfig.priceFeed).to.be.equal( + configuratorWstETHAssetConfig.priceFeed + ); + expect(wstETHAssetConfig.decimals).to.be.equal( + configuratorWstETHAssetConfig.decimals + ); + expect(wstETHAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorWstETHAssetConfig.borrowCollateralFactor + ); + expect(wstETHAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorWstETHAssetConfig.liquidateCollateralFactor + ); + expect(wstETHAssetConfig.liquidationFactor).to.be.equal( + configuratorWstETHAssetConfig.liquidationFactor + ); + expect(wstETHAssetConfig.supplyCap).to.be.equal( + configuratorWstETHAssetConfig.supplyCap + ); + }, +}); diff --git a/deployments/arbitrum/usdc/relations.ts b/deployments/arbitrum/usdc/relations.ts index b1ca91f34..103293632 100644 --- a/deployments/arbitrum/usdc/relations.ts +++ b/deployments/arbitrum/usdc/relations.ts @@ -44,4 +44,16 @@ export default { } } }, + OssifiableProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + // wstETH + '0x5979D7b546E38E414F7E9822514be443A4800529': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; \ No newline at end of file diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 056059c0e..1f4ed579f 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -10,11 +10,11 @@ scenario( { filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), supplyCaps: { - $asset0: 3000, - $asset1: 3000, + $asset0: 5000, + $asset1: 5000, }, tokenBalances: { - albert: { $base: '== 0', $asset0: 3000, $asset1: 3000 }, + albert: { $base: '== 0', $asset0: 5000, $asset1: 5000 }, $comet: { $base: 5000 }, }, }, @@ -30,7 +30,7 @@ scenario( 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 = 3000n * collateralScale; + const toSupplyCollateral = 5000n * collateralScale; const toBorrowBase = 1000n * baseScale; const toTransferBase = 500n * baseScale; const toSupplyEth = exp(0.01, 18); @@ -167,11 +167,11 @@ scenario( { filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'linea-goerli' }]), supplyCaps: { - $asset0: 3000, - $asset1: 3000, + $asset0: 5000, + $asset1: 5000, }, tokenBalances: { - albert: { $base: '== 1000000', $asset0: 3000, $asset1: 3000 }, + albert: { $base: '== 1000000', $asset0: 5000, $asset1: 5000 }, $comet: { $base: 5000 }, } }, @@ -189,7 +189,7 @@ scenario( const collateralScale = scaleBN.toBigInt(); const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); const toSupplyBase = 1_000_000n * baseScale; - const toSupplyCollateral = 3000n * collateralScale; + const toSupplyCollateral = 5000n * collateralScale; const toBorrowBase = 1000n * baseScale; const toTransferBase = 500n * baseScale; const toSupplyEth = exp(0.01, 18); From 889f0a2733019e46a92a1b2e55ee935c9937f753 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Mon, 22 Jul 2024 19:23:58 +0300 Subject: [PATCH 5/5] Deploy WETH market on Optimism (#882) 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/optimism/weth/configuration.json | 52 +++ deployments/optimism/weth/deploy.ts | 98 ++++++ .../1720515728_configurate_and_ens.ts | 306 ++++++++++++++++++ deployments/optimism/weth/relations.ts | 50 +++ deployments/optimism/weth/roots.json | 10 + hardhat.config.ts | 10 +- scenario/SupplyScenario.ts | 6 +- src/deploy/index.ts | 3 +- 9 files changed, 532 insertions(+), 5 deletions(-) create mode 100644 deployments/optimism/weth/configuration.json create mode 100644 deployments/optimism/weth/deploy.ts create mode 100644 deployments/optimism/weth/migrations/1720515728_configurate_and_ens.ts create mode 100644 deployments/optimism/weth/relations.ts create mode 100644 deployments/optimism/weth/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 156f1aa1c..0fecdd98c 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, scroll-goerli, scroll-usdc] + 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] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/optimism/weth/configuration.json b/deployments/optimism/weth/configuration.json new file mode 100644 index 000000000..9aabf9e05 --- /dev/null +++ b/deployments/optimism/weth/configuration.json @@ -0,0 +1,52 @@ +{ + "name": "Compound WETH", + "symbol": "cWETHv3", + "baseToken": "WETH", + "baseTokenAddress": "0x4200000000000000000000000000000000000006", + "pauseGuardian": "0x3fFd6c073a4ba24a113B18C8F373569640916A45", + "borrowMin": "0.000001e18", + "storeFrontPriceFactor": 0.7, + "targetReserves": "5000e18", + "rates": { + "supplyBase": 0, + "supplySlopeLow": 0.0185, + "supplyKink": 0.85, + "supplySlopeHigh": 1, + "borrowBase": 0.01, + "borrowSlopeLow": 0.014, + "borrowKink": 0.85, + "borrowSlopeHigh": 1.15 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "46296296296e0", + "baseBorrowSpeed": "34722222222e0", + "baseMinForRewards": "1e18" + }, + "assets": { + "wstETH": { + "address": "0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.93, + "liquidationFactor": 0.97, + "supplyCap": "1_300e18" + }, + "rETH": { + "address": "0x9Bcef72be871e61ED4fBbc7630889beE758eb81D", + "decimals": "18", + "borrowCF": 0.9, + "liquidateCF": 0.93, + "liquidationFactor": 0.97, + "supplyCap": "470e18" + }, + "WBTC": { + "address": "0x68f180fcCe6836688e9084f035309E29Bf0A2095", + "decimals": "8", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.90, + "supplyCap": "60e8" + } + } +} \ No newline at end of file diff --git a/deployments/optimism/weth/deploy.ts b/deployments/optimism/weth/deploy.ts new file mode 100644 index 000000000..9b0650811 --- /dev/null +++ b/deployments/optimism/weth/deploy.ts @@ -0,0 +1,98 @@ +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 trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + + const WETH = await deploymentManager.existing( + 'WETH', + '0x4200000000000000000000000000000000000006', + 'optimism' + ); + const rETH = await deploymentManager.existing( + 'rETH', + '0x9Bcef72be871e61ED4fBbc7630889beE758eb81D', + 'optimism' + ); + const wstETH = await deploymentManager.existing( + 'wstETH', + '0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb', + 'optimism' + ); + const WBTC = await deploymentManager.existing( + 'WBTC', + '0x68f180fcCe6836688e9084f035309E29Bf0A2095', + 'optimism' + ); + + const COMP = await deploymentManager.existing( + 'COMP', + '0x7e7d4467112689329f7E06571eD0E8CbAd4910eE', + 'optimism' + ); + + const wethConstantPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ConstantPriceFeed.sol', + [ + 8, // decimals + exp(1, 8) // constantPrice + ] + ); + + const rETHPriceFeed = await deploymentManager.deploy( + 'rETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x22F3727be377781d1579B7C9222382b21c9d1a8f', // rETH / ETH price feed + 8 // decimals + ] + ); + + const wstETHPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + '0xe59EBa0D492cA53C6f46015EEa00517F2707dc77', // wstETH / stETH price feed + '0x14d2d3a82AeD4019FddDfe07E8bdc485fb0d2249', // stETH / ETH price feed + 8, // decimals + 'wstETH / ETH price feed' // description + ] + ); + + const wbtcETHPriceFeed = await deploymentManager.deploy( + 'WBTC:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + '0x718A5788b89454aAE3A028AE9c111A29Be6c2a6F', // WBTC / USD price feed + '0x13e3Ee699D1909E989722E753853AE30b17e08c5', // ETH / USD price feed (reversed) + 8, // decimals + 'WBTC / ETH price feed' // description + ] + ); + + // Import shared contracts from cUSDCv3 and cUSDTv3 deployments + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'optimism', 'usdc'); + // we use cometFactory from usdc deployment, because usdt deployment use the same one. + // the factory is not the latest version of comet (update for USDT on Mainnet) + // for this market it works perfectly + const cometFactory = await deploymentManager.fromDep('cometFactory', 'optimism', 'usdc'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'optimism', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'optimism', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'optimism', 'usdc'); + const bulker = await deploymentManager.fromDep('bulker', 'optimism', 'usdc'); + const localTimelock = await deploymentManager.fromDep('timelock', 'optimism', 'usdc'); + const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'optimism', 'usdc'); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bridgeReceiver, + bulker, + rewards, + COMP + }; +} diff --git a/deployments/optimism/weth/migrations/1720515728_configurate_and_ens.ts b/deployments/optimism/weth/migrations/1720515728_configurate_and_ens.ts new file mode 100644 index 000000000..fd15ad64b --- /dev/null +++ b/deployments/optimism/weth/migrations/1720515728_configurate_and_ens.ts @@ -0,0 +1,306 @@ +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { ethers } from 'ethers'; +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'; + +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 opCOMPAddress = '0x7e7d4467112689329f7E06571eD0E8CbAd4910eE'; +const wethAmountToBridge = exp(10, 18); + +export default migration('1720515728_configurate_and_ens', { + prepare: async () => { + return {}; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager + ) => { + const trace = deploymentManager.tracer(); + const { utils } = ethers; + + const cometFactory = await deploymentManager.fromDep( + 'cometFactory', + 'optimism', + 'usdc' + ); + const { + bridgeReceiver, + timelock: localTimelock, + comet, + cometAdmin, + configurator, + rewards, + WETH + } = await deploymentManager.getContracts(); + + const { + opL1CrossDomainMessenger, + opL1StandardBridge, + 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 opChainId = 10; + const newMarketObject = { baseSymbol: 'WETH', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse( + await ENSResolver.text(subdomainHash, ENSTextRecordKey) + ); + if (officialMarketsJSON[opChainId]) { + officialMarketsJSON[opChainId].push(newMarketObject); + } else { + officialMarketsJSON[opChainId] = [newMarketObject]; + } + + const configuration = await getConfigurationStruct(deploymentManager); + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory( + comet.address, + cometFactory.address + ) + ); + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration( + comet.address, + configuration + ) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, opCOMPAddress] + ); + const depositCalldata = await calldata(WETH.populateTransaction.deposit()); + const transferCalldata = await calldata(WETH.populateTransaction.transfer(comet.address, wethAmountToBridge)); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + configurator.address, + cometAdmin.address, + rewards.address, + WETH.address, + WETH.address, + ], + [0, 0, 0, 0, wethAmountToBridge, 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)', + 'deposit()', + 'transfer(address,uint256)', + ], + [ + setFactoryCalldata, + setConfigurationCalldata, + deployAndUpgradeToCalldata, + setRewardConfigCalldata, + depositCalldata, + transferCalldata + ], + ] + ); + + const actions = [ + // 1. Bridge ETH from Ethereum to OP timelock using L1StandardBridge + { + contract: opL1StandardBridge, + // function depositETHTo(address _to,uint32 _minGasLimit,bytes calldata _extraData) + signature: 'depositETHTo(address,uint32,bytes)', + args: [ + localTimelock.address, + 200_000, + '0x', + ], + value: wethAmountToBridge + }, + // 2. Set Comet configuration + deployAndUpgradeTo new Comet, set Reward Config on Optimism + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000], + }, + // 3. 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)] + ), + }, + ]; + + // the description has speeds. speeds will be set up on on-chain proposal + const description = '# Initialize cWETHv3 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 WETH market on Optimism; upon execution, cWETHv3 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/add-market-eth-on-optimism/5274/5).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/882), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/9941213214/job/27464571077) and [forum discussion](https://www.comp.xyz/t/add-market-eth-on-optimism/5274).\n\n\n## Proposal Actions\n\nThe first action bridges 10 ETH as seed reserves from Mainnet Timelock to Optimism L2 Timelock using OpL1StandardBridge.\n\nThe second 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 4 COMP/day and borrow speed to be 3 COMP/day. The last two steps are to wrap ETH into WETH and transfer seed reserves into Comet\n\nThe third 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 cWETHv3 market.'; + const txn = await govDeploymentManager.retry(async () => { + return 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 true; + }, + + async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { + const { + comet, + rewards, + COMP + } = await deploymentManager.getContracts(); + + const { + timelock + } = await govDeploymentManager.getContracts(); + + // 2. + // uncomment on on-chain proposal PR + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + expect(stateChanges).to.deep.equal({ + rETH: { + supplyCap: exp(470, 18) + }, + wstETH: { + supplyCap: exp(1_300, 18) + }, + WBTC: { + supplyCap: exp(60, 8) + }, + baseTrackingSupplySpeed: exp(4 / 86400, 15, 18), // 46296296296 + baseTrackingBorrowSpeed: exp(3 / 86400, 15, 18), // 34722222222 + }); + + 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); + + // 1. + expect(await comet.getReserves()).to.be.equal(wethAmountToBridge); + + // 3. + const ENSResolver = await govDeploymentManager.existing( + 'ENSResolver', + ENSResolverAddress + ); + const ENSRegistry = await govDeploymentManager.existing('ENSRegistry', ENSRegistryAddress, 'goerli'); + 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' + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB' + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214' + }, + { + baseSymbol: 'WETH', + cometAddress: comet.address + }, + ], + 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' + }, + ] + }); + } +}); diff --git a/deployments/optimism/weth/relations.ts b/deployments/optimism/weth/relations.ts new file mode 100644 index 000000000..0b4c85f7b --- /dev/null +++ b/deployments/optimism/weth/relations.ts @@ -0,0 +1,50 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: + 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver', + }, + Proxy: { + artifact: 'contracts/ERC20.sol:ERC20', + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + l2StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + // rETH + L2StandardERC20: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + + // wstETH + OssifiableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + + // WBTC + WBTC: { + artifact: 'contracts/ERC20.sol:ERC20' + }, +}; diff --git a/deployments/optimism/weth/roots.json b/deployments/optimism/weth/roots.json new file mode 100644 index 000000000..a167c1b41 --- /dev/null +++ b/deployments/optimism/weth/roots.json @@ -0,0 +1,10 @@ +{ + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "comet": "0xE36A30D249f7761327fd973001A32010b521b6Fd", + "configurator": "0x84E93EC6170ED630f5ebD89A1AAE72d4F63f2713", + "rewards": "0x443EA0340cb75a160F31A440722dec7b5bc3C2E9", + "bridgeReceiver": "0xC3a73A70d1577CD5B02da0bA91C0Afc8fA434DAF", + "bulker": "0xcb3643CC8294B23171272845473dEc49739d4Ba3", + "COMP": "0x7e7d4467112689329f7E06571eD0E8CbAd4910eE" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 180b8b4d5..9d867fc32 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -42,6 +42,7 @@ import baseGoerliWethRelationConfigMap from './deployments/base-goerli/weth/rela import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relations'; import optimismRelationConfigMap from './deployments/optimism/usdc/relations'; import optimismUsdtRelationConfigMap from './deployments/optimism/usdt/relations'; +import optimismWethRelationConfigMap from './deployments/optimism/weth/relations'; import scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; @@ -382,7 +383,8 @@ const config: HardhatUserConfig = { }, optimism: { usdc: optimismRelationConfigMap, - usdt: optimismUsdtRelationConfigMap + usdt: optimismUsdtRelationConfigMap, + weth: optimismWethRelationConfigMap }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap @@ -543,6 +545,12 @@ const config: HardhatUserConfig = { deployment: 'usdt', auxiliaryBase: 'mainnet', }, + { + name: 'optimism-weth', + network: 'optimism', + deployment: 'weth', + auxiliaryBase: 'mainnet' + }, { name: 'scroll-goerli', network: 'scroll-goerli', diff --git a/scenario/SupplyScenario.ts b/scenario/SupplyScenario.ts index ddbd993d5..f9075208b 100644 --- a/scenario/SupplyScenario.ts +++ b/scenario/SupplyScenario.ts @@ -507,7 +507,8 @@ scenario( /transfer amount exceeds spender allowance/, /Dai\/insufficient-allowance/, symbol === 'WETH' ? /Transaction reverted without a reason string/ : /.^/, - symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/ + symbol === 'wstETH' ? /0xc2139725/ : /.^/, + symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/, ] ); } @@ -592,7 +593,8 @@ scenario( /transfer amount exceeds balance/, /Dai\/insufficient-balance/, symbol === 'WETH' ? /Transaction reverted without a reason string/ : /.^/, - symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/ + symbol === 'wstETH' ? /0x00b284f2/ : /.^/, + symbol === 'WMATIC' ? /Transaction reverted without a reason string/ : /.^/, ] ); } diff --git a/src/deploy/index.ts b/src/deploy/index.ts index f912c38bf..5212bd1d9 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -135,7 +135,8 @@ export const WHALES = { ], optimism: [ '0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26', // OP whale - '0x8af3827a41c26c7f32c81e93bb66e837e0210d5c' // USDC whale + '0x8af3827a41c26c7f32c81e93bb66e837e0210d5c', // USDC whale + '0xc45A479877e1e9Dfe9FcD4056c699575a1045dAA', // wstETH whale ] };