From 51e63e5aeaedbb85097d194d956b2f969be3b21a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 1 Jul 2024 13:38:48 +0300 Subject: [PATCH 01/29] 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 02/29] 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 cd5236c14b2a523c77c32c1fc1227c65ed5a7794 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 1 Jul 2024 16:59:46 +0300 Subject: [PATCH 03/29] feat: add migration --- ...9835184_update_rseth_and_weth_pricefeed.ts | 101 ++++++++++++++++++ deployments/mainnet/weth/relations.ts | 18 +++- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts diff --git a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts new file mode 100644 index 000000000..b113fce3b --- /dev/null +++ b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts @@ -0,0 +1,101 @@ +import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; +import { calldata, exp, proposal } from '../../../../src/deploy'; + +import { expect } from 'chai'; + +const RSETH_ADDRESS = '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7'; +const RSETH_PRICEFEED_ADDRESS = '0x349A73444b1a310BAe67ef67973022020d70020d'; +const WEETH_ADDRESS = '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee'; +const WEETH_PRICEFEED_ADDRESS = '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee'; + +export default migration('1719835184_update_rseth_and_weth_pricefeed', { + async prepare(deploymentManager: DeploymentManager) { + // const _wbtcScalingPriceFeed = await deploymentManager.deploy( + // 'WBTC:priceFeed', + // 'pricefeeds/WBTCPriceFeed.sol', + // [ + // WBTC_BTC_PRICE_FEED_ADDRESS, // WBTC / BTC price feed + // BTC_ETH_PRICE_FEED_ADDRESS, // BTC / ETH price feed + // 8, // decimals + // ] + // ); + const _rsETHPriceFeed = await deploymentManager.deploy( + 'rsETH:priceFeed', + 'pricefeeds/RsETHScalingPriceFeed.sol', + [RSETH_PRICEFEED_ADDRESS, 8, 'rsETH / ETH exchange rate'] + ); + + const _weETHPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'pricefeeds/RateBasedScalingPriceFeed.sol', + [WEETH_PRICEFEED_ADDRESS, 8, 18, 'weETH / ETH exchange rate'] + ); + return { rsETHPriceFeed: _rsETHPriceFeed.address, weETHPriceFeed: _weETHPriceFeed.address}; + }, + + async enact(deploymentManager: DeploymentManager, _, { rsETHPriceFeed, weETHPriceFeed }) { + const trace = deploymentManager.tracer(); + + const { + governor, + comet, + configurator, + cometAdmin, + } = await deploymentManager.getContracts(); + + const actions = [ + // 1. Update the price feed for rsETH + { + contract: configurator, + signature: 'updateAssetPriceFeed(address,address,address)', + args: [comet.address, RSETH_ADDRESS, rsETHPriceFeed], + }, + // 2. Update the price feed for weETH + { + contract: configurator, + signature: 'updateAssetPriceFeed(address,address,address)', + args: [comet.address, WEETH_ADDRESS, weETHPriceFeed], + }, + // 3. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry( + async () => trace((await governor.propose(...await proposal(actions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return true; + }, + + async verify(deploymentManager: DeploymentManager) { + const { + comet, + configurator + } = await deploymentManager.getContracts(); + + const rsETH = new deploymentManager.hre.ethers.Contract(RSETH_ADDRESS, [ + 'function symbol() view returns (string)', + ]); + + const weETH = new deploymentManager.hre.ethers.Contract(WEETH_ADDRESS, [ + 'function symbol() view returns (string)', + ]); + + expect(await rsETH.symbol()).to.eq('rsETH'); + expect(await weETH.symbol()).to.eq('weETH'); + const configuration = await configurator.getConfiguration(comet.address); + expect(configuration.priceFeed.assetConfigs[RSETH_ADDRESS]).to.eq(RSETH_PRICEFEED_ADDRESS); + expect(configuration.priceFeed.assetConfigs[WEETH_ADDRESS]).to.eq(WEETH_PRICEFEED_ADDRESS); + }, +}); diff --git a/deployments/mainnet/weth/relations.ts b/deployments/mainnet/weth/relations.ts index 37278965c..d4bbd40b4 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', - } + }, + 'UUPSProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + } + }, + }, + 'TransparentUpgradeableProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + } + }, + }, }; \ No newline at end of file From 0807aeab950de3ff888cd9a078839a2b3342e767 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 1 Jul 2024 17:29:51 +0300 Subject: [PATCH 04/29] fix: fix migration --- .../pricefeeds/RsETHScalingPriceFeed.sol | 2 +- ...9835184_update_rseth_and_weth_pricefeed.ts | 35 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/contracts/pricefeeds/RsETHScalingPriceFeed.sol b/contracts/pricefeeds/RsETHScalingPriceFeed.sol index 638a81803..69abbb518 100644 --- a/contracts/pricefeeds/RsETHScalingPriceFeed.sol +++ b/contracts/pricefeeds/RsETHScalingPriceFeed.sol @@ -10,7 +10,7 @@ import "../IPriceFeed.sol"; * @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 { +contract RsETHScalingPriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); diff --git a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts index b113fce3b..00fdd0f93 100644 --- a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts +++ b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts @@ -1,24 +1,19 @@ import { DeploymentManager, migration } from '../../../../plugins/deployment_manager'; -import { calldata, exp, proposal } from '../../../../src/deploy'; +import { proposal } from '../../../../src/deploy'; import { expect } from 'chai'; +import { ethers } from 'ethers'; const RSETH_ADDRESS = '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7'; const RSETH_PRICEFEED_ADDRESS = '0x349A73444b1a310BAe67ef67973022020d70020d'; const WEETH_ADDRESS = '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee'; const WEETH_PRICEFEED_ADDRESS = '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee'; +let newRsETHPriceFeed: string; +let newWeETHPriceFeed: string; + export default migration('1719835184_update_rseth_and_weth_pricefeed', { async prepare(deploymentManager: DeploymentManager) { - // const _wbtcScalingPriceFeed = await deploymentManager.deploy( - // 'WBTC:priceFeed', - // 'pricefeeds/WBTCPriceFeed.sol', - // [ - // WBTC_BTC_PRICE_FEED_ADDRESS, // WBTC / BTC price feed - // BTC_ETH_PRICE_FEED_ADDRESS, // BTC / ETH price feed - // 8, // decimals - // ] - // ); const _rsETHPriceFeed = await deploymentManager.deploy( 'rsETH:priceFeed', 'pricefeeds/RsETHScalingPriceFeed.sol', @@ -43,6 +38,8 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { cometAdmin, } = await deploymentManager.getContracts(); + newRsETHPriceFeed = rsETHPriceFeed; + newWeETHPriceFeed = weETHPriceFeed; const actions = [ // 1. Update the price feed for rsETH { @@ -74,8 +71,8 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { trace(`Created proposal ${proposalId}.`); }, - async enacted(deploymentManager: DeploymentManager): Promise { - return true; + async enacted(): Promise { + return false; }, async verify(deploymentManager: DeploymentManager) { @@ -84,18 +81,20 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { configurator } = await deploymentManager.getContracts(); - const rsETH = new deploymentManager.hre.ethers.Contract(RSETH_ADDRESS, [ + const rsETH = new ethers.Contract(RSETH_ADDRESS, [ 'function symbol() view returns (string)', - ]); + ], deploymentManager.hre.ethers.provider); - const weETH = new deploymentManager.hre.ethers.Contract(WEETH_ADDRESS, [ + const weETH = new ethers.Contract(WEETH_ADDRESS, [ 'function symbol() view returns (string)', - ]); + ], deploymentManager.hre.ethers.provider); expect(await rsETH.symbol()).to.eq('rsETH'); + const rsETHId = await configurator.getAssetIndex(comet.address, RSETH_ADDRESS); expect(await weETH.symbol()).to.eq('weETH'); + const weETHId = await configurator.getAssetIndex(comet.address, WEETH_ADDRESS); const configuration = await configurator.getConfiguration(comet.address); - expect(configuration.priceFeed.assetConfigs[RSETH_ADDRESS]).to.eq(RSETH_PRICEFEED_ADDRESS); - expect(configuration.priceFeed.assetConfigs[WEETH_ADDRESS]).to.eq(WEETH_PRICEFEED_ADDRESS); + expect(configuration.assetConfigs[rsETHId].priceFeed).to.eq(newRsETHPriceFeed); + expect(configuration.assetConfigs[weETHId].priceFeed).to.eq(newWeETHPriceFeed); }, }); From 5e4bd912930b3fe29a508f03ccb00653bba2356a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 2 Jul 2024 11:07:15 +0300 Subject: [PATCH 05/29] fix: add description --- .../migrations/1719835184_update_rseth_and_weth_pricefeed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts index 00fdd0f93..b5a5f6385 100644 --- a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts +++ b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts @@ -60,7 +60,7 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { args: [configurator.address, comet.address], }, ]; - const description = 'DESCRIPTION'; + const description = '# Update rsETH and weETH price feeds in cWETHv3 on Mainnet\n\n## Proposal summary\n\nThis proposal updates existing price feeds for rsETH and weETH collaterals in the WETH market on Mainnet from market rates to exchange rates. If exchange rate oracles are implemented, Gauntlet can recommend more capital efficient parameters as the asset remains insulated from market movements, although this exposes it to tail-end risks. The exchange rate based risk parameters could facilitate higher caps and Liquidation Factors along with more conservative Liquidation Penalties.\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/878), [forum discussion for rsETH](https://www.comp.xyz/t/add-rseth-market-on-ethereum-mainnet/5118) and [forum discussion for weETH](https://www.comp.xyz/t/add-weeth-market-on-ethereum/5179).\n\n\n## Proposal Actions\n\nThe first proposal action updates rsETH price feed from market rate to exchange rate.\n\nThe second proposal action updates weETH price feed from market rate to exchange rate.\n\nThe third action deploys and upgrades Comet to a new version.'; const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) ); From f242649112ff093f4b8355e65e5cd3b9521d5176 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 8 Jul 2024 12:45:07 +0300 Subject: [PATCH 06/29] fix: post audit fixes --- contracts/pricefeeds/RateBasedScalingPriceFeed.sol | 12 ++++++++++-- contracts/pricefeeds/RsETHScalingPriceFeed.sol | 14 +++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol index 1532a3f85..edff23727 100644 --- a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol +++ b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol @@ -15,7 +15,7 @@ contract RateBasedScalingPriceFeed is IPriceFeed { error InvalidInt256(); /// @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; @@ -67,7 +67,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 +84,12 @@ contract RateBasedScalingPriceFeed is IPriceFeed { } return scaledPrice; } + + /** + * @notice Current version of the price feed + * @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 69abbb518..9232d4ecd 100644 --- a/contracts/pricefeeds/RsETHScalingPriceFeed.sol +++ b/contracts/pricefeeds/RsETHScalingPriceFeed.sol @@ -15,7 +15,7 @@ contract RsETHScalingPriceFeed is IPriceFeed { error InvalidInt256(); /// @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; @@ -66,8 +66,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 +84,12 @@ contract RsETHScalingPriceFeed is IPriceFeed { } return scaledPrice; } + + /** + * @notice Current version of the price feed + * @return The version of the price feed contract + **/ + function version() external pure returns (uint256) { + return VERSION; + } } \ No newline at end of file From 563f2baeb89059f28e7c34cfc8e28f0c2a48fbae Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 8 Jul 2024 13:21:42 +0300 Subject: [PATCH 07/29] fix: add sanity checks --- contracts/pricefeeds/RateBasedScalingPriceFeed.sol | 2 ++ contracts/pricefeeds/RsETHScalingPriceFeed.sol | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol index edff23727..3ae4077ac 100644 --- a/contracts/pricefeeds/RateBasedScalingPriceFeed.sol +++ b/contracts/pricefeeds/RateBasedScalingPriceFeed.sol @@ -13,6 +13,7 @@ import "../IRateProvider.sol"; contract RateBasedScalingPriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); + error BadDecimals(); /// @notice Version of the price feed uint public constant VERSION = 1; @@ -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_; diff --git a/contracts/pricefeeds/RsETHScalingPriceFeed.sol b/contracts/pricefeeds/RsETHScalingPriceFeed.sol index 9232d4ecd..5a9353e91 100644 --- a/contracts/pricefeeds/RsETHScalingPriceFeed.sol +++ b/contracts/pricefeeds/RsETHScalingPriceFeed.sol @@ -13,6 +13,7 @@ import "../IPriceFeed.sol"; contract RsETHScalingPriceFeed is IPriceFeed { /** Custom errors **/ error InvalidInt256(); + error BadDecimals(); /// @notice Version of the price feed uint public constant VERSION = 1; @@ -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_; From ada29862408bca9195b39c4db312f84f761765d0 Mon Sep 17 00:00:00 2001 From: dmitriy-woof-software Date: Sat, 13 Jul 2024 18:11:51 +0200 Subject: [PATCH 08/29] feat: add force deploy, because rsETH and weETH already exist, but we need to update them to new ones --- .../1719835184_update_rseth_and_weth_pricefeed.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts index b5a5f6385..47fd4552e 100644 --- a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts +++ b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts @@ -17,15 +17,17 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { const _rsETHPriceFeed = await deploymentManager.deploy( 'rsETH:priceFeed', 'pricefeeds/RsETHScalingPriceFeed.sol', - [RSETH_PRICEFEED_ADDRESS, 8, 'rsETH / ETH exchange rate'] + [RSETH_PRICEFEED_ADDRESS, 8, 'rsETH / ETH exchange rate'], + true ); - + const _weETHPriceFeed = await deploymentManager.deploy( 'weETH:priceFeed', 'pricefeeds/RateBasedScalingPriceFeed.sol', - [WEETH_PRICEFEED_ADDRESS, 8, 18, 'weETH / ETH exchange rate'] + [WEETH_PRICEFEED_ADDRESS, 8, 18, 'weETH / ETH exchange rate'], + true ); - return { rsETHPriceFeed: _rsETHPriceFeed.address, weETHPriceFeed: _weETHPriceFeed.address}; + return { rsETHPriceFeed: _rsETHPriceFeed.address, weETHPriceFeed: _weETHPriceFeed.address }; }, async enact(deploymentManager: DeploymentManager, _, { rsETHPriceFeed, weETHPriceFeed }) { From 5974cff7568dac29ed84f7935c14dd6bb1f9df86 Mon Sep 17 00:00:00 2001 From: dmitriy-woof-software Date: Sat, 13 Jul 2024 19:22:15 +0200 Subject: [PATCH 09/29] feat: add ezETH part as it was queued, but not executed; it will allow to pass all the scenarios to not waiting till ezETH PR merging into main branch --- contracts/test/MockOracle.sol | 55 +++++++++++++++++++++++++++++++++++ scenario/utils/index.ts | 33 ++++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 contracts/test/MockOracle.sol diff --git a/contracts/test/MockOracle.sol b/contracts/test/MockOracle.sol new file mode 100644 index 000000000..06cad0ea7 --- /dev/null +++ b/contracts/test/MockOracle.sol @@ -0,0 +1,55 @@ +// 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/scenario/utils/index.ts b/scenario/utils/index.ts index 492c0ab8e..1a43cd772 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 @@ -519,7 +550,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } - case 'scroll': + case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'uint256', 'bytes', 'uint256'], From a79f38ce1a5bc6e30f477ed93dd0637c239eee71 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 8 Aug 2024 16:18:04 +0300 Subject: [PATCH 10/29] fix --- contracts/test/MockOracle.sol | 1 + scenario/utils/index.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/test/MockOracle.sol b/contracts/test/MockOracle.sol index 51cedb643..9576720c5 100644 --- a/contracts/test/MockOracle.sol +++ b/contracts/test/MockOracle.sol @@ -9,6 +9,7 @@ import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface * @author Compound */ contract MockOracle { + /// @notice Number of decimals for returned prices uint8 public immutable decimals; diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 3be93a0b6..03f59d4d8 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -550,7 +550,7 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } - case 'scroll': + case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( ['address', 'uint256', 'bytes', 'uint256'], From bcb8732a960d5404206692e60269574dfb57e572 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 8 Aug 2024 16:22:57 +0300 Subject: [PATCH 11/29] fix: manually set enacted as true --- .../migrations/1719835184_update_rseth_and_weth_pricefeed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts index 47fd4552e..015b2caec 100644 --- a/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts +++ b/deployments/mainnet/weth/migrations/1719835184_update_rseth_and_weth_pricefeed.ts @@ -74,7 +74,7 @@ export default migration('1719835184_update_rseth_and_weth_pricefeed', { }, async enacted(): Promise { - return false; + return true; }, async verify(deploymentManager: DeploymentManager) { From abdc3cd2319a296c584ddac323691931f030b996 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 19 Aug 2024 17:51:04 +0300 Subject: [PATCH 12/29] feat: add migration and necessary changes --- .github/workflows/run-scenarios.yaml | 2 +- .../MainnetBulkerWithWstETHSupport.sol | 99 ++++++++ deployments/mainnet/wsteth/configuration.json | 54 ++++ deployments/mainnet/wsteth/deploy.ts | 91 +++++++ .../1723732097_configurate_and_ens.ts | 240 ++++++++++++++++++ deployments/mainnet/wsteth/relations.ts | 34 +++ deployments/mainnet/wsteth/roots.json | 1 + hardhat.config.ts | 9 +- .../deployment_manager/DeploymentManager.ts | 3 +- scenario/BulkerScenario.ts | 77 ++++-- scenario/LiquidationBotScenario.ts | 6 +- scenario/constraints/ProposalConstraint.ts | 6 +- scenario/utils/index.ts | 2 +- .../liquidateUnderwaterBorrowers.ts | 18 +- 14 files changed, 606 insertions(+), 36 deletions(-) create mode 100644 contracts/bulkers/MainnetBulkerWithWstETHSupport.sol create mode 100644 deployments/mainnet/wsteth/configuration.json create mode 100644 deployments/mainnet/wsteth/deploy.ts create mode 100644 deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts create mode 100644 deployments/mainnet/wsteth/relations.ts create mode 100644 deployments/mainnet/wsteth/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 0fecdd98c..12853640f 100644 --- a/.github/workflows/run-scenarios.yaml +++ b/.github/workflows/run-scenarios.yaml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - bases: [ development, mainnet, mainnet-weth, mainnet-usdt, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol new file mode 100644 index 000000000..efb78039f --- /dev/null +++ b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "./BaseBulker.sol"; +import "../IWstETH.sol"; + +/** + * @title Compound's Bulker contract for Ethereum mainnet + * @notice Executes multiple Comet-related actions in a single transaction + * @author Compound + */ +contract MainnetBulkerWithWstETHSupport is BaseBulker { + /** General configuration constants **/ + + /// @notice The address of Lido staked ETH + address public immutable steth; + + /// @notice The address of Lido wrapped staked ETH + address public immutable wsteth; + + /** Actions **/ + + /// @notice The action for supplying staked ETH to Comet + bytes32 public constant ACTION_SUPPLY_STETH = "ACTION_SUPPLY_STETH"; + + /// @notice The action for withdrawing staked ETH from Comet + bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH"; + + /** + * @notice Construct a new MainnetBulker instance + * @param admin_ The admin of the Bulker contract + * @param weth_ The address of wrapped ETH + * @param wsteth_ The address of Lido wrapped staked ETH + **/ + constructor( + address admin_, + address payable weth_, + address wsteth_ + ) BaseBulker(admin_, weth_) { + wsteth = wsteth_; + steth = IWstETH(wsteth_).stETH(); + } + + /** + * @notice Handles actions specific to the Ethereum mainnet version of Bulker, specifically supplying and withdrawing stETH + */ + function handleAction(bytes32 action, bytes calldata data) override internal { + if (action == ACTION_SUPPLY_STETH) { + (address comet, address to, uint stETHAmount) = abi.decode(data, (address, address, uint)); + supplyStEthTo(comet, to, stETHAmount); + } else if (action == ACTION_WITHDRAW_STETH) { + (address comet, address to, uint wstETHAmount) = abi.decode(data, (address, address, uint)); + withdrawStEthTo(comet, to, wstETHAmount); + } else { + revert UnhandledAction(); + } + } + + /** + * @notice Wraps stETH to wstETH and supplies to a user in Comet + * @dev Note: This contract must have permission to manage msg.sender's Comet account + * @dev Note: wstETH base asset is NOT supported + */ + function supplyStEthTo(address comet, address to, uint stETHAmount) internal { + doTransferIn(steth, msg.sender, stETHAmount); + ERC20(steth).approve(wsteth, stETHAmount); + uint wstETHAmount = IWstETH(wsteth).wrap(stETHAmount); + ERC20(wsteth).approve(comet, wstETHAmount); + CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount); + } + + /** + * @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user + * @dev Note: This contract must have permission to manage msg.sender's Comet account + * @dev Note: wstETH base asset is NOT supported + * @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet + */ + function withdrawStEthTo(address comet, address to, uint stETHAmount) internal { + uint wstETHAmount = stETHAmount == type(uint256).max + ? CometInterface(comet).collateralBalanceOf(msg.sender, wsteth) + : IWstETH(wsteth).getWstETHByStETH(stETHAmount); + CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount); + uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount); + doTransferOut(steth, to, unwrappedStETHAmount); + } + + uint256 public lastValue = 1; + + /** + * @notice Submits received ether to get stETH and wraps it to wstETH, received wstETH is transferred to Comet + */ + function deposit(address comet) external payable { + (bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0)); + require(success, 'transfer failed'); + uint wstETHAmount = ERC20(wsteth).balanceOf(address(this)); + lastValue=wstETHAmount; + doTransferOut(wsteth, comet, wstETHAmount); + } +} \ No newline at end of file diff --git a/deployments/mainnet/wsteth/configuration.json b/deployments/mainnet/wsteth/configuration.json new file mode 100644 index 000000000..f816becd4 --- /dev/null +++ b/deployments/mainnet/wsteth/configuration.json @@ -0,0 +1,54 @@ +{ + "name": "Compound wstETH", + "symbol": "cWstETHv3", + "baseToken": "wstETH", + "baseTokenAddress": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "borrowMin": "0.1e18", + "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", + "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", + "storeFrontPriceFactor": 0.7, + "targetReserves": "5000e18", + "rates": { + "supplyBase": 0, + "supplySlopeLow": 0.012, + "supplyKink": 0.85, + "supplySlopeHigh": 1, + "borrowBase": 0.01, + "borrowSlopeLow": 0.014, + "borrowKink": 0.85, + "borrowSlopeHigh": 1.15 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "0e15", + "baseBorrowSpeed": "0e15", + "baseMinForRewards": "1000e18" + }, + "rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", + "assets": { + "rsETH": { + "address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.91, + "liquidationFactor": 0.96, + "supplyCap": "0e18" + }, + "ezETH": { + "address": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.91, + "liquidationFactor": 0.94, + "supplyCap": "0e18" + }, + "weETH": { + "address": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", + "decimals": "18", + "borrowCF": 0.90, + "liquidateCF": 0.93, + "liquidationFactor": 0.96, + "supplyCap": "0e18" + } + } +} diff --git a/deployments/mainnet/wsteth/deploy.ts b/deployments/mainnet/wsteth/deploy.ts new file mode 100644 index 000000000..a7c041449 --- /dev/null +++ b/deployments/mainnet/wsteth/deploy.ts @@ -0,0 +1,91 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet, exp } from '../../../src/deploy'; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'); + const weth = await deploymentManager.existing('weth', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'); + const rsETHToETHPriceFeed = await deploymentManager.fromDep('rsETH:priceFeed', 'mainnet', 'weth'); + const wstETHToETHPriceFeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'weth'); + const ezETHToETHPriceFeed = await deploymentManager.fromDep('ezETH:priceFeed', 'mainnet', 'weth'); + const weETHToETHPriceFeed = await deploymentManager.fromDep('weETH:priceFeed', 'mainnet', 'weth'); + + // Deploy constant price feed for wstETH + const wstETHConstantPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/ConstantPriceFeed.sol', + [ + 8, // decimals + exp(1, 8) // constantPrice + ], + true + ); + + // Deploy reverse multiplicative price feed for rsETH + const rsETHScalingPriceFeed = await deploymentManager.deploy( + 'rsETH:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + rsETHToETHPriceFeed.address, // rsETH / ETH price feed + wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) + 8, // decimals + 'rsETH / wstETH price feed' // description + ], + true + ); + + // Deploy reverse multiplicative price feed for ezETH + const ezETHScalingPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + ezETHToETHPriceFeed.address, // ezETH / ETH price feed + wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) + 8, // decimals + 'ezETH / wstETH price feed' // description + ], + true + ); + + // Deploy reverse multiplicative price feed for weETH + const weETHScalingPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + weETHToETHPriceFeed.address, // weETH / ETH price feed + wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) + 8, // decimals + 'weETH / wstETH price feed' // description + ], + true + ); + + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'mainnet', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'mainnet', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'mainnet', 'usdc'); + + // Deploy all Comet-related contracts + const deployed = await deployComet(deploymentManager, deploySpec); + const { comet } = deployed; + + // Deploy Bulker + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/MainnetBulkerWithWstETHSupport.sol', + [ + await comet.governor(), // admin_ + weth.address, // weth_ + wstETH.address // wsteth_ + ], + true + ); + console.log('Bulker deployed at:', bulker.address); + + const bulkerNow = await deploymentManager.contract('bulker'); + console.log('Bulker now at:', bulkerNow? bulkerNow.address: 'N/A'); + + return { ...deployed, bulker }; +} diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts new file mode 100644 index 000000000..76ca9762e --- /dev/null +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -0,0 +1,240 @@ +import { ethers } from 'ethers'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; +import { expect } from 'chai'; +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSRegistryAddress = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const wstETHAmount = ethers.BigNumber.from(exp(100, 18)); + +export default migration('1723732097_configurate_and_ens', { + async prepare() { + return {}; + }, + + async enact(deploymentManager: DeploymentManager) { + const trace = deploymentManager.tracer(); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt', true); + const ethToWstETHPriceFeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'weth', true); + const price = (await ethToWstETHPriceFeed.latestRoundData())[1]; + const etherToWstETH = ethers.BigNumber.from(wstETHAmount).mul(price).div(exp(1,8)).toBigInt(); + + const { + comet, + cometAdmin, + configurator, + rewards, + COMP, + governor, + bulker, + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const currentChainId = 1; + const newMarketObject = { baseSymbol: 'wstETH', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + if (officialMarketsJSON[currentChainId]) { + officialMarketsJSON[currentChainId].push(newMarketObject); + } else { + officialMarketsJSON[currentChainId] = [newMarketObject]; + } + + const actions = [ + // 1. Set the Comet factory in configuration + { + contract: configurator, + signature: 'setFactory(address,address)', + args: [comet.address, cometFactory.address], + }, + // 2. Set the Comet configuration + { + contract: configurator, + signature: 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', + args: [comet.address, configuration], + }, + // 3. Deploy Comet and upgrade it to the new implementation + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + // 4. Set the reward configuration + { + contract: rewards, + signature: 'setRewardConfig(address,address)', + args: [comet.address, COMP.address], + }, + // 5. Deposit ether to get wstETH and transfer it to the Comet + { + target: bulker.address, + value: etherToWstETH, + signature: 'deposit(address)', + calldata: ethers.utils.defaultAbiCoder.encode( + ['address'], + [comet.address] + ), + }, + // 6. Update the list of official markets + { + target: ENSResolverAddress, + signature: 'setText(bytes32,string,string)', + calldata: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'string', 'string'], + [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] + ) + } + ]; + + const description = 'DESCRIPTION'; + const txn = await deploymentManager.retry( + async () => trace((await governor.propose(...await proposal(actions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { + comet, + rewards, + timelock, + COMP, + // rsETH, + // ezETH, + // weETH + } = await deploymentManager.getContracts(); + + // 1. & 2. & 3. + // const rsETHInfo = await comet.getAssetInfoByAddress(rsETH.address); + // const ezETHInfo = await comet.getAssetInfoByAddress(ezETH.address); + // const weETHInfo = await comet.getAssetInfoByAddress(weETH.address); + + // expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); + // expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); + // expect(weETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); + + // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(70 / 86400, 15, 18)); + // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(50 / 86400, 15, 18)); + + // 4 + const config = await rewards.rewardConfig(comet.address); + expect(config.token).to.be.equal(COMP.address); + expect(config.rescaleFactor).to.be.equal(exp(1, 12)); + expect(config.shouldUpscale).to.be.equal(true); + + expect((await comet.pauseGuardian()).toLowerCase()).to.be.eq('0xbbf3f1421d886e9b2c5d716b5192ac998af2012c'); + + // 5. & 6. + // expect reserves to be close to wstETHAmount +- 0.05 + expect(await comet.getReserves()).to.be.closeTo(wstETHAmount, exp(5, 16)); + + // 7. + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const ENSRegistry = await deploymentManager.existing('ENSRegistry', ENSRegistryAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); + expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); + expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); + expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: comet.address, + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + } +}); \ No newline at end of file diff --git a/deployments/mainnet/wsteth/relations.ts b/deployments/mainnet/wsteth/relations.ts new file mode 100644 index 000000000..4211ead18 --- /dev/null +++ b/deployments/mainnet/wsteth/relations.ts @@ -0,0 +1,34 @@ +import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + 'wstETH': { + artifact: 'contracts/bulkers/IWstETH.sol', + relations: { + stETH: { + field: async (wstETH) => wstETH.stETH() + } + } + }, + 'AppProxyUpgradeable': { + artifact: 'contracts/ERC20.sol:ERC20', + }, + 'UUPSProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + 'TransparentUpgradeableProxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; + diff --git a/deployments/mainnet/wsteth/roots.json b/deployments/mainnet/wsteth/roots.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/deployments/mainnet/wsteth/roots.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index c532b5ef2..d15407876 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -26,6 +26,7 @@ import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import mainnetUsdtRelationConfigMap from './deployments/mainnet/usdt/relations'; +import mainnetWstETHRelationConfigMap from './deployments/mainnet/wsteth/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import polygonUsdtRelationConfigMap from './deployments/polygon/usdt/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; @@ -353,7 +354,8 @@ const config: HardhatUserConfig = { mainnet: { usdc: mainnetRelationConfigMap, weth: mainnetWethRelationConfigMap, - usdt: mainnetUsdtRelationConfigMap + usdt: mainnetUsdtRelationConfigMap, + wsteth: mainnetWstETHRelationConfigMap }, polygon: { usdc: polygonRelationConfigMap, @@ -413,6 +415,11 @@ const config: HardhatUserConfig = { network: 'mainnet', deployment: 'usdt' }, + { + name: 'mainnet-wsteth', + network: 'mainnet', + deployment: 'wsteth' + }, { name: 'development', network: 'hardhat', diff --git a/plugins/deployment_manager/DeploymentManager.ts b/plugins/deployment_manager/DeploymentManager.ts index f15071978..4c53658b0 100644 --- a/plugins/deployment_manager/DeploymentManager.ts +++ b/plugins/deployment_manager/DeploymentManager.ts @@ -222,10 +222,11 @@ export class DeploymentManager { alias: Alias, network: string, deployment: string, + force?: boolean, otherAlias = alias ): Promise { const maybeExisting = await this.contract(alias); - if (!maybeExisting) { + if (!maybeExisting || force) { const trace = this.tracer(); const spider = await this.spiderOther(network, deployment); const contract = spider.contracts.get(otherAlias) as C; diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 1f4ed579f..7155fd782 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -1,9 +1,22 @@ -import { scenario } from './context/CometContext'; +import { CometContext, scenario } from './context/CometContext'; import { constants, utils } from 'ethers'; import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; +async function hasWETHAsCollateral(ctx: CometContext): Promise { + const comet = await ctx.getComet(); + const bulker = await ctx.getBulker(); + const wrappedNativeToken = await bulker.wrappedNativeToken(); + const numAssets = await comet.numAssets(); + for (let i = 0; i < numAssets; i++) { + const { asset } = await comet.getAssetInfo(i); + if (asset.toLowerCase() === wrappedNativeToken.toLowerCase()) { + return true; + } + } +} + // XXX properly handle cases where asset0 is WETH scenario( 'Comet#bulker > (non-WETH base) all non-reward actions in one txn', @@ -59,17 +72,21 @@ scenario( const calldata = [ supplyAssetCalldata, withdrawAssetCalldata, - transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata + transferAssetCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), - await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), + await bulker.ACTION_TRANSFER_ASSET() ]; + + if(await hasWETHAsCollateral(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -77,7 +94,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasWETHAsCollateral(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -135,17 +152,21 @@ scenario( const calldata = [ supplyAssetCalldata, withdrawAssetCalldata, - transferAssetCalldata, - supplyNativeTokenCalldata, - withdrawNativeTokenCalldata + transferAssetCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), - await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), + await bulker.ACTION_TRANSFER_ASSET() ]; + + if(await hasWETHAsCollateral(context)){ + calldata.push(supplyNativeTokenCalldata); + calldata.push(withdrawNativeTokenCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -155,7 +176,7 @@ scenario( expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); - expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase - (toSupplyEth - toWithdrawEth)); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase - (await hasWETHAsCollateral(context)?(toSupplyEth - toWithdrawEth):0n)); return txn; // return txn to measure gas } @@ -232,18 +253,22 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata, claimRewardCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), await bulker.ACTION_CLAIM_REWARD(), ]; + + if(await hasWETHAsCollateral(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -252,7 +277,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasWETHAsCollateral(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -326,18 +351,22 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyNativeTokenCalldata, - withdrawNativeTokenCalldata, claimRewardCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), await bulker.ACTION_CLAIM_REWARD(), ]; + + if(await hasWETHAsCollateral(context)){ + calldata.push(supplyNativeTokenCalldata); + calldata.push(withdrawNativeTokenCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index bf21963b7..8c504b8e1 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -537,7 +537,7 @@ for (let i = 0; i < MAX_ASSETS; i++) { scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when value is beneath liquidationThreshold`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), tokenBalances: { $comet: { $base: 100000 }, }, @@ -648,7 +648,7 @@ scenario( scenario( `LiquidationBot > absorbs, but does not attempt to purchase collateral when maxAmountToPurchase=0`, { - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), tokenBalances: { $comet: { $base: 100000 }, }, @@ -778,7 +778,7 @@ scenario( upgrade: { targetReserves: exp(20_000, 18) }, - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}]), tokenBalances: async (ctx) => ( { $comet: { diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index f1b9fa204..141df1d66 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,9 +62,9 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 281 - if (proposal.id.eq(281)) { - console.log('Skipping proposal 281'); + // temporary hack to skip proposal 307 + if (proposal.id.eq(307)) { + console.log('Skipping proposal 307'); continue; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 03f59d4d8..3fd02d954 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -328,7 +328,7 @@ export async function fetchLogs( } async function redeployRenzoOracle(dm: DeploymentManager){ - if(dm.network === 'mainnet' && dm.deployment === 'weth') { + if(dm.network === 'mainnet') { // renzo admin 0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A const renzoOracle = new Contract( '0x5a12796f7e7EBbbc8a402667d266d2e65A814042', diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index b2d5d4e0b..fec951619 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -39,6 +39,8 @@ const addresses = { WETH9: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', CB_ETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', WST_ETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + // WE_ETH: '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee', + // RS_ETH: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, goerli: { @@ -67,7 +69,8 @@ const addresses = { const liquidationThresholds = { mainnet: { 'usdc': 10e6, - 'weth': 1e18 + 'weth': 1e18, + // 'wsteth': 1e18, }, goerli: { 'usdc': 10e6 @@ -94,7 +97,11 @@ export const flashLoanPools = { usdt: { tokenAddress: addresses.mainnet.DAI, poolFee: 100 - } + }, + // wsteth: { + // tokenAddress: addresses.mainnet.WE_ETH, + // poolFee: 500 + // }, }, goerli: { usdc: { @@ -196,6 +203,13 @@ export function getPoolConfig(tokenAddress: string) { balancerPoolId: '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' } }, + // [addresses.mainnet.RS_ETH.toLowerCase()]: { + // ...defaultPoolConfig, + // ...{ + // exchange: Exchange.Balancer, + // balancerPoolId: '0x58aadfb1afac0ad7fca1148f3cde6aedf5236b6d00000000000000000000067f' + // } + // }, [addresses.polygon.WMATIC.toLowerCase()]: { ...defaultPoolConfig, ...{ From 65571177d4ac2665910d4a0bc1bc5b7619601f87 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 19 Aug 2024 18:09:57 +0300 Subject: [PATCH 13/29] feat: add description --- .../mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index 76ca9762e..937b05622 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -94,7 +94,7 @@ export default migration('1723732097_configurate_and_ens', { } ]; - const description = 'DESCRIPTION'; + const description = '# Initialize cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-dai-v3-comet-on-mainnet-recommendation/5380/1).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](<>) and [forum discussion](https://www.comp.xyz/t/gauntlet-dai-v3-comet-on-mainnet-recommendation/5380).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Ethereum Mainnet cwstETHv3 market.'; const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) ); From 4b137fb3fcffd29c558de7c1fd34fda47f3b2dac Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 23 Aug 2024 14:08:12 +0300 Subject: [PATCH 14/29] fix: change bulker scenario --- scenario/BulkerScenario.ts | 209 +++++++++++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 18 deletions(-) diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 7155fd782..83748f7f8 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -20,6 +20,87 @@ async function hasWETHAsCollateral(ctx: CometContext): Promise { // XXX properly handle cases where asset0 is WETH scenario( 'Comet#bulker > (non-WETH base) all non-reward actions in one txn', + { + filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), + supplyCaps: { + $asset0: 5000, + $asset1: 5000, + }, + tokenBalances: { + albert: { $base: '== 0', $asset0: 5000, $asset1: 5000 }, + $comet: { $base: 5000 }, + }, + }, + async ({ comet, actors, bulker }, context) => { + const { albert, betty } = actors; + const wrappedNativeToken = await bulker.wrappedNativeToken(); + const baseAssetAddress = await comet.baseToken(); + const baseAsset = context.getAssetByAddress(baseAssetAddress); + const baseScale = (await comet.baseScale()).toBigInt(); + // if asset 0 is native token we took asset 1 + const { asset: asset0, scale: scale0 } = await comet.getAssetInfo(0); + const { asset: asset1, scale: scale1 } = await comet.getAssetInfo(1); + const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; + const collateralAsset = context.getAssetByAddress(collateralAssetAddress); + const collateralScale = scaleBN.toBigInt(); + const toSupplyCollateral = 5000n * collateralScale; + const toBorrowBase = 1000n * baseScale; + const toTransferBase = 500n * baseScale; + const toSupplyEth = exp(0.01, 18); + const toWithdrawEth = exp(0.005, 18); + + // Approvals + await collateralAsset.approve(albert, comet.address); + await albert.allow(bulker.address, true); + + // Initial expectations + expect(await collateralAsset.balanceOf(albert.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(0n); + expect(await comet.balanceOf(albert.address)).to.be.equal(0n); + + // Albert's actions: + // 1. Supplies 3000 units of collateral + // 2. Borrows 1000 base + // 3. Transfers 500 base to Betty + // 4. Supplies 0.01 ETH + // 5. Withdraws 0.005 ETH + const supplyAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, collateralAsset.address, toSupplyCollateral]); + const withdrawAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, baseAsset.address, toBorrowBase]); + const transferAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, betty.address, baseAsset.address, toTransferBase]); + const supplyEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toSupplyEth]); + const withdrawEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toWithdrawEth]); + const calldata = [ + supplyAssetCalldata, + withdrawAssetCalldata, + transferAssetCalldata, + supplyEthCalldata, + withdrawEthCalldata + ]; + const actions = [ + await bulker.ACTION_SUPPLY_ASSET(), + await bulker.ACTION_WITHDRAW_ASSET(), + await bulker.ACTION_TRANSFER_ASSET(), + await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), + await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), + ]; + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); + + // Final expectations + const baseIndexScale = (await comet.baseIndexScale()).toBigInt(); + const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); + const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); + expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); + expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); + expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); + + return txn; // return txn to measure gas + } +); + +scenario( + 'Comet#bulker > (wstETH base) all non-reward actions in one txn', { filter: async (ctx) => await isBulkerSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'mumbai' }, { network: 'linea-goerli' }]), supplyCaps: { @@ -152,21 +233,115 @@ scenario( const calldata = [ supplyAssetCalldata, withdrawAssetCalldata, - transferAssetCalldata + transferAssetCalldata, + supplyNativeTokenCalldata, + withdrawNativeTokenCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), - await bulker.ACTION_TRANSFER_ASSET() + await bulker.ACTION_TRANSFER_ASSET(), + await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), + await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), ]; + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); - if(await hasWETHAsCollateral(context)){ - calldata.push(supplyNativeTokenCalldata); - calldata.push(withdrawNativeTokenCalldata); - actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); - actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + // Final expectations + const baseIndexScale = (await comet.baseIndexScale()).toBigInt(); + const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); + const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); + expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); + expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase - (toSupplyEth - toWithdrawEth)); + + return txn; // return txn to measure gas + } +); + +// XXX properly handle cases where asset0 is WETH +scenario( + 'Comet#bulker > (non-WETH base) all actions in one txn', + { + filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { deployment: 'wsteth' }, { network: 'linea-goerli' }]), + supplyCaps: { + $asset0: 5000, + $asset1: 5000, + }, + tokenBalances: { + albert: { $base: '== 1000000', $asset0: 5000, $asset1: 5000 }, + $comet: { $base: 5000 }, } + }, + async ({ comet, actors, rewards, bulker }, context, world) => { + const { albert, betty } = actors; + const wrappedNativeToken = await bulker.wrappedNativeToken(); + const baseAssetAddress = await comet.baseToken(); + const baseAsset = context.getAssetByAddress(baseAssetAddress); + const baseScale = (await comet.baseScale()).toBigInt(); + // if asset 0 is native token we took asset 1 + const { asset: asset0, scale: scale0 } = await comet.getAssetInfo(0); + const { asset: asset1, scale: scale1 } = await comet.getAssetInfo(1); + const { asset: collateralAssetAddress, scale: scaleBN } = asset0 === wrappedNativeToken ? { asset: asset1, scale: scale1 } : { asset: asset0, scale: scale0 }; + const collateralAsset = context.getAssetByAddress(collateralAssetAddress); + const collateralScale = scaleBN.toBigInt(); + const [rewardTokenAddress] = await rewards.rewardConfig(comet.address); + const toSupplyBase = 1_000_000n * baseScale; + const toSupplyCollateral = 5000n * collateralScale; + const toBorrowBase = 1000n * baseScale; + const toTransferBase = 500n * baseScale; + const toSupplyEth = exp(0.01, 18); + const toWithdrawEth = exp(0.005, 18); + // Approvals + await baseAsset.approve(albert, comet.address); + await collateralAsset.approve(albert, comet.address); + await albert.allow(bulker.address, true); + + // Accrue some rewards to Albert, then transfer away Albert's supplied base + await albert.safeSupplyAsset({ asset: baseAssetAddress, amount: toSupplyBase }); + await world.increaseTime(86400); // fast forward a day + await albert.transferAsset({ dst: constants.AddressZero, asset: baseAssetAddress, amount: constants.MaxUint256 }); // transfer all base away + + // Initial expectations + expect(await collateralAsset.balanceOf(albert.address)).to.be.equal(toSupplyCollateral); + expect(await baseAsset.balanceOf(albert.address)).to.be.equal(0n); + expect(await comet.balanceOf(albert.address)).to.be.equal(0n); + const startingRewardBalance = await albert.getErc20Balance(rewardTokenAddress); + const rewardOwed = ((await rewards.callStatic.getRewardOwed(comet.address, albert.address)).owed).toBigInt(); + const expectedFinalRewardBalance = collateralAssetAddress === rewardTokenAddress ? + startingRewardBalance + rewardOwed - toSupplyCollateral : + startingRewardBalance + rewardOwed; + + // Albert's actions: + // 1. Supplies 3000 units of collateral + // 2. Borrows 1000 base + // 3. Transfers 500 base to Betty + // 4. Supplies 0.01 ETH + // 5. Withdraws 0.005 ETH + // 6. Claim rewards + const supplyAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, collateralAsset.address, toSupplyCollateral]); + const withdrawAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, albert.address, baseAsset.address, toBorrowBase]); + const transferAssetCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'uint'], [comet.address, betty.address, baseAsset.address, toTransferBase]); + const supplyEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toSupplyEth]); + const withdrawEthCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'uint'], [comet.address, albert.address, toWithdrawEth]); + const claimRewardCalldata = utils.defaultAbiCoder.encode(['address', 'address', 'address', 'bool'], [comet.address, rewards.address, albert.address, true]); + const calldata = [ + supplyAssetCalldata, + withdrawAssetCalldata, + transferAssetCalldata, + supplyEthCalldata, + withdrawEthCalldata, + claimRewardCalldata + ]; + const actions = [ + await bulker.ACTION_SUPPLY_ASSET(), + await bulker.ACTION_WITHDRAW_ASSET(), + await bulker.ACTION_TRANSFER_ASSET(), + await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), + await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), + await bulker.ACTION_CLAIM_REWARD(), + ]; const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -175,16 +350,18 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); + expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); - expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase - (await hasWETHAsCollateral(context)?(toSupplyEth - toWithdrawEth):0n)); + expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); return txn; // return txn to measure gas } ); -// XXX properly handle cases where asset0 is WETH + scenario( - 'Comet#bulker > (non-WETH base) all actions in one txn', + 'Comet#bulker > (wstETH base) all actions in one txn', { filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{ deployment: 'weth' }, { network: 'linea-goerli' }]), supplyCaps: { @@ -351,22 +528,18 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, + supplyNativeTokenCalldata, + withdrawNativeTokenCalldata, claimRewardCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), + await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), + await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), await bulker.ACTION_CLAIM_REWARD(), ]; - - if(await hasWETHAsCollateral(context)){ - calldata.push(supplyNativeTokenCalldata); - calldata.push(withdrawNativeTokenCalldata); - actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); - actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); - } - const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations From 802204cb08b1a9502a99ef550a43f2b724928d0a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 23 Aug 2024 14:16:20 +0300 Subject: [PATCH 15/29] fix: remove commented code --- .../liquidateUnderwaterBorrowers.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index fec951619..6b7efa5b4 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -39,8 +39,6 @@ const addresses = { WETH9: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', CB_ETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', WST_ETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', - // WE_ETH: '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee', - // RS_ETH: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, goerli: { @@ -70,7 +68,6 @@ const liquidationThresholds = { mainnet: { 'usdc': 10e6, 'weth': 1e18, - // 'wsteth': 1e18, }, goerli: { 'usdc': 10e6 @@ -98,10 +95,6 @@ export const flashLoanPools = { tokenAddress: addresses.mainnet.DAI, poolFee: 100 }, - // wsteth: { - // tokenAddress: addresses.mainnet.WE_ETH, - // poolFee: 500 - // }, }, goerli: { usdc: { @@ -203,13 +196,6 @@ export function getPoolConfig(tokenAddress: string) { balancerPoolId: '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080' } }, - // [addresses.mainnet.RS_ETH.toLowerCase()]: { - // ...defaultPoolConfig, - // ...{ - // exchange: Exchange.Balancer, - // balancerPoolId: '0x58aadfb1afac0ad7fca1148f3cde6aedf5236b6d00000000000000000000067f' - // } - // }, [addresses.polygon.WMATIC.toLowerCase()]: { ...defaultPoolConfig, ...{ From c818da9801e8d8dfb186e1a93da4a09a8ceba191 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Fri, 23 Aug 2024 14:18:27 +0300 Subject: [PATCH 16/29] fix: clean up --- scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index 6b7efa5b4..b2d5d4e0b 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -67,7 +67,7 @@ const addresses = { const liquidationThresholds = { mainnet: { 'usdc': 10e6, - 'weth': 1e18, + 'weth': 1e18 }, goerli: { 'usdc': 10e6 @@ -94,7 +94,7 @@ export const flashLoanPools = { usdt: { tokenAddress: addresses.mainnet.DAI, poolFee: 100 - }, + } }, goerli: { usdc: { From cf9cc716e1fd759de5736aa7809723f370d02693 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 27 Aug 2024 11:34:03 +0300 Subject: [PATCH 17/29] fix: remove weETH from the configuration --- deployments/mainnet/wsteth/configuration.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/deployments/mainnet/wsteth/configuration.json b/deployments/mainnet/wsteth/configuration.json index f816becd4..f854a839a 100644 --- a/deployments/mainnet/wsteth/configuration.json +++ b/deployments/mainnet/wsteth/configuration.json @@ -41,14 +41,6 @@ "liquidateCF": 0.91, "liquidationFactor": 0.94, "supplyCap": "0e18" - }, - "weETH": { - "address": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", - "decimals": "18", - "borrowCF": 0.90, - "liquidateCF": 0.93, - "liquidationFactor": 0.96, - "supplyCap": "0e18" } } } From 04d2623d24fbca07e12d588c8ce5a2c9304c5fd0 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 27 Aug 2024 16:55:34 +0300 Subject: [PATCH 18/29] feat: add new workflow and update configs --- .github/workflows/enact-migration.yaml | 17 +++++++++++++++++ deployments/mainnet/wsteth/configuration.json | 2 +- deployments/mainnet/wsteth/deploy.ts | 14 -------------- .../1723732097_configurate_and_ens.ts | 5 +---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index f3a5e4524..723f0dbf2 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -40,6 +40,11 @@ on: description: Impersonate Account required: false default: '' + with_deploy: + type: boolean + description: Deploy Market + required: false + default: false jobs: enact-migration: name: Enact Migration @@ -108,6 +113,18 @@ jobs: path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/ if: github.event.inputs.run_id != '' + - name: Run Deploy Market and Enact Migration (impersonate) + run: | + yarn hardhat deploy --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} + yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} + env: + DEBUG: true + ETH_PK: "${{ inputs.eth_pk }}" + NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} + GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} + GOV_NETWORK: ${{ env.GOV_NETWORK }} + REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} + if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'true' - name: Run Enact Migration run: | yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} diff --git a/deployments/mainnet/wsteth/configuration.json b/deployments/mainnet/wsteth/configuration.json index f854a839a..34dcd26b9 100644 --- a/deployments/mainnet/wsteth/configuration.json +++ b/deployments/mainnet/wsteth/configuration.json @@ -22,7 +22,7 @@ "indexScale": "1e15", "baseSupplySpeed": "0e15", "baseBorrowSpeed": "0e15", - "baseMinForRewards": "1000e18" + "baseMinForRewards": "10e18" }, "rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", "assets": { diff --git a/deployments/mainnet/wsteth/deploy.ts b/deployments/mainnet/wsteth/deploy.ts index a7c041449..dfb9e1977 100644 --- a/deployments/mainnet/wsteth/deploy.ts +++ b/deployments/mainnet/wsteth/deploy.ts @@ -46,20 +46,6 @@ export default async function deploy(deploymentManager: DeploymentManager, deplo true ); - // Deploy reverse multiplicative price feed for weETH - const weETHScalingPriceFeed = await deploymentManager.deploy( - 'weETH:priceFeed', - 'pricefeeds/ReverseMultiplicativePriceFeed.sol', - [ - weETHToETHPriceFeed.address, // weETH / ETH price feed - wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed) - 8, // decimals - 'weETH / wstETH price feed' // description - ], - true - ); - - // Import shared contracts from cUSDCv3 const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt'); diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index 937b05622..83f0e1340 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -116,18 +116,15 @@ export default migration('1723732097_configurate_and_ens', { timelock, COMP, // rsETH, - // ezETH, - // weETH + // ezETH } = await deploymentManager.getContracts(); // 1. & 2. & 3. // const rsETHInfo = await comet.getAssetInfoByAddress(rsETH.address); // const ezETHInfo = await comet.getAssetInfoByAddress(ezETH.address); - // const weETHInfo = await comet.getAssetInfoByAddress(weETH.address); // expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); // expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); - // expect(weETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(70 / 86400, 15, 18)); // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(50 / 86400, 15, 18)); From 96b8e8dcdbe03a59703b0c6edb1428a1c0551aa7 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Tue, 27 Aug 2024 22:13:12 +0300 Subject: [PATCH 19/29] feat: create new task --- .github/workflows/enact-migration.yaml | 3 +- tasks/deployment_manager/task.ts | 121 +++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 723f0dbf2..d1129b07c 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -115,8 +115,7 @@ jobs: - name: Run Deploy Market and Enact Migration (impersonate) run: | - yarn hardhat deploy --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} - yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} + yarn hardhat deploy_and_migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} env: DEBUG: true ETH_PK: "${{ inputs.eth_pk }}" diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index f68286910..cf69fdc2a 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -231,3 +231,124 @@ task('migrate', 'Runs migration') } } ); + +task('deploy_and_migrate', 'Runs deploy and migration') + .addPositionalParam('migration', 'name of migration') + .addOptionalParam('impersonate', 'the governor will impersonate the passed account for proposals [only when simulating]') + .addFlag('simulate', 'only simulates the blockchain effects') + .addFlag('noDeploy', 'skip the actual deploy step') + .addFlag('noVerify', 'do not verify any contracts') + .addFlag('noVerifyImpl', 'do not verify the impl contract') + .addFlag('overwrite', 'overwrites cache') + .addFlag('prepare', 'runs preparation [defaults to true if enact not specified]') + .addFlag('enact', 'enacts migration [implies prepare]') + .addFlag('noEnacted', 'do not write enacted to the migration script') + .addParam('deployment', 'The deployment to deploy') + .setAction( + async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate, noDeploy, noVerify, noVerifyImpl }, env) => { + const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env; + const network = env.network.name; + const tag = `${network}/${deployment}`; + const dm = new DeploymentManager( + network, + deployment, + maybeForkEnv, + { + writeCacheToDisk: !simulate || overwrite, // Don't write to disk when simulating, unless overwrite is set + verificationStrategy: 'lazy', + } + ); + + if (noDeploy) { + // Don't run the deploy script + } else { + try { + const overrides = undefined; // TODO: pass through cli args + const delta = await dm.runDeployScript(overrides ?? { allMissing: true }); + console.log(`[${tag}] Deployed ${dm.counter} contracts, spent ${dm.spent} Ξ`); + console.log(`[${tag}]\n${dm.diffDelta(delta)}`); + } catch (e) { + console.log(`[${tag}] Failed to deploy with error: ${e}`); + } + } + + const verify = noVerify ? false : !simulate; + const desc = verify ? 'Verify' : 'Would verify'; + if (noVerify && simulate) { + // Don't even print if --no-verify is set with --simulate + } else { + await dm.verifyContracts(async (address, args) => { + if (args.via === 'buildfile') { + const { contract: _, ...rest } = args; + console.log(`[${tag}] ${desc} ${address}:`, rest); + } else { + console.log(`[${tag}] ${desc} ${address}:`, args); + } + return verify; + }); + + if (noVerifyImpl) { + // Don't even try if --no-verify-impl + } else { + // Maybe verify the comet impl too + const comet = await dm.contract('comet'); + const cometImpl = await dm.contract('comet:implementation'); + const configurator = await dm.contract('configurator'); + const config = await configurator.getConfiguration(comet.address); + const args: VerifyArgs = { + via: 'artifacts', + address: cometImpl.address, + constructorArguments: [config] + }; + console.log(`[${tag}] ${desc} ${cometImpl.address}:`, args); + if (verify) { + await dm.verifyContract(args); + } + } + } + await dm.spider(); + + let governanceDm: DeploymentManager; + const base = env.config.scenario.bases.find(b => b.network === network && b.deployment === deployment); + const isBridgedDeployment = base.auxiliaryBase !== undefined; + const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined; + + if (governanceBase) { + const governanceEnv = hreForBase(governanceBase, simulate); + governanceDm = new DeploymentManager( + governanceBase.network, + governanceBase.deployment, + governanceEnv, + { + writeCacheToDisk: !simulate || overwrite, // Don't write to disk when simulating, unless overwrite is set + verificationStrategy: 'eager', // We use eager here to verify contracts right after they are deployed + } + ); + await governanceDm.spider(); + } else { + governanceDm = dm; + } + + if (impersonate && !simulate) { + throw new Error('Cannot impersonate an address if not simulating a migration. Please specify --simulate to simulate.'); + } else if (impersonate && simulate) { + const signer = await impersonateAddress(governanceDm, impersonate, 10n ** 18n); + governanceDm._signers.unshift(signer); + } + + const migrationPath = `${__dirname}/../../deployments/${network}/${deployment}/migrations/${migrationName}.ts`; + const [migration] = await loadMigrations([migrationPath]); + if (!migration) { + throw new Error(`Unknown migration for network ${network}/${deployment}: \`${migrationName}\`.`); + } + if (!prepare && !enact) { + prepare = true; + } + + await runMigration(dm, governanceDm, prepare, enact, migration, overwrite); + + if (enact && !noEnacted) { + await writeEnacted(migration, dm, true); + } + + }); From 2db7f1c5e1b7a5b1d85a5406cb7b686408f33569 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 28 Aug 2024 14:48:28 +0300 Subject: [PATCH 20/29] fix --- .github/workflows/enact-migration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index d1129b07c..67fc6ecad 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -134,7 +134,7 @@ jobs: GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} GOV_NETWORK: ${{ env.GOV_NETWORK }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - if: github.event.inputs.impersonateAccount == '' + if: github.event.inputs.impersonateAccount == '' && github.event.inputs.with_deploy == 'false' - name: Run Enact Migration (impersonate) run: | yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }} @@ -145,7 +145,7 @@ jobs: GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }} GOV_NETWORK: ${{ env.GOV_NETWORK }} REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} - if: github.event.inputs.impersonateAccount != '' + if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'false' - name: Commit changes if: ${{ github.event.inputs.simulate == 'false' }} run: | From 98fc0c2020113f3d3dad6f52aef977c85877cd2a Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 28 Aug 2024 17:55:48 +0300 Subject: [PATCH 21/29] fix: linter --- contracts/bulkers/MainnetBulkerWithWstETHSupport.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol index efb78039f..ae9382846 100644 --- a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol +++ b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol @@ -91,7 +91,8 @@ contract MainnetBulkerWithWstETHSupport is BaseBulker { */ function deposit(address comet) external payable { (bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0)); - require(success, 'transfer failed'); + if(!success) revert TransferOutFailed(); + uint wstETHAmount = ERC20(wsteth).balanceOf(address(this)); lastValue=wstETHAmount; doTransferOut(wsteth, comet, wstETHAmount); From ca245ce7244d6a77e3a0641a8d329a1d8531e35b Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 4 Sep 2024 12:30:31 +0300 Subject: [PATCH 22/29] fix: fixes for contract after audit --- .../MainnetBulkerWithWstETHSupport.sol | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol index ae9382846..a05016d91 100644 --- a/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol +++ b/contracts/bulkers/MainnetBulkerWithWstETHSupport.sol @@ -26,6 +26,10 @@ contract MainnetBulkerWithWstETHSupport is BaseBulker { /// @notice The action for withdrawing staked ETH from Comet bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH"; + /** Custom errors **/ + + error UnsupportedBaseAsset(); + /** * @notice Construct a new MainnetBulker instance * @param admin_ The admin of the Bulker contract @@ -59,12 +63,17 @@ contract MainnetBulkerWithWstETHSupport is BaseBulker { /** * @notice Wraps stETH to wstETH and supplies to a user in Comet * @dev Note: This contract must have permission to manage msg.sender's Comet account - * @dev Note: wstETH base asset is NOT supported + * @dev Note: Supports `stETHAmount` of `uint256.max` to fully repay the wstETH debt + * @dev Note: Only for the cwstETHv3 market */ function supplyStEthTo(address comet, address to, uint stETHAmount) internal { - doTransferIn(steth, msg.sender, stETHAmount); - ERC20(steth).approve(wsteth, stETHAmount); - uint wstETHAmount = IWstETH(wsteth).wrap(stETHAmount); + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); + uint256 _stETHAmount = stETHAmount == type(uint256).max + ? IWstETH(wsteth).getStETHByWstETH(CometInterface(comet).borrowBalanceOf(msg.sender)) + : stETHAmount; + doTransferIn(steth, msg.sender, _stETHAmount); + ERC20(steth).approve(wsteth, _stETHAmount); + uint wstETHAmount = IWstETH(wsteth).wrap(_stETHAmount); ERC20(wsteth).approve(comet, wstETHAmount); CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount); } @@ -72,29 +81,29 @@ contract MainnetBulkerWithWstETHSupport is BaseBulker { /** * @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user * @dev Note: This contract must have permission to manage msg.sender's Comet account - * @dev Note: wstETH base asset is NOT supported * @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet + * @dev Note: Only for the cwstETHv3 market */ function withdrawStEthTo(address comet, address to, uint stETHAmount) internal { + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); uint wstETHAmount = stETHAmount == type(uint256).max - ? CometInterface(comet).collateralBalanceOf(msg.sender, wsteth) + ? CometInterface(comet).balanceOf(msg.sender) : IWstETH(wsteth).getWstETHByStETH(stETHAmount); CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount); uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount); doTransferOut(steth, to, unwrappedStETHAmount); } - uint256 public lastValue = 1; - /** * @notice Submits received ether to get stETH and wraps it to wstETH, received wstETH is transferred to Comet */ function deposit(address comet) external payable { + if(msg.sender != admin) revert Unauthorized(); + if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset(); (bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0)); if(!success) revert TransferOutFailed(); uint wstETHAmount = ERC20(wsteth).balanceOf(address(this)); - lastValue=wstETHAmount; doTransferOut(wsteth, comet, wstETHAmount); } } \ No newline at end of file From 18f9d8ced03c92d067e91833ac2d365c07f6d5f2 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 4 Sep 2024 13:56:20 +0300 Subject: [PATCH 23/29] fix: working migration --- .../mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts | 2 +- src/deploy/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index 83f0e1340..f4e5121e3 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -139,7 +139,7 @@ export default migration('1723732097_configurate_and_ens', { // 5. & 6. // expect reserves to be close to wstETHAmount +- 0.05 - expect(await comet.getReserves()).to.be.closeTo(wstETHAmount, exp(5, 16)); + expect(await comet.getReserves()).to.be.closeTo(wstETHAmount, exp(1, 17)); // 7. const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 3a03a4e46..7381208a9 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -88,6 +88,7 @@ export const WHALES = { '0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e', '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', + '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' ], polygon: [ From 8f70ce0bac9959b42c4afffec1e803a30f1554b8 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 4 Sep 2024 16:03:56 +0300 Subject: [PATCH 24/29] fix: description, rewards and scenario fixes --- .../migrations/1723732097_configurate_and_ens.ts | 8 ++++---- scenario/BulkerScenario.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index f4e5121e3..180cc5825 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -94,7 +94,7 @@ export default migration('1723732097_configurate_and_ens', { } ]; - const description = '# Initialize cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-dai-v3-comet-on-mainnet-recommendation/5380/1).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](<>) and [forum discussion](https://www.comp.xyz/t/gauntlet-dai-v3-comet-on-mainnet-recommendation/5380).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Ethereum Mainnet cwstETHv3 market.'; + const description = '# Initialize cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504/4).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](<>) and [forum discussion](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Ethereum Mainnet cwstETHv3 market.'; const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) ); @@ -126,8 +126,8 @@ export default migration('1723732097_configurate_and_ens', { // expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); // expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); - // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(70 / 86400, 15, 18)); - // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(50 / 86400, 15, 18)); + // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(8 / 86400, 15, 18)); // 92592592592 + // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(4 / 86400, 15, 18)); // 46296296296 // 4 const config = await rewards.rewardConfig(comet.address); @@ -138,7 +138,7 @@ export default migration('1723732097_configurate_and_ens', { expect((await comet.pauseGuardian()).toLowerCase()).to.be.eq('0xbbf3f1421d886e9b2c5d716b5192ac998af2012c'); // 5. & 6. - // expect reserves to be close to wstETHAmount +- 0.05 + // expect reserves to be close to wstETHAmount +- 0.1 expect(await comet.getReserves()).to.be.closeTo(wstETHAmount, exp(1, 17)); // 7. diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 83748f7f8..e2d55889d 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -4,10 +4,11 @@ import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; -async function hasWETHAsCollateral(ctx: CometContext): Promise { +async function hasWETHAsCollateralOrBase(ctx: CometContext): Promise { const comet = await ctx.getComet(); const bulker = await ctx.getBulker(); const wrappedNativeToken = await bulker.wrappedNativeToken(); + if((await comet.baseToken()).toLowerCase() === wrappedNativeToken.toLowerCase()) return true; const numAssets = await comet.numAssets(); for (let i = 0; i < numAssets; i++) { const { asset } = await comet.getAssetInfo(i); @@ -161,7 +162,7 @@ scenario( await bulker.ACTION_TRANSFER_ASSET() ]; - if(await hasWETHAsCollateral(context)){ + if(await hasWETHAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -175,7 +176,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - if(await hasWETHAsCollateral(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -439,7 +440,7 @@ scenario( await bulker.ACTION_CLAIM_REWARD(), ]; - if(await hasWETHAsCollateral(context)){ + if(await hasWETHAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -454,7 +455,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - if(await hasWETHAsCollateral(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); From f7b6607c864653e98e71cd2c9afb410ad3fe36b9 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Wed, 4 Sep 2024 20:59:31 +0300 Subject: [PATCH 25/29] fix: reduce seeded reserves to 20 --- .../mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index 180cc5825..a3f56c88f 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -10,7 +10,7 @@ const ENSSubdomainLabel = 'v3-additional-grants'; const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; const ENSTextRecordKey = 'v3-official-markets'; -const wstETHAmount = ethers.BigNumber.from(exp(100, 18)); +const wstETHAmount = ethers.BigNumber.from(exp(20, 18)); export default migration('1723732097_configurate_and_ens', { async prepare() { From 5500d7be5faa946a9249e50ffe6c979b55f69533 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 5 Sep 2024 09:40:35 +0000 Subject: [PATCH 26/29] Modified deployment roots from GitHub Actions --- deployments/mainnet/wsteth/roots.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deployments/mainnet/wsteth/roots.json b/deployments/mainnet/wsteth/roots.json index 9e26dfeeb..6d3a235ec 100644 --- a/deployments/mainnet/wsteth/roots.json +++ b/deployments/mainnet/wsteth/roots.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "comet": "0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3", + "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", + "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", + "bulker": "0x2c776041CCFe903071AF44aa147368a9c8EEA518" +} \ No newline at end of file From 7481cd30ae4502346236ae7d5c45cc2dbcf6c9fe Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Thu, 5 Sep 2024 13:17:14 +0300 Subject: [PATCH 27/29] feat: set caps and speeds --- deployments/mainnet/wsteth/configuration.json | 8 ++++---- .../1723732097_configurate_and_ens.ts | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/deployments/mainnet/wsteth/configuration.json b/deployments/mainnet/wsteth/configuration.json index 34dcd26b9..0bd7b4d37 100644 --- a/deployments/mainnet/wsteth/configuration.json +++ b/deployments/mainnet/wsteth/configuration.json @@ -20,8 +20,8 @@ }, "tracking": { "indexScale": "1e15", - "baseSupplySpeed": "0e15", - "baseBorrowSpeed": "0e15", + "baseSupplySpeed": "92592592592e0", + "baseBorrowSpeed": "46296296296e0", "baseMinForRewards": "10e18" }, "rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", @@ -32,7 +32,7 @@ "borrowCF": 0.88, "liquidateCF": 0.91, "liquidationFactor": 0.96, - "supplyCap": "0e18" + "supplyCap": "10_000e18" }, "ezETH": { "address": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", @@ -40,7 +40,7 @@ "borrowCF": 0.88, "liquidateCF": 0.91, "liquidationFactor": 0.94, - "supplyCap": "0e18" + "supplyCap": "15_000e18" } } } diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index a3f56c88f..529a59c12 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -94,7 +94,7 @@ export default migration('1723732097_configurate_and_ens', { } ]; - const description = '# Initialize cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504/4).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](<>) and [forum discussion](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Ethereum Mainnet cwstETHv3 market.'; + const description = '# Initialize cwstETHv3 on Ethereum Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Mainnet network. This proposal takes the governance steps recommended and necessary to initialize a Compound III wstETH market on Mainnet; upon execution, cwstETHv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504/4).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/911), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/10717773287) and [forum discussion](https://www.comp.xyz/t/add-wsteth-market-on-mainnet/5504).\n\n\n## Proposal Actions\n\nThe first proposal action sets the CometFactory for the new Comet instance in the existing Configurator.\n\nThe second action configures the Comet instance in the Configurator.\n\nThe third action deploys an instance of the newly configured factory and upgrades the Comet instance to use that implementation.\n\nThe fourth action configures the existing rewards contract for the newly deployed Comet instance.\n\nThe fifth action converts ether to wstETH and transfers it to the Comet to seed the reserves.\n\nThe sixth action updates the ENS TXT record `v3-official-markets` on `v3-additional-grants.compound-community-licenses.eth`, updating the official markets JSON to include the new Ethereum Mainnet cwstETHv3 market.'; const txn = await deploymentManager.retry( async () => trace((await governor.propose(...await proposal(actions, description)))) ); @@ -115,19 +115,19 @@ export default migration('1723732097_configurate_and_ens', { rewards, timelock, COMP, - // rsETH, - // ezETH + rsETH, + ezETH } = await deploymentManager.getContracts(); // 1. & 2. & 3. - // const rsETHInfo = await comet.getAssetInfoByAddress(rsETH.address); - // const ezETHInfo = await comet.getAssetInfoByAddress(ezETH.address); + const rsETHInfo = await comet.getAssetInfoByAddress(rsETH.address); + const ezETHInfo = await comet.getAssetInfoByAddress(ezETH.address); - // expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); - // expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); + expect(rsETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); + expect(ezETHInfo.supplyCap).to.be.eq(exp(15_000, 18)); - // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(8 / 86400, 15, 18)); // 92592592592 - // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(4 / 86400, 15, 18)); // 46296296296 + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(8 / 86400, 15, 18)); // 92592592592 + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(4 / 86400, 15, 18)); // 46296296296 // 4 const config = await rewards.rewardConfig(comet.address); From 9c6025e6fa6ff529fc81fe9ec50e44f74277d975 Mon Sep 17 00:00:00 2001 From: Mikhailo Shabodyash Date: Mon, 9 Sep 2024 15:09:24 +0300 Subject: [PATCH 28/29] fix: revert skip --- scenario/constraints/ProposalConstraint.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 141df1d66..f1b9fa204 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,9 +62,9 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 307 - if (proposal.id.eq(307)) { - console.log('Skipping proposal 307'); + // temporary hack to skip proposal 281 + if (proposal.id.eq(281)) { + console.log('Skipping proposal 281'); continue; } From 001a437bcf81e9b76bfefcc875325bb7e9a0b821 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Mon, 9 Sep 2024 13:19:50 +0000 Subject: [PATCH 29/29] Modified migration from GitHub Actions --- .../wsteth/migrations/1723732097_configurate_and_ens.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts index 529a59c12..6d628e27c 100644 --- a/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts +++ b/deployments/mainnet/wsteth/migrations/1723732097_configurate_and_ens.ts @@ -105,8 +105,8 @@ export default migration('1723732097_configurate_and_ens', { trace(`Created proposal ${proposalId}.`); }, - async enacted(): Promise { - return false; + async enacted(deploymentManager: DeploymentManager): Promise { + return true; }, async verify(deploymentManager: DeploymentManager) {