From b7fdb3411cec480e0af292e088a3eeebbd114735 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 24 Jun 2024 11:22:38 +0300 Subject: [PATCH 01/18] feat: custom feeds --- .../pricefeeds/EzETHExchangeRatePriceFeed.sol | 88 ++++++++++++++++++ .../ReverseMultiplicativePriceFeed.sol | 89 +++++++++++++++++++ .../vendor/renzo/IBalancerRateProvider.sol | 6 ++ 3 files changed, 183 insertions(+) create mode 100644 contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol create mode 100644 contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol create mode 100644 contracts/vendor/renzo/IBalancerRateProvider.sol diff --git a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol new file mode 100644 index 000000000..688fc1ac0 --- /dev/null +++ b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol @@ -0,0 +1,88 @@ +// 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(); + + /// @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 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; + 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 (0, scalePrice(int256(rate)), 0, 0, 0); + } + + 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; + } +} diff --git a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol new file mode 100644 index 000000000..04754c92c --- /dev/null +++ b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol @@ -0,0 +1,89 @@ +// 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 + * @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 Combined scale of the two underlying Chainlink price feeds + int public immutable priceFeedAScalling; + + /// @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(); + priceFeedAScalling = signed256(10 ** (priceFeedADecimals)); + + 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 * priceB * priceFeedScale / combinedScale; + int256 price = priceA * int256(10**(AggregatorV3Interface(priceFeedB).decimals())) * priceFeedScale / priceB / priceFeedAScalling; + 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/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); +} From 228b6e443ce75f6e794877786c4b4ee8c84a301f Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 24 Jun 2024 12:45:15 +0300 Subject: [PATCH 02/18] feat: add 'for example' --- contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol index 04754c92c..779566f16 100644 --- a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol +++ b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol @@ -7,6 +7,7 @@ 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 { From 36cde41b14aca1ef6681aa95eb171738cbcf2c40 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 25 Jun 2024 13:06:34 +0300 Subject: [PATCH 03/18] feat: add ezETH to weth mainnet --- contracts/test/MockOracle.sol | 46 ++++++ .../1718352598_add_ezeth_as_collateral.ts | 155 ++++++++++++++++++ deployments/mainnet/weth/relations.ts | 20 ++- scenario/LiquidationBotScenario.ts | 2 +- scenario/utils/index.ts | 33 +++- 5 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 contracts/test/MockOracle.sol create mode 100644 deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts 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/1718352598_add_ezeth_as_collateral.ts b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts new file mode 100644 index 000000000..4345ed66b --- /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/867), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/9545875152/job/26307579839) 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 false; + }, + + 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 37278965c..031976417 100644 --- a/deployments/mainnet/weth/relations.ts +++ b/deployments/mainnet/weth/relations.ts @@ -13,5 +13,21 @@ export default { }, 'AppProxyUpgradeable': { artifact: 'contracts/ERC20.sol:ERC20', - } -}; \ No newline at end of file + }, + 'UUPSProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + 'TransparentUpgradeableProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 763468c9e..6cf5b64ec 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -739,7 +739,7 @@ scenario( const assetAmounts = { mainnet: { usdc: ' == 5000', // COMP - weth: ' == 10000', // CB_ETH + weth: ' == 7000', // CB_ETH }, }; 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 51e63e5aeaedbb85097d194d956b2f969be3b21a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 1 Jul 2024 13:38:48 +0300 Subject: [PATCH 04/18] feat: new pricefeeds --- .../pricefeeds/RateBasedScalingPriceFeed.sol | 90 +++++++++++++++++++ .../pricefeeds/rsETHScalingPriceFeed.sol | 87 ++++++++++++++++++ contracts/vendor/kelp/IrsETHOracle.sol | 6 ++ 3 files changed, 183 insertions(+) create mode 100644 contracts/pricefeeds/RateBasedScalingPriceFeed.sol create mode 100644 contracts/pricefeeds/rsETHScalingPriceFeed.sol create mode 100644 contracts/vendor/kelp/IrsETHOracle.sol diff --git a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol new file mode 100644 index 000000000..502f5b084 --- /dev/null +++ b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../IPriceFeed.sol"; + +interface IRateProvider { + function getRate() external view returns (uint256); +} + +/** + * @title Scaling price feed for rate based oracles + * @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 RateBasedScalingPriceFeed 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_, uint8 underlyingDecimals_, string memory description_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = decimals_; + description = description_; + + uint8 priceFeedDecimals = underlyingDecimals_; + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = priceFeedDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ - priceFeedDecimals)) + : signed256(10 ** (priceFeedDecimals - 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 = IRateProvider(underlyingPriceFeed).getRate(); + return (roundId, scalePrice(int256(rate)), 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/pricefeeds/rsETHScalingPriceFeed.sol b/contracts/pricefeeds/rsETHScalingPriceFeed.sol new file mode 100644 index 000000000..506115d47 --- /dev/null +++ b/contracts/pricefeeds/rsETHScalingPriceFeed.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../vendor/kelp/IrsETHOracle.sol"; +import "../IPriceFeed.sol"; + +/** + * @title Scaling price feed for rsETH + * @notice A custom price feed that scales up or down the price received from an underlying Kelp price feed and returns the result + * @author Compound + */ +contract rsETHScalingPriceFeed 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 Kelp 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 kelpPriceFeedDecimals = 18; + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = kelpPriceFeedDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ - kelpPriceFeedDecimals)) + : signed256(10 ** (kelpPriceFeedDecimals - 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 + ) { + int256 price = int256(IrsETHOracle(underlyingPriceFeed).rsETHPrice()); + 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/vendor/kelp/IrsETHOracle.sol b/contracts/vendor/kelp/IrsETHOracle.sol new file mode 100644 index 000000000..7b462be7c --- /dev/null +++ b/contracts/vendor/kelp/IrsETHOracle.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +interface IrsETHOracle { + function rsETHPrice() external view returns (uint256); +} From 2c339853e1afe6d75f1f44fa689908fa23c74705 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 1 Jul 2024 14:11:48 +0300 Subject: [PATCH 05/18] fix: rename --- contracts/IRateProvider.sol | 6 ++++++ contracts/pricefeeds/RateBasedScalingPriceFeed.sol | 5 +---- ...calingPriceFeed.sol => RsETHScalingPriceFeed.sol} | 12 ++++++------ .../vendor/kelp/{IrsETHOracle.sol => ILRTOracle.sol} | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 contracts/IRateProvider.sol rename contracts/pricefeeds/{rsETHScalingPriceFeed.sol => RsETHScalingPriceFeed.sol} (88%) rename contracts/vendor/kelp/{IrsETHOracle.sol => ILRTOracle.sol} (83%) diff --git a/contracts/IRateProvider.sol b/contracts/IRateProvider.sol new file mode 100644 index 000000000..1a9fbcc10 --- /dev/null +++ b/contracts/IRateProvider.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +interface IRateProvider { + function getRate() external view returns (uint256); +} diff --git a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol index 502f5b084..1532a3f85 100644 --- a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol +++ b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol @@ -3,10 +3,7 @@ pragma solidity 0.8.15; import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../IPriceFeed.sol"; - -interface IRateProvider { - function getRate() external view returns (uint256); -} +import "../IRateProvider.sol"; /** * @title Scaling price feed for rate based oracles diff --git a/contracts/pricefeeds/rsETHScalingPriceFeed.sol b/contracts/pricefeeds/RsETHScalingPriceFeed.sol similarity index 88% rename from contracts/pricefeeds/rsETHScalingPriceFeed.sol rename to contracts/pricefeeds/RsETHScalingPriceFeed.sol index 506115d47..638a81803 100644 --- a/contracts/pricefeeds/rsETHScalingPriceFeed.sol +++ b/contracts/pricefeeds/RsETHScalingPriceFeed.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import "../vendor/kelp/IrsETHOracle.sol"; +import "../vendor/kelp/ILRTOracle.sol"; import "../IPriceFeed.sol"; /** @@ -42,12 +42,12 @@ contract rsETHScalingPriceFeed is IPriceFeed { decimals = decimals_; description = description_; - uint8 kelpPriceFeedDecimals = 18; + uint8 underlyingPriceFeedDecimals = 18; // Note: Solidity does not allow setting immutables in if/else statements - shouldUpscale = kelpPriceFeedDecimals < decimals_ ? true : false; + shouldUpscale = underlyingPriceFeedDecimals < decimals_ ? true : false; rescaleFactor = (shouldUpscale - ? signed256(10 ** (decimals_ - kelpPriceFeedDecimals)) - : signed256(10 ** (kelpPriceFeedDecimals - decimals_)) + ? signed256(10 ** (decimals_ - underlyingPriceFeedDecimals)) + : signed256(10 ** (underlyingPriceFeedDecimals - decimals_)) ); } @@ -66,7 +66,7 @@ contract rsETHScalingPriceFeed is IPriceFeed { uint256 updatedAt, uint80 answeredInRound ) { - int256 price = int256(IrsETHOracle(underlyingPriceFeed).rsETHPrice()); + int256 price = int256(ILRTOracle(underlyingPriceFeed).rsETHPrice()); return (roundId, scalePrice(price), startedAt, updatedAt, answeredInRound); } diff --git a/contracts/vendor/kelp/IrsETHOracle.sol b/contracts/vendor/kelp/ILRTOracle.sol similarity index 83% rename from contracts/vendor/kelp/IrsETHOracle.sol rename to contracts/vendor/kelp/ILRTOracle.sol index 7b462be7c..9b194a259 100644 --- a/contracts/vendor/kelp/IrsETHOracle.sol +++ b/contracts/vendor/kelp/ILRTOracle.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.15; -interface IrsETHOracle { +interface ILRTOracle { function rsETHPrice() external view returns (uint256); } From 2971a1b479c01702f967f254950d9b5867284ee4 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 5 Jul 2024 15:57:12 +0300 Subject: [PATCH 06/18] fix: correct pr link and remove deploy action mention --- .../weth/migrations/1718352598_add_ezeth_as_collateral.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts index 4345ed66b..2ef21fe7a 100644 --- a/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts @@ -68,7 +68,7 @@ export default migration('1718352598_add_ezeth_as_collateral', { }, ]; - 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/867), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/9545875152/job/26307579839) 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 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))) From df7bab0051b6183572124c9609c098a5b50188c4 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 8 Jul 2024 11:05:22 +0300 Subject: [PATCH 07/18] fix: audit fixes --- .../pricefeeds/EzETHExchangeRatePriceFeed.sol | 4 +++- .../pricefeeds/ReverseMultiplicativePriceFeed.sol | 15 ++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol index 688fc1ac0..5e723831c 100644 --- a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol +++ b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol @@ -12,6 +12,7 @@ import "../IPriceFeed.sol"; contract EzETHExchangeRatePriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); + error BadDecimals(); /// @notice Version of the price feed uint public constant override version = 1; @@ -38,6 +39,7 @@ contract EzETHExchangeRatePriceFeed is IPriceFeed { **/ constructor(address ezETHRateProvider, uint8 decimals_, string memory description_) { underlyingPriceFeed = ezETHRateProvider; + if (decimals_ > 18) revert BadDecimals(); decimals = decimals_; description = description_; @@ -68,7 +70,7 @@ contract EzETHExchangeRatePriceFeed is IPriceFeed { 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 (0, scalePrice(int256(rate)), 0, 0, 0); + return (1, scalePrice(signed256(rate)), block.timestamp, block.timestamp, 1); } function signed256(uint256 n) internal pure returns (int256) { diff --git a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol index 779566f16..b79b1a2c0 100644 --- a/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol +++ b/contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol @@ -30,8 +30,11 @@ contract ReverseMultiplicativePriceFeed is IPriceFeed { /// @notice Chainlink price feed B address public immutable priceFeedB; - /// @notice Combined scale of the two underlying Chainlink price feeds - int public immutable priceFeedAScalling; + /// @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; @@ -47,7 +50,9 @@ contract ReverseMultiplicativePriceFeed is IPriceFeed { priceFeedA = priceFeedA_; priceFeedB = priceFeedB_; uint8 priceFeedADecimals = AggregatorV3Interface(priceFeedA_).decimals(); - priceFeedAScalling = signed256(10 ** (priceFeedADecimals)); + uint8 priceFeedBDecimals = AggregatorV3Interface(priceFeedB_).decimals(); + priceFeedAScale = signed256(10 ** (priceFeedADecimals)); + priceFeedBScale = signed256(10 ** (priceFeedBDecimals)); if (decimals_ > 18) revert BadDecimals(); decimals = decimals_; @@ -70,8 +75,8 @@ contract ReverseMultiplicativePriceFeed is IPriceFeed { if (priceA <= 0 || priceB <= 0) return (roundId_, 0, startedAt_, updatedAt_, answeredInRound_); - // int256 price = priceA * priceB * priceFeedScale / combinedScale; - int256 price = priceA * int256(10**(AggregatorV3Interface(priceFeedB).decimals())) * priceFeedScale / priceB / priceFeedAScalling; + // int256 price = priceA * (priceFeedBScale/priceB) * priceFeedScale / priceFeedAScale; + int256 price = priceA * priceFeedBScale * priceFeedScale / priceB / priceFeedAScale; return (roundId_, price, startedAt_, updatedAt_, answeredInRound_); } From 2553a7de1b79eb3823cc2a904329f6b49136c906 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 8 Jul 2024 12:03:30 +0300 Subject: [PATCH 08/18] fix: upper case fix --- contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol index 5e723831c..f3419a696 100644 --- a/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol +++ b/contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol @@ -15,7 +15,7 @@ contract EzETHExchangeRatePriceFeed is IPriceFeed { error BadDecimals(); /// @notice Version of the price feed - uint public constant override version = 1; + uint public constant VERSION = 1; /// @notice Description of the price feed string public description; @@ -87,4 +87,12 @@ contract EzETHExchangeRatePriceFeed is IPriceFeed { } return scaledPrice; } + + /** + * @notice Price for the latest round + * @return The version of the price feed contract + **/ + function version() external pure returns (uint256) { + return VERSION; + } } From 80f08d17269868012ba7ee234ebc7a696f08c287 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 9 Jul 2024 11:39:39 +0300 Subject: [PATCH 09/18] fix: post audit fixes --- .../pricefeeds/RateBasedScalingPriceFeed.sol | 14 ++++++++++++-- contracts/pricefeeds/RsETHScalingPriceFeed.sol | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol index 1532a3f85..34bf92096 100644 --- a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol +++ b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol @@ -13,9 +13,10 @@ import "../IRateProvider.sol"; contract RateBasedScalingPriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); + error BadDecimals(); /// @notice Version of the price feed - uint public constant override version = 1; + uint public constant VERSION = 1; /// @notice Description of the price feed string public description; @@ -39,6 +40,7 @@ contract RateBasedScalingPriceFeed is IPriceFeed { **/ constructor(address underlyingPriceFeed_, uint8 decimals_, uint8 underlyingDecimals_, string memory description_) { underlyingPriceFeed = underlyingPriceFeed_; + if (decimals_ > 18) revert BadDecimals(); decimals = decimals_; description = description_; @@ -67,7 +69,7 @@ contract RateBasedScalingPriceFeed is IPriceFeed { uint80 answeredInRound ) { uint256 rate = IRateProvider(underlyingPriceFeed).getRate(); - return (roundId, scalePrice(int256(rate)), startedAt, updatedAt, answeredInRound); + return (1, scalePrice(signed256(rate)), block.timestamp, block.timestamp, 1); } function signed256(uint256 n) internal pure returns (int256) { @@ -84,4 +86,12 @@ contract RateBasedScalingPriceFeed is IPriceFeed { } return scaledPrice; } + + /** + * @notice Contract version + * @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/contracts/pricefeeds/RsETHScalingPriceFeed.sol b/contracts/pricefeeds/RsETHScalingPriceFeed.sol index 638a81803..106b1c130 100644 --- a/contracts/pricefeeds/RsETHScalingPriceFeed.sol +++ b/contracts/pricefeeds/RsETHScalingPriceFeed.sol @@ -13,9 +13,10 @@ import "../IPriceFeed.sol"; contract rsETHScalingPriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); + error BadDecimals(); /// @notice Version of the price feed - uint public constant override version = 1; + uint public constant VERSION = 1; /// @notice Description of the price feed string public description; @@ -39,6 +40,7 @@ contract rsETHScalingPriceFeed is IPriceFeed { **/ constructor(address underlyingPriceFeed_, uint8 decimals_, string memory description_) { underlyingPriceFeed = underlyingPriceFeed_; + if (decimals_ > 18) revert BadDecimals(); decimals = decimals_; description = description_; @@ -66,8 +68,8 @@ contract rsETHScalingPriceFeed is IPriceFeed { uint256 updatedAt, uint80 answeredInRound ) { - int256 price = int256(ILRTOracle(underlyingPriceFeed).rsETHPrice()); - return (roundId, scalePrice(price), startedAt, updatedAt, answeredInRound); + int256 price = signed256(ILRTOracle(underlyingPriceFeed).rsETHPrice()); + return (1, scalePrice(price), block.timestamp, block.timestamp, 1); } function signed256(uint256 n) internal pure returns (int256) { @@ -84,4 +86,12 @@ contract rsETHScalingPriceFeed is IPriceFeed { } return scaledPrice; } + + /** + * @notice Contract version + * @return The version of the price feed contract + **/ + function version() external pure returns (uint256) { + return VERSION; + } } \ No newline at end of file From 2ef7c83c2e4bf6667f13edcb91c6bbda7e98d5ef Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 11 Jul 2024 14:25:56 +0300 Subject: [PATCH 10/18] feat: add rswETH to mainnet-weth --- .../1720695274_add_rsweth_as_collateral.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts diff --git a/deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts new file mode 100644 index 000000000..d13c339e1 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1720695274_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 RSWETH_ADDRESS = '0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0'; +const RSWETH_PRICE_FEED_ADDRESS = '0xb613CfebD0b6e95abDDe02677d6bC42394FdB857'; +let newPriceFeedAddress: string; + +export default migration('1720695274_add_rsweth_as_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _rswETHScalingPriceFeed = await deploymentManager.deploy( + 'rswETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + RSWETH_PRICE_FEED_ADDRESS, // rswETH / ETH price feed + 8 // decimals + ] + ); + return { rswETHScalingPriceFeed: _rswETHScalingPriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { rswETHScalingPriceFeed }) { + + const trace = deploymentManager.tracer(); + + const rswETH = await deploymentManager.existing( + 'rswETH', + RSWETH_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const rswEthPricefeed = await deploymentManager.existing( + 'rswETH:priceFeed', + rswETHScalingPriceFeed, + 'mainnet' + ); + + newPriceFeedAddress = rswEthPricefeed.address; + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const rswETHAssetConfig = { + asset: rswETH.address, + priceFeed: rswEthPricefeed.address, + decimals: await rswETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(1_000, 18), + }; + + const mainnetActions = [ + // 1. Add rswETH as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, rswETHAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add rswETH as collateral into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add rswETH 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-rsweth-as-collateral-to-eth-market-on-mainnet/5308/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/885) and [forum discussion](https://www.comp.xyz/t/add-rsweth-as-collateral-to-eth-market-on-mainnet/5308).\n\n\n## Proposal Actions\n\nThe first proposal action adds rswETH 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 rswETHAssetIndex = Number(await comet.numAssets()) - 1; + + const rswETH = await deploymentManager.existing( + 'rswETH', + RSWETH_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + + const rswETHAssetConfig = { + asset: rswETH.address, + priceFeed: newPriceFeedAddress, + decimals: await rswETH.decimals(), + borrowCollateralFactor: exp(0.80, 18), + liquidateCollateralFactor: exp(0.85, 18), + liquidationFactor: exp(0.9, 18), + supplyCap: exp(1_000, 18), + }; + + // 1. Compare rswETH asset config with Comet and Configurator asset info + const cometRswETHAssetInfo = await comet.getAssetInfoByAddress( + RSWETH_ADDRESS + ); + expect(rswETHAssetIndex).to.be.equal(cometRswETHAssetInfo.offset); + expect(rswETHAssetConfig.asset).to.be.equal(cometRswETHAssetInfo.asset); + expect(exp(1, rswETHAssetConfig.decimals)).to.be.equal( + cometRswETHAssetInfo.scale + ); + expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal( + cometRswETHAssetInfo.borrowCollateralFactor + ); + expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal( + cometRswETHAssetInfo.liquidateCollateralFactor + ); + expect(rswETHAssetConfig.liquidationFactor).to.be.equal( + cometRswETHAssetInfo.liquidationFactor + ); + expect(rswETHAssetConfig.supplyCap).to.be.equal( + cometRswETHAssetInfo.supplyCap + ); + const configuratorRswETHAssetConfig = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[rswETHAssetIndex]; + expect(rswETHAssetConfig.asset).to.be.equal( + configuratorRswETHAssetConfig.asset + ); + expect(rswETHAssetConfig.decimals).to.be.equal( + configuratorRswETHAssetConfig.decimals + ); + expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal( + configuratorRswETHAssetConfig.borrowCollateralFactor + ); + expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal( + configuratorRswETHAssetConfig.liquidateCollateralFactor + ); + expect(rswETHAssetConfig.liquidationFactor).to.be.equal( + configuratorRswETHAssetConfig.liquidationFactor + ); + expect(rswETHAssetConfig.supplyCap).to.be.equal( + configuratorRswETHAssetConfig.supplyCap + ); + }, +}); From 1ffd201891da56e81fe205e806bdb61de9d3e3d0 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 11 Jul 2024 17:02:12 +0300 Subject: [PATCH 11/18] fix: working migration --- .../weth/migrations/1718352598_add_ezeth_as_collateral.ts | 2 +- scenario/constraints/MigrationConstraint.ts | 4 ++-- scenario/constraints/ProposalConstraint.ts | 6 ------ 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts index 2ef21fe7a..02e75ab6c 100644 --- a/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1718352598_add_ezeth_as_collateral.ts @@ -83,7 +83,7 @@ export default migration('1718352598_add_ezeth_as_collateral', { }, async enacted(): Promise { - return false; + return true; }, async verify(deploymentManager: DeploymentManager) { diff --git a/scenario/constraints/MigrationConstraint.ts b/scenario/constraints/MigrationConstraint.ts index a0c2b16a2..41ecb1b16 100644 --- a/scenario/constraints/MigrationConstraint.ts +++ b/scenario/constraints/MigrationConstraint.ts @@ -1,7 +1,7 @@ import { StaticConstraint, Solution, World, debug } from '../../plugins/scenario'; import { CometContext, MigrationData } from '../context/CometContext'; import { Migration, loadMigrations, Actions } from '../../plugins/deployment_manager/Migration'; -import { modifiedPaths, subsets } from '../utils'; +import { modifiedPaths } from '../utils'; import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { exp } from '../../test/helpers'; @@ -22,7 +22,7 @@ export class MigrationConstraint implements StaticConstr async solve(world: World) { const label = `[${world.base.name}] {MigrationConstraint}`; const solutions: Solution[] = []; - const migrationPaths = [...subsets(await getMigrations(world))]; + const migrationPaths = [await getMigrations(world)]; for (const migrationList of migrationPaths) { if (migrationList.length == 0 && migrationPaths.length > 1) { diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 02ccc7407..83e5c967d 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 259 - if (proposal.id.eq(259)) { - console.log('Skipping proposal 259'); - continue; - } - try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); From 00034ba73dd38850ee6c499b6ba3f08a90eb3276 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 5 Aug 2024 13:11:56 +0300 Subject: [PATCH 12/18] feat: add new price feed --- .../pricefeeds/RswETHExchangePriceFeed.sol | 98 +++++++++++++++++++ contracts/vendor/swell/IRswETH.sol | 6 ++ 2 files changed, 104 insertions(+) create mode 100644 contracts/pricefeeds/RswETHExchangePriceFeed.sol create mode 100644 contracts/vendor/swell/IRswETH.sol diff --git a/contracts/pricefeeds/RswETHExchangePriceFeed.sol b/contracts/pricefeeds/RswETHExchangePriceFeed.sol new file mode 100644 index 000000000..4b48d9e3c --- /dev/null +++ b/contracts/pricefeeds/RswETHExchangePriceFeed.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/swell/IRswETH.sol"; +import "../IPriceFeed.sol"; + +/** + * @title rswETH Scaling price feed + * @notice A custom price feed that scales up or down the price received from an underlying rswETH / ETH exchange rate price feed and returns the result + * @author Compound + */ +contract RswETHExchangePriceFeed 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 rswETH 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 rswETH scaling price feed + * @param rswETH The address of the rswETH to fetch prices from + * @param decimals_ The number of decimals for the returned prices + **/ + constructor(address rswETH, uint8 decimals_, string memory description_) { + underlyingPriceFeed = rswETH; + if (decimals_ > 18) revert BadDecimals(); + decimals = decimals_; + description = description_; + + uint8 rswETHDecimals = 18; + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = rswETHDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ -rswETHDecimals)) + : signed256(10 ** (rswETHDecimals - 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 = IRswETH(underlyingPriceFeed).rswETHToETHRate(); + // 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/vendor/swell/IRswETH.sol b/contracts/vendor/swell/IRswETH.sol new file mode 100644 index 000000000..fb06deec9 --- /dev/null +++ b/contracts/vendor/swell/IRswETH.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IRswETH { + function rswETHToETHRate() external view returns (uint256); +} From b791d32690ae0f054ec27d861c3a569906f6456e Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 5 Aug 2024 13:28:51 +0300 Subject: [PATCH 13/18] feat: update price feed --- ...=> 1722853005_add_rsweth_as_collateral.ts} | 71 ++++++------------- 1 file changed, 23 insertions(+), 48 deletions(-) rename deployments/mainnet/weth/migrations/{1720695274_add_rsweth_as_collateral.ts => 1722853005_add_rsweth_as_collateral.ts} (72%) diff --git a/deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts similarity index 72% rename from deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts rename to deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts index d13c339e1..8d3729b71 100644 --- a/deployments/mainnet/weth/migrations/1720695274_add_rsweth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts @@ -4,23 +4,23 @@ import { migration } from '../../../../plugins/deployment_manager/Migration'; import { exp, proposal } from '../../../../src/deploy'; const RSWETH_ADDRESS = '0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0'; -const RSWETH_PRICE_FEED_ADDRESS = '0xb613CfebD0b6e95abDDe02677d6bC42394FdB857'; let newPriceFeedAddress: string; -export default migration('1720695274_add_rsweth_as_collateral', { +export default migration('1722853005_add_rsweth_as_collateral', { async prepare(deploymentManager: DeploymentManager) { - const _rswETHScalingPriceFeed = await deploymentManager.deploy( + const _rswETHPriceFeed = await deploymentManager.deploy( 'rswETH:priceFeed', - 'pricefeeds/ScalingPriceFeed.sol', + 'pricefeeds/RswETHExchangePriceFeed.sol', [ - RSWETH_PRICE_FEED_ADDRESS, // rswETH / ETH price feed - 8 // decimals + RSWETH_ADDRESS, // rswETH / ETH price feed + 8, // decimals + 'rswETH/ETH price feed', // description ] ); - return { rswETHScalingPriceFeed: _rswETHScalingPriceFeed.address }; + return { rswETHPriceFeed: _rswETHPriceFeed.address }; }, - async enact(deploymentManager: DeploymentManager, _, { rswETHScalingPriceFeed }) { + async enact(deploymentManager: DeploymentManager, _, { rswETHPriceFeed }) { const trace = deploymentManager.tracer(); @@ -32,7 +32,7 @@ export default migration('1720695274_add_rsweth_as_collateral', { ); const rswEthPricefeed = await deploymentManager.existing( 'rswETH:priceFeed', - rswETHScalingPriceFeed, + rswETHPriceFeed, 'mainnet' ); @@ -111,46 +111,21 @@ export default migration('1720695274_add_rsweth_as_collateral', { }; // 1. Compare rswETH asset config with Comet and Configurator asset info - const cometRswETHAssetInfo = await comet.getAssetInfoByAddress( - RSWETH_ADDRESS - ); + const cometRswETHAssetInfo = await comet.getAssetInfoByAddress(RSWETH_ADDRESS); expect(rswETHAssetIndex).to.be.equal(cometRswETHAssetInfo.offset); expect(rswETHAssetConfig.asset).to.be.equal(cometRswETHAssetInfo.asset); - expect(exp(1, rswETHAssetConfig.decimals)).to.be.equal( - cometRswETHAssetInfo.scale - ); - expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal( - cometRswETHAssetInfo.borrowCollateralFactor - ); - expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal( - cometRswETHAssetInfo.liquidateCollateralFactor - ); - expect(rswETHAssetConfig.liquidationFactor).to.be.equal( - cometRswETHAssetInfo.liquidationFactor - ); - expect(rswETHAssetConfig.supplyCap).to.be.equal( - cometRswETHAssetInfo.supplyCap - ); - const configuratorRswETHAssetConfig = ( - await configurator.getConfiguration(comet.address) - ).assetConfigs[rswETHAssetIndex]; - expect(rswETHAssetConfig.asset).to.be.equal( - configuratorRswETHAssetConfig.asset - ); - expect(rswETHAssetConfig.decimals).to.be.equal( - configuratorRswETHAssetConfig.decimals - ); - expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal( - configuratorRswETHAssetConfig.borrowCollateralFactor - ); - expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal( - configuratorRswETHAssetConfig.liquidateCollateralFactor - ); - expect(rswETHAssetConfig.liquidationFactor).to.be.equal( - configuratorRswETHAssetConfig.liquidationFactor - ); - expect(rswETHAssetConfig.supplyCap).to.be.equal( - configuratorRswETHAssetConfig.supplyCap - ); + expect(exp(1, rswETHAssetConfig.decimals)).to.be.equal(cometRswETHAssetInfo.scale); + expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal(cometRswETHAssetInfo.borrowCollateralFactor); + expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal(cometRswETHAssetInfo.liquidateCollateralFactor); + expect(rswETHAssetConfig.liquidationFactor).to.be.equal(cometRswETHAssetInfo.liquidationFactor); + expect(rswETHAssetConfig.supplyCap).to.be.equal(cometRswETHAssetInfo.supplyCap); + + const configuratorRswETHAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[rswETHAssetIndex]; + expect(rswETHAssetConfig.asset).to.be.equal(configuratorRswETHAssetConfig.asset); + expect(rswETHAssetConfig.decimals).to.be.equal(configuratorRswETHAssetConfig.decimals); + expect(rswETHAssetConfig.borrowCollateralFactor).to.be.equal(configuratorRswETHAssetConfig.borrowCollateralFactor); + expect(rswETHAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorRswETHAssetConfig.liquidateCollateralFactor); + expect(rswETHAssetConfig.liquidationFactor).to.be.equal(configuratorRswETHAssetConfig.liquidationFactor); + expect(rswETHAssetConfig.supplyCap).to.be.equal(configuratorRswETHAssetConfig.supplyCap); }, }); From 280a617345d087da6dee063828c2179a3c41099b Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 5 Aug 2024 18:24:37 +0300 Subject: [PATCH 14/18] fix: update price feed --- .../pricefeeds/RswETHExchangePriceFeed.sol | 98 ------------------- .../1722853005_add_rsweth_as_collateral.ts | 3 +- 2 files changed, 2 insertions(+), 99 deletions(-) delete mode 100644 contracts/pricefeeds/RswETHExchangePriceFeed.sol diff --git a/contracts/pricefeeds/RswETHExchangePriceFeed.sol b/contracts/pricefeeds/RswETHExchangePriceFeed.sol deleted file mode 100644 index 4b48d9e3c..000000000 --- a/contracts/pricefeeds/RswETHExchangePriceFeed.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.15; - -import "../vendor/swell/IRswETH.sol"; -import "../IPriceFeed.sol"; - -/** - * @title rswETH Scaling price feed - * @notice A custom price feed that scales up or down the price received from an underlying rswETH / ETH exchange rate price feed and returns the result - * @author Compound - */ -contract RswETHExchangePriceFeed 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 rswETH 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 rswETH scaling price feed - * @param rswETH The address of the rswETH to fetch prices from - * @param decimals_ The number of decimals for the returned prices - **/ - constructor(address rswETH, uint8 decimals_, string memory description_) { - underlyingPriceFeed = rswETH; - if (decimals_ > 18) revert BadDecimals(); - decimals = decimals_; - description = description_; - - uint8 rswETHDecimals = 18; - // Note: Solidity does not allow setting immutables in if/else statements - shouldUpscale = rswETHDecimals < decimals_ ? true : false; - rescaleFactor = (shouldUpscale - ? signed256(10 ** (decimals_ -rswETHDecimals)) - : signed256(10 ** (rswETHDecimals - 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 = IRswETH(underlyingPriceFeed).rswETHToETHRate(); - // 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/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts index 8d3729b71..54f574b0e 100644 --- a/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts @@ -10,10 +10,11 @@ export default migration('1722853005_add_rsweth_as_collateral', { async prepare(deploymentManager: DeploymentManager) { const _rswETHPriceFeed = await deploymentManager.deploy( 'rswETH:priceFeed', - 'pricefeeds/RswETHExchangePriceFeed.sol', + 'pricefeeds/RateBasedScalingPriceFeed.sol', [ RSWETH_ADDRESS, // rswETH / ETH price feed 8, // decimals + 18, // oracleDecimals 'rswETH/ETH price feed', // description ] ); From d23517a7038ffd310f687cd868d2608f52781c63 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 8 Aug 2024 16:28:06 +0300 Subject: [PATCH 15/18] fix: remove unused interface --- contracts/vendor/swell/IRswETH.sol | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 contracts/vendor/swell/IRswETH.sol diff --git a/contracts/vendor/swell/IRswETH.sol b/contracts/vendor/swell/IRswETH.sol deleted file mode 100644 index fb06deec9..000000000 --- a/contracts/vendor/swell/IRswETH.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IRswETH { - function rswETHToETHRate() external view returns (uint256); -} From e9dbb89bf90cdc8e214a0a4e925f95e1c9b33c69 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 23 Aug 2024 13:43:29 +0300 Subject: [PATCH 16/18] fix: clean up --- scenario/constraints/MigrationConstraint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scenario/constraints/MigrationConstraint.ts b/scenario/constraints/MigrationConstraint.ts index 41ecb1b16..a0c2b16a2 100644 --- a/scenario/constraints/MigrationConstraint.ts +++ b/scenario/constraints/MigrationConstraint.ts @@ -1,7 +1,7 @@ import { StaticConstraint, Solution, World, debug } from '../../plugins/scenario'; import { CometContext, MigrationData } from '../context/CometContext'; import { Migration, loadMigrations, Actions } from '../../plugins/deployment_manager/Migration'; -import { modifiedPaths } from '../utils'; +import { modifiedPaths, subsets } from '../utils'; import { DeploymentManager } from '../../plugins/deployment_manager'; import { impersonateAddress } from '../../plugins/scenario/utils'; import { exp } from '../../test/helpers'; @@ -22,7 +22,7 @@ export class MigrationConstraint implements StaticConstr async solve(world: World) { const label = `[${world.base.name}] {MigrationConstraint}`; const solutions: Solution[] = []; - const migrationPaths = [await getMigrations(world)]; + const migrationPaths = [...subsets(await getMigrations(world))]; for (const migrationList of migrationPaths) { if (migrationList.length == 0 && migrationPaths.length > 1) { From 99dd1f3336e9af4b331c10d593fdc7e9d21fa3d4 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 23 Aug 2024 16:05:09 +0300 Subject: [PATCH 17/18] fix: change description for the price feed --- .../migrations/1722853005_add_rsweth_as_collateral.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts index 54f574b0e..b810d949a 100644 --- a/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts @@ -12,10 +12,10 @@ export default migration('1722853005_add_rsweth_as_collateral', { 'rswETH:priceFeed', 'pricefeeds/RateBasedScalingPriceFeed.sol', [ - RSWETH_ADDRESS, // rswETH / ETH price feed - 8, // decimals - 18, // oracleDecimals - 'rswETH/ETH price feed', // description + RSWETH_ADDRESS, // rswETH / ETH price feed + 8, // decimals + 18, // oracleDecimals + 'rswETH/ETH exchange rate price feed', // description ] ); return { rswETHPriceFeed: _rswETHPriceFeed.address }; From 8fbea599c5a942b569e79703626b77a46a05b66f Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 26 Sep 2024 11:08:08 +0300 Subject: [PATCH 18/18] set enacted true --- .../weth/migrations/1722853005_add_rsweth_as_collateral.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts index b810d949a..099c219f3 100644 --- a/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts +++ b/deployments/mainnet/weth/migrations/1722853005_add_rsweth_as_collateral.ts @@ -86,7 +86,7 @@ export default migration('1722853005_add_rsweth_as_collateral', { }, async enacted(): Promise { - return false; + return true; }, async verify(deploymentManager: DeploymentManager) {