diff --git a/deployments/arbitrum/weth/migrations/1766663003_change_price_feeds_to_svr.ts b/deployments/arbitrum/weth/migrations/1766663003_change_price_feeds_to_svr.ts new file mode 100644 index 000000000..9d62632da --- /dev/null +++ b/deployments/arbitrum/weth/migrations/1766663003_change_price_feeds_to_svr.ts @@ -0,0 +1,381 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; +import { applyL1ToL2Alias, estimateL2Transaction } from '../../../../scenario/utils/arbitrumUtils'; + +const USDC_TO_USD_SVR_PRICE_FEED_ADDRESS = '0xe4c892BE702F8e0771122CCaAA0E50BF9639e2Fd'; +const USDT_TO_USD_SVR_PRICE_FEED_ADDRESS = '0x6AA147E11E423F529BEDAed75F3128D5fbE67939'; +const ETH_TO_USD_SVR_PRICE_FEED_ADDRESS = '0xe4dF63Bf89fD868A899F2422B030709FD79Be921'; + +const WSTETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS = '0x311930889C61E141E15a61D11BE974D749390E7A'; +const ETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS = '0xA2699232B341881B1Ed85d91592b7c259E029aCf'; +const RSETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS = '0xA4F2e977CAb3177D61E2e7eAEcd257Bf09F2f915'; +const WETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS = '0x4F12633d511dC3049DE1ea923b7047fBeD0070D2'; +const RETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS = '0x60F2058379716A64a7A5d29219397e79bC552194'; + +let newPriceFeedUSDCAddress: string; +let newPriceFeedUSDTAddress: string; + +let oldUSDCPriceFeed: string; +let oldUSDTPriceFeed: string; + +let oldWstETHPriceFeed: string; +let oldEzETHPriceFeed: string; +let oldRsETHPriceFeed: string; +let oldWeETHPriceFeed: string; +let oldRETHPriceFeed: string; + +export default migration('1766663003_change_price_feeds_to_svr', { + async prepare(deploymentManager: DeploymentManager) { + const _usdcPriceFeed = await deploymentManager.deploy( + 'USDC:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + USDC_TO_USD_SVR_PRICE_FEED_ADDRESS, // USDC / USD price feed + ETH_TO_USD_SVR_PRICE_FEED_ADDRESS, // USD / ETH price feed + 8, // decimals + 'USDC / ETH SVR Price Feed', // description + ], + true + ); + + const _usdtPriceFeed = await deploymentManager.deploy( + 'USDT:priceFeed', + 'pricefeeds/ReverseMultiplicativePriceFeed.sol', + [ + USDT_TO_USD_SVR_PRICE_FEED_ADDRESS, // USDT / USD price feed + ETH_TO_USD_SVR_PRICE_FEED_ADDRESS, // USD / ETH price feed + 8, // decimals + 'USDT / ETH SVR Price Feed', // description + ], + true + ); + + return { + USDCPriceFeedAddress: _usdcPriceFeed.address, + USDTPriceFeedAddress: _usdtPriceFeed.address, + }; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + { + USDCPriceFeedAddress, + USDTPriceFeedAddress, + } + ) => { + const trace = deploymentManager.tracer(); + newPriceFeedUSDCAddress = USDCPriceFeedAddress; + newPriceFeedUSDTAddress = USDTPriceFeedAddress; + + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator, + USDC, + 'USD₮0':USDT, + wstETH, + ezETH, + rsETH, + weETH, + rETH + } = await deploymentManager.getContracts(); + + const { governor, timelock, arbitrumInbox } = await govDeploymentManager.getContracts(); + + const updateUSDCPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + USDC.address, + newPriceFeedUSDCAddress + ) + ); + + const updateUSDTPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + USDT.address, + newPriceFeedUSDTAddress + ) + ); + + const updateWstETHPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + wstETH.address, + WSTETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS + ) + ); + + const updateEzETHPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + ezETH.address, + ETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS + ) + ); + + const updateRsETHPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + rsETH.address, + RSETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS + ) + ); + + const updateWeETHPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + weETH.address, + WETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS + ) + ); + + const updateRETHPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + rETH.address, + RETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS + ) + ); + + const deployAndUpgradeToCalldata = await calldata( + cometAdmin.populateTransaction.deployAndUpgradeTo( + configurator.address, + comet.address + ) + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + configurator.address, + configurator.address, + configurator.address, + configurator.address, + configurator.address, + configurator.address, + cometAdmin.address + ], + [0, 0, 0, 0, 0, 0, 0, 0], + [ + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'deployAndUpgradeTo(address,address)' + ], + [ + updateUSDCPriceFeedCalldata, + updateUSDTPriceFeedCalldata, + updateWstETHPriceFeedCalldata, + updateEzETHPriceFeedCalldata, + updateRsETHPriceFeedCalldata, + updateWeETHPriceFeedCalldata, + updateRETHPriceFeedCalldata, + deployAndUpgradeToCalldata], + ] + ); + + [,, oldUSDCPriceFeed] = await comet.getAssetInfoByAddress(USDC.address); + [,, oldUSDTPriceFeed] = await comet.getAssetInfoByAddress(USDT.address); + [,, oldWstETHPriceFeed] = await comet.getAssetInfoByAddress(wstETH.address); + [,, oldEzETHPriceFeed] = await comet.getAssetInfoByAddress(ezETH.address); + [,, oldRsETHPriceFeed] = await comet.getAssetInfoByAddress(rsETH.address); + [,, oldWeETHPriceFeed] = await comet.getAssetInfoByAddress(weETH.address); + [,, oldRETHPriceFeed] = await comet.getAssetInfoByAddress(rETH.address); + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + + const refundAddress = l2Timelock.address; + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo USDT Comet on Arbitrum. + { + contract: arbitrumInbox, + signature: 'createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)', + args: [ + bridgeReceiver.address, // address to, + 0, // uint256 l2CallValue, + createRetryableTicketGasParams.maxSubmissionCost, // uint256 maxSubmissionCost, + refundAddress, // address excessFeeRefundAddress, + refundAddress, // address callValueRefundAddress, + createRetryableTicketGasParams.gasLimit, // uint256 gasLimit, + createRetryableTicketGasParams.maxFeePerGas, // uint256 maxFeePerGas, + l2ProposalData, // bytes calldata data + ], + value: createRetryableTicketGasParams.deposit + }, + ]; + + const description = `# Update price feeds in cWETHv3 on Arbitrum with SVR and CAPO price feeds. + +## Proposal summary + +This proposal updates existing price feeds for USDC, USDT, wstETH, ezETH, rsETH, weETH, and rETH assets on the WETH market on Arbitrum. + +Further detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/1080), [forum discussion for CAPO](https://www.comp.xyz/t/woof-correlated-assets-price-oracle-capo/6245) and [forum discussion for SVR](https://www.comp.xyz/t/request-for-proposal-rfp-oracle-extractable-value-oev-solution-for-compound-protocol/6786). + +### CAPO summary + +CAPO is a price oracle adapter designed to support assets that grow gradually relative to a base asset - such as liquid staking tokens that accumulate yield over time. It provides a mechanism to track this expected growth while protecting downstream protocol from sudden or manipulated price spikes. wstETH, ezETH, rsETH, weETH, and rETH price feeds are updated to their CAPO implementations. + +### CAPO audit + +CAPO has been audited by [OpenZeppelin](https://www.comp.xyz/t/capo-price-feed-audit/6631, as well as the LST / LRT implementation [here](https://www.comp.xyz/t/capo-lst-lrt-audit/7118). + + +### SVR fee recipient + +SVR generates revenue from liquidators and Compound DAO will receive that revenue as part of the protocol fee. The fee recipient for SVR is set to Compound DAO multisig: 0xd9496F2A3fd2a97d8A4531D92742F3C8F53183cB. + +## Proposal actions + +The first action updates USDC and USDT price feeds to the SVR implementation. This sends the encoded 'updateAssetPriceFeed' and 'deployAndUpgradeTo' calls across the bridge to the governance receiver on Arbitrum. +`; + const txn = await govDeploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ), 0, 300_000 + ); + + const event = txn.events.find( + (event: { event: string }) => 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, + USDC, + 'USD₮0':USDT, + wstETH, + ezETH, + rsETH, + weETH, + rETH, + } = await deploymentManager.getContracts(); + + // 1. USDC + const USDCIndexInComet = await configurator.getAssetIndex( + comet.address, + USDC.address + ); + const USDCInCometInfo = await comet.getAssetInfoByAddress(USDC.address); + const USDCInConfiguratorInfoUSDCComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[USDCIndexInComet]; + + expect(USDCInCometInfo.priceFeed).to.eq(newPriceFeedUSDCAddress); + expect(USDCInConfiguratorInfoUSDCComet.priceFeed).to.eq(newPriceFeedUSDCAddress); + + expect(await comet.getPrice(newPriceFeedUSDCAddress)).to.be.closeTo(await comet.getPrice(oldUSDCPriceFeed), 1e6); + + // 2. USDT + const USDTIndexInComet = await configurator.getAssetIndex( + comet.address, + USDT.address + ); + const USDTInCometInfo = await comet.getAssetInfoByAddress(USDT.address); + const USDTInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[USDTIndexInComet]; + + expect(USDTInCometInfo.priceFeed).to.eq(newPriceFeedUSDTAddress); + expect(USDTInConfiguratorInfoUSDTComet.priceFeed).to.eq(newPriceFeedUSDTAddress); + expect(await comet.getPrice(newPriceFeedUSDTAddress)).to.be.closeTo(await comet.getPrice(oldUSDTPriceFeed), 1e6); + + // 3. wstETH + const wstETHIndexInComet = await configurator.getAssetIndex( + comet.address, + wstETH.address + ); + const wstETHInCometInfo = await comet.getAssetInfoByAddress(wstETH.address); + const wstETHInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[wstETHIndexInComet]; + + expect(wstETHInCometInfo.priceFeed).to.eq(WSTETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(wstETHInConfiguratorInfoUSDTComet.priceFeed).to.eq(WSTETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(await comet.getPrice(WSTETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS)).to.be.closeTo(await comet.getPrice(oldWstETHPriceFeed), 1e6); + + // 4. ezETH + const ezETHIndexInComet = await configurator.getAssetIndex( + comet.address, + ezETH.address + ); + const ezETHInCometInfo = await comet.getAssetInfoByAddress(ezETH.address); + const ezETHInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[ezETHIndexInComet]; + + expect(ezETHInCometInfo.priceFeed).to.eq(ETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(ezETHInConfiguratorInfoUSDTComet.priceFeed).to.eq(ETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(await comet.getPrice(ETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS)).to.be.closeTo(await comet.getPrice(oldEzETHPriceFeed), 1e6); + + // 5. rsETH + const rsETHIndexInComet = await configurator.getAssetIndex( + comet.address, + rsETH.address + ); + const rsETHInCometInfo = await comet.getAssetInfoByAddress(rsETH.address); + const rsETHInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[rsETHIndexInComet]; + + expect(rsETHInCometInfo.priceFeed).to.eq(RSETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(rsETHInConfiguratorInfoUSDTComet.priceFeed).to.eq(RSETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(await comet.getPrice(RSETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS)).to.be.closeTo(await comet.getPrice(oldRsETHPriceFeed), 1e6); + + // 6. weETH + const weETHIndexInComet = await configurator.getAssetIndex( + comet.address, + weETH.address + ); + const weETHInCometInfo = await comet.getAssetInfoByAddress(weETH.address); + const weETHInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[weETHIndexInComet]; + + expect(weETHInCometInfo.priceFeed).to.eq(WETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(weETHInConfiguratorInfoUSDTComet.priceFeed).to.eq(WETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(await comet.getPrice(WETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS)).to.be.closeTo(await comet.getPrice(oldWeETHPriceFeed), 1e6); + + // 7. rETH + const rETHIndexInComet = await configurator.getAssetIndex( + comet.address, + rETH.address + ); + const rETHInCometInfo = await comet.getAssetInfoByAddress(rETH.address); + const rETHInConfiguratorInfoUSDTComet = ( + await configurator.getConfiguration(comet.address) + ).assetConfigs[rETHIndexInComet]; + + expect(rETHInCometInfo.priceFeed).to.eq(RETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(rETHInConfiguratorInfoUSDTComet.priceFeed).to.eq(RETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS); + expect(await comet.getPrice(RETH_TO_ETH_CAPO_PRICE_FEED_ADDRESS)).to.be.closeTo(await comet.getPrice(oldRETHPriceFeed), 1e6); + } +}); diff --git a/deployments/base/aero/migrations/1761125221_upgrade_to_capo_price_feeds.ts b/deployments/base/aero/migrations/1761125221_upgrade_to_capo_price_feeds.ts new file mode 100644 index 000000000..2a6172bc0 --- /dev/null +++ b/deployments/base/aero/migrations/1761125221_upgrade_to_capo_price_feeds.ts @@ -0,0 +1,165 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; +import { Numeric } from '../../../../test/helpers'; +import { AggregatorV3Interface } from '../../../../build/types'; + +export function exp(i: number, d: Numeric = 0, r: Numeric = 6): bigint { + return (BigInt(Math.floor(i * 10 ** Number(r))) * 10n ** BigInt(d)) / 10n ** BigInt(r); +} + +const ETH_USD_PRICE_FEED = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70'; + +const WSTETH_ADDRESS = '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452'; +const WSTETH_STETH_PRICE_FEED_ADDRESS = '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061'; + +const FEED_DECIMALS = 8; +const blockToFetch = 36000000; + +let newWstETHPriceFeed: string; +let oldWstETHPriceFeed: string; + +export default migration('1761125221_upgrade_to_capo_price_feeds', { + async prepare(deploymentManager: DeploymentManager) { + const { timelock } = await deploymentManager.getContracts(); + const blockToFetchTimestamp = (await deploymentManager.hre.ethers.provider.getBlock(blockToFetch))!.timestamp; + + //1. wstEth + const rateProviderWstEth = await deploymentManager.existing('wstEth:priceFeed', WSTETH_STETH_PRICE_FEED_ADDRESS, 'base', 'contracts/capo/contracts/interfaces/AggregatorV3Interface.sol:AggregatorV3Interface') as AggregatorV3Interface; + const [, currentRatioWstEth] = await rateProviderWstEth.latestRoundData({ blockTag: blockToFetch }); + + const wstEthCapoPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol', + [ + timelock.address, + ETH_USD_PRICE_FEED, + WSTETH_STETH_PRICE_FEED_ADDRESS, + 'wstETH / USD CAPO Price Feed', + FEED_DECIMALS, + 3600, + { + snapshotRatio: currentRatioWstEth, + snapshotTimestamp: blockToFetchTimestamp, + maxYearlyRatioGrowthPercent: exp(0.0404, 4) + } + ], + true + ); + + return { + wstEthCapoPriceFeedAddress: wstEthCapoPriceFeed.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { + wstEthCapoPriceFeedAddress + }) { + newWstETHPriceFeed = wstEthCapoPriceFeedAddress; + + const trace = deploymentManager.tracer(); + + const { + configurator, + comet, + bridgeReceiver, + cometAdmin + } = await deploymentManager.getContracts(); + + const { + governor, + baseL1CrossDomainMessenger + } = await govDeploymentManager.getContracts(); + + const updateWstEthPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + WSTETH_ADDRESS, + wstEthCapoPriceFeedAddress + ) + ); + + const deployAndUpgradeToCalldata = await calldata( + cometAdmin.populateTransaction.deployAndUpgradeTo( + configurator.address, + comet.address + ) + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, cometAdmin.address], + [0, 0], + ['updateAssetPriceFeed(address,address,address)', 'deployAndUpgradeTo(address,address)'], + [updateWstEthPriceFeedCalldata, deployAndUpgradeToCalldata], + ] + ); + + [,, oldWstETHPriceFeed] = await comet.getAssetInfoByAddress(WSTETH_ADDRESS); + + const mainnetActions = [ + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [ + bridgeReceiver.address, + l2ProposalData, + 3_000_000 + ] + }, + ]; + + const description = `# Update wstETH price feed in cAEROv3 on Base with CAPO implementation. + +## Proposal summary + +This proposal updates existing price feeds for wstETH on the AERO market on Base. + +### CAPO summary + +CAPO is a price oracle adapter designed to support assets that grow gradually relative to a base asset - such as liquid staking tokens that accumulate yield over time. It provides a mechanism to track this expected growth while protecting downstream protocol from sudden or manipulated price spikes. wstETH price feed is updated to their CAPO implementations. + +Further detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/1038) and [forum discussion for CAPO](https://www.comp.xyz/t/woof-correlated-assets-price-oracle-capo/6245). + +### CAPO audit + +CAPO has been audited by [OpenZeppelin](https://www.comp.xyz/t/capo-price-feed-audit/6631, as well as the LST / LRT implementation [here](https://www.comp.xyz/t/capo-lst-lrt-audit/7118). + +## Proposal actions + +The first action updates wstETH price feed to the CAPO implementation. This sends the encoded 'updateAssetPriceFeed' and 'deployAndUpgradeTo' calls across the bridge to the governance receiver on Base. +`; + + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + const event = txn.events.find( + (event: { event: string }) => 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 wstETHIndexInComet = await configurator.getAssetIndex(comet.address, WSTETH_ADDRESS); + + // Check if the price feeds are set correctly. + const wstETHInCometInfo = await comet.getAssetInfoByAddress(WSTETH_ADDRESS); + const wstETHInConfiguratorInfoWETHComet = (await configurator.getConfiguration(comet.address)).assetConfigs[wstETHIndexInComet]; + + expect(wstETHInCometInfo.priceFeed).to.eq(newWstETHPriceFeed); + expect(wstETHInConfiguratorInfoWETHComet.priceFeed).to.eq(newWstETHPriceFeed); + expect(await comet.getPrice(newWstETHPriceFeed)).to.equal(await comet.getPrice(oldWstETHPriceFeed)); + }, +}); diff --git a/deployments/base/weth/migrations/1761228877_upgrade_to_capo_price_feeds.ts b/deployments/base/weth/migrations/1761228877_upgrade_to_capo_price_feeds.ts new file mode 100644 index 000000000..8ac4fb51c --- /dev/null +++ b/deployments/base/weth/migrations/1761228877_upgrade_to_capo_price_feeds.ts @@ -0,0 +1,324 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, proposal } from '../../../../src/deploy'; +import { utils } from 'ethers'; +import { Numeric } from '../../../../test/helpers'; +import { AggregatorV3Interface } from '../../../../build/types'; + +export function exp(i: number, d: Numeric = 0, r: Numeric = 6): bigint { + return (BigInt(Math.floor(i * 10 ** Number(r))) * 10n ** BigInt(d)) / 10n ** BigInt(r); +} + +const WSTETH_ADDRESS = '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452'; +const WSTETH_STETH_PRICE_FEED_ADDRESS = '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061'; + +const EZETH_ADDRESS = '0x2416092f143378750bb29b79ed961ab195cceea5'; +const EZETH_TO_ETH_PRICE_FEED_ADDRESS = '0xC4300B7CF0646F0Fe4C5B2ACFCCC4dCA1346f5d8'; + +const WRSETH_ADDRESS = '0xEDfa23602D0EC14714057867A78d01e94176BEA0'; +const WRSETH_ORACLE = '0xe8dD07CCf5BC4922424140E44Eb970F5950725ef'; + +const WEETH_ADDRESS = '0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A'; +const WEETH_STETH_PRICE_FEED_ADDRESS = '0x35e9D7001819Ea3B39Da906aE6b06A62cfe2c181'; + +const blockToFetch = 36000000; + +let newWstETHToETHPriceFeed: string; +let newEzETHToETHPriceFeed: string; +let newWrsEthToETHPriceFeed: string; +let newWeEthToETHPriceFeed: string; + +let oldWstETHToETHPriceFeed: string; +let oldEzETHToETHPriceFeed: string; +let oldWrsEthToETHPriceFeed: string; +let oldWeEthToETHPriceFeed: string; + +const FEED_DECIMALS = 8; +export default migration('1761228877_upgrade_to_capo_price_feeds', { + async prepare(deploymentManager: DeploymentManager) { + const { timelock } = await deploymentManager.getContracts(); + const blockToFetchTimestamp = (await deploymentManager.hre.ethers.provider.getBlock(blockToFetch))!.timestamp; + const constantPriceFeed = await deploymentManager.fromDep('WETH:priceFeed', 'base', 'weth'); + + //1. wstEth + const rateProviderWstEth = await deploymentManager.existing('wstETH:_rateProvider', WSTETH_STETH_PRICE_FEED_ADDRESS, 'base', 'contracts/capo/contracts/interfaces/AggregatorV3Interface.sol:AggregatorV3Interface') as AggregatorV3Interface; + const [, currentRatioWstEth] = await rateProviderWstEth.latestRoundData({blockTag: blockToFetch}); + + const wstEthCapoPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol', + [ + timelock.address, + constantPriceFeed.address, + WSTETH_STETH_PRICE_FEED_ADDRESS, + 'wstETH / ETH CAPO Price Feed', + FEED_DECIMALS, + 3600, + { + snapshotRatio: currentRatioWstEth, + snapshotTimestamp: blockToFetchTimestamp, + maxYearlyRatioGrowthPercent: exp(0.0404, 4) + } + ], + true + ); + + //2. ezEth + const rateProviderEzEth = await deploymentManager.existing('ezETH:_rateProvider', EZETH_TO_ETH_PRICE_FEED_ADDRESS, 'base', 'contracts/capo/contracts/interfaces/AggregatorV3Interface.sol:AggregatorV3Interface') as AggregatorV3Interface; + const [, currentRatioEzEth] = await rateProviderEzEth.latestRoundData({blockTag: blockToFetch}); + const ezEthCapoPriceFeed = await deploymentManager.deploy( + 'ezETH:priceFeed', + 'capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol', + [ + timelock.address, + constantPriceFeed.address, + EZETH_TO_ETH_PRICE_FEED_ADDRESS, + 'ezETH / ETH CAPO Price Feed', + FEED_DECIMALS, + 3600, + { + snapshotRatio: currentRatioEzEth, + snapshotTimestamp: blockToFetchTimestamp, + maxYearlyRatioGrowthPercent: exp(0.0707, 4) + } + ], + true + ); + + const rateProviderRsEth = await deploymentManager.existing('rsETH:_rateProvider', WRSETH_ORACLE, 'base', 'contracts/capo/contracts/interfaces/AggregatorV3Interface.sol:AggregatorV3Interface') as AggregatorV3Interface; + const [, currentRatioWrsEth] = await rateProviderRsEth.latestRoundData({blockTag: blockToFetch}); + const rsEthCapoPriceFeed = await deploymentManager.deploy( + 'rsETH:priceFeed', + 'capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol', + [ + timelock.address, + constantPriceFeed.address, + WRSETH_ORACLE, + 'rsETH / ETH CAPO Price Feed', + FEED_DECIMALS, + 3600, + { + snapshotRatio: currentRatioWrsEth, + snapshotTimestamp: blockToFetchTimestamp, + maxYearlyRatioGrowthPercent: exp(0.0554, 4) + } + ], + true + ); + + + const rateProviderWeEth = await deploymentManager.existing('weETH:_rateProvider', WEETH_STETH_PRICE_FEED_ADDRESS, 'base', 'contracts/capo/contracts/interfaces/AggregatorV3Interface.sol:AggregatorV3Interface') as AggregatorV3Interface; + const [, currentRatioWeEth] = await rateProviderWeEth.latestRoundData({blockTag: blockToFetch}); + const weEthCapoPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol', + [ + timelock.address, + constantPriceFeed.address, + WEETH_STETH_PRICE_FEED_ADDRESS, + 'weETH / ETH CAPO Price Feed', + FEED_DECIMALS, + 3600, + { + snapshotRatio: currentRatioWeEth, + snapshotTimestamp: blockToFetchTimestamp, + maxYearlyRatioGrowthPercent: exp(0.0323, 4) + } + ], + true + ); + + return { + wstEthCapoPriceFeedAddress: wstEthCapoPriceFeed.address, + ezEthCapoPriceFeedAddress: ezEthCapoPriceFeed.address, + rsEthCapoPriceFeedAddress: rsEthCapoPriceFeed.address, + weEthCapoPriceFeedAddress: weEthCapoPriceFeed.address + }; + }, + + async enact(deploymentManager: DeploymentManager, govDeploymentManager, { + wstEthCapoPriceFeedAddress, + ezEthCapoPriceFeedAddress, + rsEthCapoPriceFeedAddress, + weEthCapoPriceFeedAddress + }) { + + newWstETHToETHPriceFeed = wstEthCapoPriceFeedAddress; + newEzETHToETHPriceFeed = ezEthCapoPriceFeedAddress; + newWrsEthToETHPriceFeed = rsEthCapoPriceFeedAddress; + newWeEthToETHPriceFeed = weEthCapoPriceFeedAddress; + + const trace = deploymentManager.tracer(); + + const { + configurator, + comet, + bridgeReceiver, + cometAdmin + } = await deploymentManager.getContracts(); + + const { + governor, + baseL1CrossDomainMessenger + } = await govDeploymentManager.getContracts(); + + const updateEzEthPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + EZETH_ADDRESS, + ezEthCapoPriceFeedAddress + ) + ); + + const updateWstEthPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + WSTETH_ADDRESS, + wstEthCapoPriceFeedAddress + ) + ); + + const updateRsEthPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + WRSETH_ADDRESS, + rsEthCapoPriceFeedAddress + ) + ); + + const updateWeEthPriceFeedCalldata = await calldata( + configurator.populateTransaction.updateAssetPriceFeed( + comet.address, + WEETH_ADDRESS, + weEthCapoPriceFeedAddress + ) + ); + + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + configurator.address, + configurator.address, + configurator.address, + cometAdmin.address + ], + [0, 0, 0, 0, 0], + [ + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'updateAssetPriceFeed(address,address,address)', + 'deployAndUpgradeTo(address,address)' + ], + [ + updateWstEthPriceFeedCalldata, + updateEzEthPriceFeedCalldata, + updateRsEthPriceFeedCalldata, + updateWeEthPriceFeedCalldata, + deployAndUpgradeToCalldata + ], + ] + ); + + [,, oldWstETHToETHPriceFeed] = await comet.getAssetInfoByAddress(WSTETH_ADDRESS); + [,, oldEzETHToETHPriceFeed] = await comet.getAssetInfoByAddress(EZETH_ADDRESS); + [,, oldWrsEthToETHPriceFeed] = await comet.getAssetInfoByAddress(WRSETH_ADDRESS); + [,, oldWeEthToETHPriceFeed] = await comet.getAssetInfoByAddress(WEETH_ADDRESS); + + const mainnetActions = [ + { + contract: baseL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [ + bridgeReceiver.address, + l2ProposalData, + 3_000_000 + ] + }, + ]; + + const description = `# Update price feeds in cWETHv3 on Base with CAPO implementation. + +## Proposal summary + +This proposal updates existing price feeds for wstETH, ezETH, rsETH, and weETH on the WETH market on Base. + +### CAPO summary + +CAPO is a price oracle adapter designed to support assets that grow gradually relative to a base asset - such as liquid staking tokens that accumulate yield over time. It provides a mechanism to track this expected growth while protecting downstream protocol from sudden or manipulated price spikes. wstETH, ezETH, rsETH, and weETH price feeds are updated to their CAPO implementations. + +Further detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/1040) and [forum discussion for CAPO](https://www.comp.xyz/t/woof-correlated-assets-price-oracle-capo/6245). + +### CAPO audit + +CAPO has been audited by [OpenZeppelin](https://www.comp.xyz/t/capo-price-feed-audit/6631, as well as the LST / LRT implementation [here](https://www.comp.xyz/t/capo-lst-lrt-audit/7118). + +## Proposal actions + +The first action updates wstETH, ezETH, rsETH, and weETH price feeds to the CAPO implementation. This sends the encoded 'updateAssetPriceFeed' and 'deployAndUpgradeTo' calls across the bridge to the governance receiver on Base. +`; + const txn = await govDeploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event: { event: string }) => 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(); + + // 1. wstETH + const wstETHIndexInComet = await configurator.getAssetIndex(comet.address, WSTETH_ADDRESS); + const wstETHInCometInfo = await comet.getAssetInfoByAddress(WSTETH_ADDRESS); + const wstETHInConfiguratorInfoWETHComet = (await configurator.getConfiguration(comet.address)).assetConfigs[wstETHIndexInComet]; + + expect(wstETHInCometInfo.priceFeed).to.eq(newWstETHToETHPriceFeed); + expect(wstETHInConfiguratorInfoWETHComet.priceFeed).to.eq(newWstETHToETHPriceFeed); + expect(await comet.getPrice(newWstETHToETHPriceFeed)).to.be.closeTo(await comet.getPrice(oldWstETHToETHPriceFeed), 1e6); + + // 2. ezETH + const ezETHIndexInComet = await configurator.getAssetIndex(comet.address, EZETH_ADDRESS); + const ezETHInCometInfo = await comet.getAssetInfoByAddress(EZETH_ADDRESS); + const ezETHInConfiguratorInfoWETHComet = (await configurator.getConfiguration(comet.address)).assetConfigs[ezETHIndexInComet]; + + expect(ezETHInCometInfo.priceFeed).to.eq(newEzETHToETHPriceFeed); + expect(ezETHInConfiguratorInfoWETHComet.priceFeed).to.eq(newEzETHToETHPriceFeed); + expect(await comet.getPrice(newEzETHToETHPriceFeed)).to.equal(await comet.getPrice(oldEzETHToETHPriceFeed)); + + // 3. wrsETH + const wrsETHIndexInComet = await configurator.getAssetIndex(comet.address, WRSETH_ADDRESS); + const wrsETHInCometInfo = await comet.getAssetInfoByAddress(WRSETH_ADDRESS); + const wrsETHInConfiguratorInfoWETHComet = (await configurator.getConfiguration(comet.address)).assetConfigs[wrsETHIndexInComet]; + + expect(wrsETHInCometInfo.priceFeed).to.eq(newWrsEthToETHPriceFeed); + expect(wrsETHInConfiguratorInfoWETHComet.priceFeed).to.eq(newWrsEthToETHPriceFeed); + expect(await comet.getPrice(newWrsEthToETHPriceFeed)).to.equal(await comet.getPrice(oldWrsEthToETHPriceFeed)); + + // 4. weETH + const weETHIndexInComet = await configurator.getAssetIndex(comet.address, WEETH_ADDRESS); + const weETHInCometInfo = await comet.getAssetInfoByAddress(WEETH_ADDRESS); + const weETHInConfiguratorInfoWETHComet = (await configurator.getConfiguration(comet.address)).assetConfigs[weETHIndexInComet]; + + expect(weETHInCometInfo.priceFeed).to.eq(newWeEthToETHPriceFeed); + expect(weETHInConfiguratorInfoWETHComet.priceFeed).to.eq(newWeEthToETHPriceFeed); + expect(await comet.getPrice(newWeEthToETHPriceFeed)).to.equal(await comet.getPrice(oldWeEthToETHPriceFeed)); + }, +}); diff --git a/deployments/mainnet/usdt/migrations/1735299664_upgrade_to_capo_price_feeds.ts b/deployments/mainnet/usdt/migrations/1735299664_upgrade_to_capo_price_feeds.ts index 399fe8401..efe7f8750 100644 --- a/deployments/mainnet/usdt/migrations/1735299664_upgrade_to_capo_price_feeds.ts +++ b/deployments/mainnet/usdt/migrations/1735299664_upgrade_to_capo_price_feeds.ts @@ -262,11 +262,11 @@ export default migration('1735299664_upgrade_to_capo_price_feeds', { This proposal updates existing price feeds for wstETH, sFRAX, weETH, WBTC, WETH, mETH, COMP, and LINK on the USDT market on Mainnet. -SVR summery +## SVR summary [RFP process](https://www.comp.xyz/t/oev-rfp-process-update-july-2025/6945) and community [vote](https://snapshot.box/#/s:comp-vote.eth/proposal/0x98a3873319cdb5a4c66b6f862752bdcfb40d443a5b9c2f9472188d7ed5f9f2e0) passed and decided to implement Chainlink's SVR solution for Mainnet markets, this proposal updates wstETH, WBTC, WETH, LINK, weETH, mETH, COMP price feeds to support SVR implementations. -CAPO summery +## CAPO summary CAPO is a price oracle adapter designed to support assets that grow gradually relative to a base asset - such as liquid staking tokens that accumulate yield over time. It provides a mechanism to track this expected growth while protecting downstream protocol from sudden or manipulated price spikes. wstETH, sFRAX, weETH, mETH price feeds are updated to their CAPO implementations. diff --git a/forge/script/marketupdates/helpers/GovernanceHelper.sol b/forge/script/marketupdates/helpers/GovernanceHelper.sol index eaaf77422..f56257cfb 100644 --- a/forge/script/marketupdates/helpers/GovernanceHelper.sol +++ b/forge/script/marketupdates/helpers/GovernanceHelper.sol @@ -210,7 +210,7 @@ library GovernanceHelper { } function voteOnProposal(Vm vm, uint256 proposalId, address proposalCreator) public { - address[12] memory voters = getTopDelegates(); + address[11] memory voters = getTopDelegates(); console.log("Voting on proposal with ID: ", proposalId); console.log("Proposal Creator: ", proposalCreator); @@ -225,12 +225,11 @@ library GovernanceHelper { } } - function getTopDelegates() public pure returns (address[12] memory) { + function getTopDelegates() public pure returns (address[11] memory) { return [ 0x070341aA5Ed571f0FB2c4a5641409B1A46b4961b, 0x0579A616689f7ed748dC07692A3F150D44b0CA09, - 0x9AA835Bc7b8cE13B9B0C9764A52FbF71AC62cCF1, - 0x7E959eAB54932f5cFd10239160a7fd6474171318, + 0x66cD62c6F8A4BB0Cd8720488BCBd1A6221B765F9, 0x2210dc066aacB03C9676C4F1b36084Af14cCd02E, 0x88F659b4B6D5614B991c6404b34f821e10390eC0, 0xb06DF4dD01a5c5782f360aDA9345C87E86ADAe3D, diff --git a/hardhat.config.ts b/hardhat.config.ts index 63d2caae8..480c078b3 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -342,6 +342,7 @@ const config: HardhatUserConfig = { hardforkHistory: { berlin: 1, london: 2, + shanghai: 3, } }; return acc; diff --git a/plugins/deployment_manager/DeploymentManager.ts b/plugins/deployment_manager/DeploymentManager.ts index 2bcfc2357..5ddf68d71 100644 --- a/plugins/deployment_manager/DeploymentManager.ts +++ b/plugins/deployment_manager/DeploymentManager.ts @@ -282,7 +282,7 @@ export class DeploymentManager { } } - stashRelayMessage(messanger: string, callData: string, signer: string) { + stashRelayMessage(messenger: string, callData: string, signer: string) { try { const cacheDir = path.resolve(__dirname, '../..', 'cache'); mkdirSync(cacheDir, { recursive: true }); @@ -301,7 +301,7 @@ export class DeploymentManager { } } - const newEntry = { messanger, callData, signer }; + const newEntry = { messenger, callData, signer }; if (!data.some(entry => JSON.stringify(entry) === JSON.stringify(newEntry))) { data.push(newEntry); writeFileSync(file, JSON.stringify(data, null, 2), 'utf8'); diff --git a/scenario/SupplyScenario.ts b/scenario/SupplyScenario.ts index be3be8537..10a02a33f 100644 --- a/scenario/SupplyScenario.ts +++ b/scenario/SupplyScenario.ts @@ -406,12 +406,20 @@ scenario( scenario( 'Comet#supplyFrom > repay borrow', { - tokenBalances: { - albert: { $base: 1010 } - }, - cometBalances: { - betty: { $base: '<= -1000' } // in units of asset, not wei - }, + tokenBalances: async (ctx) => ( + { + albert: { + $base: getConfigForScenario(ctx).supplyBase + (0.01 * getConfigForScenario(ctx).supplyBase) + } + } + ), + cometBalances: async (ctx) => ( + { + betty: { + $base: `<= -${getConfigForScenario(ctx).supplyBase}` + } + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; diff --git a/scenario/TransferScenario.ts b/scenario/TransferScenario.ts index e5c94310b..0039b9601 100644 --- a/scenario/TransferScenario.ts +++ b/scenario/TransferScenario.ts @@ -180,32 +180,35 @@ scenario( scenario( 'Comet#transferFrom > withdraw to repay', { - cometBalances: { - albert: { $base: 1000, $asset0: 50 }, // in units of asset, not wei - betty: { $base: -1000 }, - charles: { $base: 1000 }, // to give the protocol enough base for others to borrow from - }, + cometBalances: async (ctx) => ( + { + albert: { $base: getConfigForScenario(ctx).transferBase, $asset0: getConfigForScenario(ctx).transferAsset2 }, // in units of asset, not wei + betty: { $base: -getConfigForScenario(ctx).transferBase }, + charles: { $base: getConfigForScenario(ctx).transferBase }, // to give the protocol enough base for others to borrow from + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; const baseAssetAddress = await comet.baseToken(); const baseAsset = context.getAssetByAddress(baseAssetAddress); const scale = (await comet.baseScale()).toBigInt(); + const amountTransferred = BigInt(getConfigForScenario(context).transferBase) * scale; const utilization = await comet.getUtilization(); const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 70 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await albert.getCometBaseBalance(), amountTransferred, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -amountTransferred, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); await albert.allow(betty, true); // Betty withdraws from Albert to repay her own borrows - const toTransfer = 999n * scale; // XXX cannot withdraw 1000 (to ~0) + const toTransfer = amountTransferred - scale; // XXX cannot withdraw 1000 (to ~0) const txn = await betty.transferAssetFrom({ src: albert.address, dst: betty.address, asset: baseAsset.address, amount: toTransfer }); - expectApproximately(await albert.getCometBaseBalance(), scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await albert.getCometBaseBalance(), scale, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -scale, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); return txn; // return txn to measure gas } @@ -214,26 +217,29 @@ scenario( scenario( 'Comet#transfer base reverts if undercollateralized', { - cometBalances: { - albert: { $base: 1000, $asset0: 0.000001 }, // in units of asset, not wei - betty: { $base: -1000 }, - charles: { $base: 1000 }, // to give the protocol enough base for others to borrow from - }, + cometBalances: async (ctx) => ( + { + albert: { $base: getConfigForScenario(ctx).transferBase, $asset0: 0.000001 }, // in units of asset, not wei + betty: { $base: -getConfigForScenario(ctx).transferBase }, + charles: { $base: getConfigForScenario(ctx).transferBase }, // to give the protocol enough base for others to borrow from + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; const baseAssetAddress = await comet.baseToken(); const baseAsset = context.getAssetByAddress(baseAssetAddress); const scale = (await comet.baseScale()).toBigInt(); + const amountTransferred = BigInt(getConfigForScenario(context).transferBase) * scale; const utilization = await comet.getUtilization(); const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 100 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, 100n) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, 100n) + 2n); + expectApproximately(await albert.getCometBaseBalance(), amountTransferred, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -amountTransferred, getInterest(amountTransferred, borrowRate, 100n) + 2n); // Albert with positive balance transfers to Betty with negative balance - const toTransfer = 2001n * scale; // XXX min borrow... + const toTransfer = 2n*amountTransferred + scale; // XXX min borrow... await expectRevertCustom( albert.transferAsset({ dst: betty.address, @@ -248,28 +254,31 @@ scenario( scenario( 'Comet#transferFrom base reverts if undercollateralized', { - cometBalances: { - albert: { $base: 1000, $asset0: 0.000001 }, // in units of asset, not wei - betty: { $base: -1000 }, - charles: { $base: 1000 }, // to give the protocol enough base for others to borrow from - }, + cometBalances: async (ctx) => ( + { + albert: { $base: getConfigForScenario(ctx).transferBase, $asset0: 0.000001 }, // in units of asset, not wei + betty: { $base: -getConfigForScenario(ctx).transferBase }, + charles: { $base: getConfigForScenario(ctx).transferBase }, // to give the protocol enough base for others to borrow from + } + ), }, async ({ comet, actors }, context) => { const { albert, betty } = actors; const baseAssetAddress = await comet.baseToken(); const baseAsset = context.getAssetByAddress(baseAssetAddress); const scale = (await comet.baseScale()).toBigInt(); + const amountTransferred = BigInt(getConfigForScenario(context).transferBase) * scale; const utilization = await comet.getUtilization(); const borrowRate = (await comet.getBorrowRate(utilization)).toBigInt(); // XXX 70 seconds?! - expectApproximately(await albert.getCometBaseBalance(), 1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); - expectApproximately(await betty.getCometBaseBalance(), -1000n * scale, getInterest(1000n * scale, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await albert.getCometBaseBalance(), amountTransferred, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); + expectApproximately(await betty.getCometBaseBalance(), -amountTransferred, getInterest(amountTransferred, borrowRate, BigInt(getConfigForScenario(context).interestSeconds)) + 2n); await albert.allow(betty, true); // Albert with positive balance transfers to Betty with negative balance - const toTransfer = 2001n * scale; // XXX min borrow... + const toTransfer = 2n*amountTransferred + scale; // XXX min borrow... await expectRevertCustom( betty.transferAssetFrom({ src: albert.address, diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index df76002d9..7abb2c7f0 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -78,9 +78,15 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 510 - if (proposal.id.eq(510)) { - console.log('Skipping proposal 510'); + // temporary hack to skip proposal 524 + if (proposal.id.eq(524)) { + console.log('Skipping proposal 524'); + continue; + } + + // temporary hack to skip proposal 525 + if (proposal.id.eq(525)) { + console.log('Skipping proposal 525'); continue; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 410d7c318..23f94ddfa 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -910,7 +910,7 @@ export async function tenderlyExecute( return { network_id: chainId2.toString(), from: msg.signer, - to: msg.messanger, + to: msg.messenger, block_number: Number(block), block_header: { timestamp: gdm.hre.ethers.utils.hexlify(Number(timestamp)) diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index 0bccd6467..fa0523665 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -16,7 +16,7 @@ export default async function relayMessage( tenderlyLogs?: any[] ) { const bridgeNetwork = bridgeDeploymentManager.network; - console.log(`Relaying messages from ${bridgeNetwork} -> ${governanceDeploymentManager.network}`); + console.log(`Relaying messages from ${governanceDeploymentManager.network} -> ${bridgeNetwork}`); let proposal; switch (bridgeNetwork) { case 'base': diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index a104a629c..0e342c876 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -22,6 +22,7 @@ const config = { transferBase: 1000, transferAsset: 5000, transferAsset1: 5000, + transferAsset2: 50, interestSeconds: 110, withdrawBase: 1000, withdrawAsset: 3000, @@ -29,7 +30,8 @@ const config = { withdrawAsset1: 3000, withdrawCollateral: 100, transferCollateral: 100, - supplyCollateral: 100 + supplyCollateral: 100, + supplyBase: 1000, }; export function getConfigForScenario(ctx: CometContext, i?: number) { @@ -111,10 +113,11 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { } if (ctx.world.base.network === 'arbitrum' && ctx.world.base.deployment === 'usdc') { - config.bulkerAsset = 10000; - config.bulkerAsset1 = 10000; - config.withdrawAsset = 7000; + config.bulkerAsset = 100000; + config.bulkerAsset1 = 100000; + config.withdrawAsset = 10000; config.transferAsset = 500000; + config.transferAsset1 = 500000; config.transferBase = 100; if(i == 8) { // tBTC config.supplyCollateral = 2; @@ -124,11 +127,11 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { } if (ctx.world.base.network === 'arbitrum' && ctx.world.base.deployment === 'usdt') { - config.withdrawAsset = 7000; - config.bulkerAsset = 10000; + config.withdrawAsset = 10000; + config.bulkerAsset = 100000; config.bulkerAsset1 = 10000; - config.transferAsset = 10000; - config.transferAsset1 = 10000; + config.transferAsset = 100000; + config.transferAsset1 = 100000; if(i == 5) { // tBTC config.supplyCollateral = 2; config.transferCollateral = 2; @@ -137,11 +140,11 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { } if (ctx.world.base.network === 'arbitrum' && ctx.world.base.deployment === 'usdc.e') { - config.withdrawAsset = 7000; - config.bulkerAsset = 10000; - config.bulkerAsset1 = 10000; - config.transferAsset = 10000; - config.transferAsset1 = 10000; + config.withdrawAsset = 10000; + config.bulkerAsset = 100000; + config.bulkerAsset1 = 100000; + config.transferAsset = 500000; + config.transferAsset1 = 500000; config.liquidationDenominator = 84; config.liquidationBase = 100000; config.liquidationBase1 = 50000; @@ -153,15 +156,16 @@ export function getConfigForScenario(ctx: CometContext, i?: number) { } if (ctx.world.base.network === 'ronin' && ctx.world.base.deployment === 'weth') { + config.supplyBase = 100; config.transferBase = 10; - config.transferAsset = 200000; - config.transferAsset1 = 200000; + config.transferAsset = 400000; + config.transferAsset1 = 400000; config.rewardsAsset = 1000000; config.rewardsBase = 200; config.withdrawBase = 10; config.withdrawBase1 = 10; - config.withdrawAsset = 100000; - config.withdrawAsset1 = 10000; + config.withdrawAsset = 400000; + config.withdrawAsset1 = 30000; config.liquidationBase = 150; config.liquidationBase1 = 50; config.liquidationAsset = 5; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 3757ac900..3a3acd6ed 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -79,12 +79,11 @@ export type TestnetProposal = [ // Ideally these wouldn't be hardcoded, but other solutions are much more complex, and slower export const COMP_WHALES = { mainnet: [ - '0x9aa835bc7b8ce13b9b0c9764a52fbf71ac62ccf1', - '0x683a4f9915d6216f73d6df50151725036bd26c02', + '0x66cD62c6F8A4BB0Cd8720488BCBd1A6221B765F9', + '0xb06df4dd01a5c5782f360ada9345c87e86adae3d', + '0x3FB19771947072629C8EEE7995a2eF23B72d4C8A', '0x8169522c2C57883E8EF80C498aAB7820dA539806', - '0x8d07D225a769b7Af3A923481E1FdF49180e6A265', - '0x7d1a02C0ebcF06E1A36231A54951E061673ab27f', - '0x54A37d93E57c5DA659F508069Cf65A381b61E189', + '0x36cc7B13029B5DEe4034745FB4F24034f3F2ffc6', ], testnet: ['0xbbfe34e868343e6f4f5e8b5308de980d7bd88c46'] @@ -149,6 +148,7 @@ export const WHALES = { '0x54b5569deC8A6A8AE61A36Fd34e5c8945810db8b', // tBTC whale '0xDBD974Eb5360d053ea0c56B4DaCF4A9D3E894Ee2', // tETH whale '0xbA1333333333a1BA1108E8412f11850A5C319bA9', // tETH whale + '0xEA1132120ddcDDA2F119e99Fa7A27a0d036F7Ac9', // ezETH whale ], base: [ '0x6D3c5a4a7aC4B1428368310E4EC3bB1350d01455', // USDbC whale