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/deployments/mainnet/weth/migrations/1720779152_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1720779152_add_rsweth_as_collateral.ts new file mode 100644 index 000000000..9a246400d --- /dev/null +++ b/deployments/mainnet/weth/migrations/1720779152_add_rsweth_as_collateral.ts @@ -0,0 +1,156 @@ +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 SFRXETH_ADDRESS = '0xac3E018457B222d93114458476f3E3416Abbe38F'; +const SFRXETH_PRICE_FEED_ADDRESS = '0xB9af7723CfBd4469A7E8aa60B93428D648Bda99d'; +let newPriceFeedAddress: string; + +export default migration('1720779152_add_sfrxeth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _sfrxETHScalingPriceFeed = await deploymentManager.deploy( + 'sfrxETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + SFRXETH_PRICE_FEED_ADDRESS, // sfrxETH / ETH price feed + 8 // decimals + ] + ); + return { sfrxETHScalingPriceFeed: _sfrxETHScalingPriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { sfrxETHScalingPriceFeed }) { + + const trace = deploymentManager.tracer(); + + const sfrxETH = await deploymentManager.existing( + 'sfrxETH', + SFRXETH_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const sfrxEthPricefeed = await deploymentManager.existing( + 'sfrxETH:priceFeed', + sfrxETHScalingPriceFeed, + 'mainnet' + ); + + newPriceFeedAddress = sfrxEthPricefeed.address; + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const sfrxETHAssetConfig = { + asset: sfrxETH.address, + priceFeed: sfrxEthPricefeed.address, + decimals: await sfrxETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(5_000, 18), + }; + + const mainnetActions = [ + // 1. Add sfrxETH as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, sfrxETHAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add sfrxETH as collateral into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add sfrxETH 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/886) and [forum discussion](https://www.comp.xyz/t/listing-ethx-on-compound/4730).\n\n\n## Proposal Actions\n\nThe first proposal action adds sfrxETH 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 sfrxETHAssetIndex = Number(await comet.numAssets()) - 1; + + const sfrxETH = await deploymentManager.existing( + 'sfrxETH', + SFRXETH_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const sfrxETHAssetConfig = { + asset: sfrxETH.address, + priceFeed: newPriceFeedAddress, + decimals: await sfrxETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(5_000, 18), + }; + + // 1. Compare sfrxETH asset config with Comet and Configurator asset info + const cometSfrxETHAssetInfo = await comet.getAssetInfoByAddress( + SFRXETH_ADDRESS + ); + expect(sfrxETHAssetIndex).to.be.equal(cometSfrxETHAssetInfo.offset); + expect(sfrxETHAssetConfig.asset).to.be.equal(cometSfrxETHAssetInfo.asset); + expect(exp(1, sfrxETHAssetConfig.decimals)).to.be.equal( + cometSfrxETHAssetInfo.scale + ); + expect(sfrxETHAssetConfig.borrowCollateralFactor).to.be.equal( + cometSfrxETHAssetInfo.borrowCollateralFactor + ); + expect(sfrxETHAssetConfig.liquidateCollateralFactor).to.be.equal( + cometSfrxETHAssetInfo.liquidateCollateralFactor + ); + expect(sfrxETHAssetConfig.liquidationFactor).to.be.equal( + cometSfrxETHAssetInfo.liquidationFactor + ); + expect(sfrxETHAssetConfig.supplyCap).to.be.equal( + cometSfrxETHAssetInfo.supplyCap + ); + const configuratorSfrxETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[sfrxETHAssetIndex]; + expect(sfrxETHAssetConfig.asset).to.be.equal( + configuratorSfrxETHAssetConfig.asset + ); + expect(sfrxETHAssetConfig.decimals).to.be.equal( + configuratorSfrxETHAssetConfig.decimals + ); + expect(sfrxETHAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorSfrxETHAssetConfig.borrowCollateralFactor + ); + expect(sfrxETHAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorSfrxETHAssetConfig.liquidateCollateralFactor + ); + expect(sfrxETHAssetConfig.liquidationFactor).to.be.equal( + configuratorSfrxETHAssetConfig.liquidationFactor + ); + expect(sfrxETHAssetConfig.supplyCap).to.be.equal( + configuratorSfrxETHAssetConfig.supplyCap + ); + }, +}); diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 492c0ab8e..3d0c8d643 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