From 9085b8a046535d40c41b1158652851d234e8fee3 Mon Sep 17 00:00:00 2001 From: MishaShWoof Date: Thu, 24 Oct 2024 19:58:08 +0300 Subject: [PATCH] Proposal for Mainnet USDS market (#942) Co-authored-by: dmitriy-woof-software Co-authored-by: GitHub Actions Bot <> Co-authored-by: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> --- .github/workflows/run-scenarios.yaml | 2 +- deployments/mainnet/usds/configuration.json | 75 +++++ deployments/mainnet/usds/deploy.ts | 40 +++ .../1729069465_configurate_and_ens.ts | 279 ++++++++++++++++++ deployments/mainnet/usds/relations.ts | 25 ++ deployments/mainnet/usds/roots.json | 7 + hardhat.config.ts | 9 +- scenario/LiquidationBotScenario.ts | 61 ++-- scenario/MainnetBulkerScenario.ts | 4 +- scenario/constraints/ProposalConstraint.ts | 12 +- scenario/utils/scenarioHelper.ts | 5 + .../liquidateUnderwaterBorrowers.ts | 16 + src/deploy/index.ts | 2 + 13 files changed, 502 insertions(+), 35 deletions(-) create mode 100644 deployments/mainnet/usds/configuration.json create mode 100644 deployments/mainnet/usds/deploy.ts create mode 100644 deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts create mode 100644 deployments/mainnet/usds/relations.ts create mode 100644 deployments/mainnet/usds/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 7f2020873..5da35d811 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, 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-aero, 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, mainnet-usds, 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-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/mainnet/usds/configuration.json b/deployments/mainnet/usds/configuration.json new file mode 100644 index 000000000..08c9c2de6 --- /dev/null +++ b/deployments/mainnet/usds/configuration.json @@ -0,0 +1,75 @@ +{ + "name": "Compound USDS", + "symbol": "cUSDSv3", + "baseToken": "USDS", + "baseTokenAddress": "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + "borrowMin": "10e18", + "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", + "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", + "baseTokenPriceFeed": "0xfF30586cD0F29eD462364C7e81375FC0C71219b1", + "storeFrontPriceFactor": 0.6, + "targetReserves": "20_000_000e18", + "rates": { + "borrowBase": 0.015, + "borrowSlopeLow": 0.0333, + "borrowKink": 0.9, + "borrowSlopeHigh": 4.0, + "supplyBase": 0, + "supplySlopeLow": 0.039, + "supplyKink": 0.9, + "supplySlopeHigh": 3.6 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "289351851851e0", + "baseBorrowSpeed": "289351851851e0", + "baseMinForRewards": "10000e18" + }, + "rewardTokenAddress": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "assets": { + "WETH": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "decimals": "18", + "borrowCF": 0.83, + "liquidateCF": 0.9, + "liquidationFactor": 0.95, + "supplyCap": "50_000e18" + }, + "USDe": { + "address": "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", + "priceFeed": "0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961", + "decimals": "18", + "borrowCF": 0.7, + "liquidateCF": 0.75, + "liquidationFactor": 0.85, + "supplyCap": "50_000_000e18" + }, + "cbBTC": { + "address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + "priceFeed": "0x2665701293fCbEB223D11A08D826563EDcCE423A", + "decimals": "8", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "150e8" + }, + "tBTC": { + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "priceFeed": "0x8350b7De6a6a2C1368E7D4Bd968190e13E354297", + "decimals": "18", + "borrowCF": 0.76, + "liquidateCF": 0.81, + "liquidationFactor": 0.9, + "supplyCap": "285e18" + }, + "wstETH": { + "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "decimals": "18", + "borrowCF": 0.82, + "liquidateCF": 0.87, + "liquidationFactor": 0.92, + "supplyCap": "10_000e18" + } + } +} \ No newline at end of file diff --git a/deployments/mainnet/usds/deploy.ts b/deployments/mainnet/usds/deploy.ts new file mode 100644 index 000000000..82bbcdbc0 --- /dev/null +++ b/deployments/mainnet/usds/deploy.ts @@ -0,0 +1,40 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const WETH = await deploymentManager.existing('WETH', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); + const COMP = await deploymentManager.existing('COMP', '0xc00e94Cb662C3520282E6f5717214004A7f26888'); + const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'); + const cbBTC = await deploymentManager.existing('cbBTC', '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf'); + const tBTC = await deploymentManager.existing('tBTC', '0x18084fbA666a33d37592fA2633fD49a74DD93a88'); + const USDe = await deploymentManager.existing('USDe', '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3'); + const USDS = await deploymentManager.existing('USDS', '0xdC035D45d973E3EC169d2276DDab16f1e407384F'); + + const wstETHtoUsdPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/WstETHPriceFeed.sol', + [ + '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', // ETH / USD price feed + wstETH.address, // wstETH token + 8, // decimals + ], + true + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); + 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'); + const bulker = await deploymentManager.fromDep('bulker', 'mainnet', 'weth'); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bulker, + rewards, + COMP + }; +} \ No newline at end of file diff --git a/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts b/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts new file mode 100644 index 000000000..0512a7db6 --- /dev/null +++ b/deployments/mainnet/usds/migrations/1729069465_configurate_and_ens.ts @@ -0,0 +1,279 @@ +import { ethers, utils } 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 USDSAmount = ethers.BigNumber.from(exp(300_000, 18)); +const cDAIAddress = '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643'; +const DaiToUsdsConverterAddress = '0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A'; +const DAIAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + +export default migration('1729069465_configurate_and_ens', { + async prepare() { + return {}; + }, + + async enact(deploymentManager: DeploymentManager, _) { + const trace = deploymentManager.tracer(); + + const { + comet, + cometAdmin, + configurator, + rewards, + COMP, + governor + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt', true); + + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const currentChainId = 1; + const newMarketObject = { baseSymbol: 'USDS', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + if (officialMarketsJSON[currentChainId]) { + officialMarketsJSON[currentChainId].push(newMarketObject); + } else { + officialMarketsJSON[currentChainId] = [newMarketObject]; + } + + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [USDSAmount] + ); + + const approveCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [DaiToUsdsConverterAddress, USDSAmount] + ); + + const convertCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [comet.address, USDSAmount] + ); + + 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. Get DAI reserves from cDAI contract + { + target: cDAIAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 6. Approve DAI to the Comet contract + { + target: DAIAddress, + signature: 'approve(address,uint256)', + calldata: approveCalldata, + }, + // 7. Convert DAI to USDS + { + target: DaiToUsdsConverterAddress, + signature: 'daiToUsds(address,uint256)', + calldata: convertCalldata + }, + // 8. 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 = '# Initialize cUSDSv3 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 USDS market on Mainnet; upon execution, cUSDSv3 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-collateral-usds-market-on-eth-mainnet/5781/5).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/942), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/11392132048) and [forum discussion](https://www.comp.xyz/t/add-collateral-usds-market-on-eth-mainnet/5781).\n\n\n## wstETH price feed\n\nFor LSTs, the goal is to use full exchange rate price feeds. Thus, we treat that stETH:ETH is 1:1.\n\n## sUSDS and sUSDe collaterals\n\nGauntlet suggests having sUDSS and sUSDe collaterals. This proposal does not include them, because the price feed is under the audit. We suggest to start bootstrapping the liquidity without these collaterals and as the price feed is audited, we will add these collaterals. We discussed it with Gauntlet, and we received the approval to have such an approach.\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 reduces Compound’s [cDAI](https://etherscan.io/address/0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643) reserves and transfers it to Timelock, in order to convert it to USDS to then seed the market reserves for the cUSDSv3 Comet.\n\nThe sixth action approves DAI to DAI-to-USDS native converter.\n\nThe seventh action converts DAI to USDS with 1:1 ratio and transfers USDS to cUSDSv3 Comet.\n\nThe eight 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 cUSDSv3 market.'; + 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, + rewards, + timelock, + WETH, + wstETH, + cbBTC, + tBTC, + USDe, + COMP + } = await deploymentManager.getContracts(); + + // 1. & 2. & 3. + const cbbtcInfo = await comet.getAssetInfoByAddress(cbBTC.address); + const tbtcInfo = await comet.getAssetInfoByAddress(tBTC.address); + const wethInfo = await comet.getAssetInfoByAddress(WETH.address); + const usdeInfo = await comet.getAssetInfoByAddress(USDe.address); + const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); + + expect(cbbtcInfo.supplyCap).to.be.eq(exp(150, 8)); + expect(tbtcInfo.supplyCap).to.be.eq(exp(285, 18)); + expect(wethInfo.supplyCap).to.be.eq(exp(50_000, 18)); + expect(usdeInfo.supplyCap).to.be.eq(exp(50_000_000, 18)); + expect(wstETHInfo.supplyCap).to.be.eq(exp(10_000, 18)); + + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(25 / 86400, 15, 18)); // 289351851851 + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(25 / 86400, 15, 18)); // 289351851851 + + // 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(await comet.getReserves()).to.be.equal(USDSAmount); + + // 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); + expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); + expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); + expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); + expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); + + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3', + }, + { + baseSymbol: 'USDS', + 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', + }, + { + baseSymbol: 'AERO', + cometAddress: '0x784efeB622244d2348d4F2522f8860B96fbEcE89' + } + ], + 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/usds/relations.ts b/deployments/mainnet/usds/relations.ts new file mode 100644 index 000000000..7b0cc418d --- /dev/null +++ b/deployments/mainnet/usds/relations.ts @@ -0,0 +1,25 @@ +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', + }, + 'ERC1967Proxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; \ No newline at end of file diff --git a/deployments/mainnet/usds/roots.json b/deployments/mainnet/usds/roots.json new file mode 100644 index 000000000..565665908 --- /dev/null +++ b/deployments/mainnet/usds/roots.json @@ -0,0 +1,7 @@ +{ + "comet": "0x5D409e56D886231aDAf00c8775665AD0f9897b56", + "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", + "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", + "bulker": "0xa397a8C2086C554B531c02E29f3291c9704B00c7", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 411476a38..f2cf294fc 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -27,6 +27,7 @@ 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 mainnetUsdsRelationConfigMap from './deployments/mainnet/usds/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import polygonUsdtRelationConfigMap from './deployments/polygon/usdt/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; @@ -356,7 +357,8 @@ const config: HardhatUserConfig = { usdc: mainnetRelationConfigMap, weth: mainnetWethRelationConfigMap, usdt: mainnetUsdtRelationConfigMap, - wsteth: mainnetWstETHRelationConfigMap + wsteth: mainnetWstETHRelationConfigMap, + usds: mainnetUsdsRelationConfigMap, }, polygon: { usdc: polygonRelationConfigMap, @@ -422,6 +424,11 @@ const config: HardhatUserConfig = { network: 'mainnet', deployment: 'wsteth' }, + { + name: 'mainnet-usds', + network: 'mainnet', + deployment: 'usds' + }, { name: 'development', network: 'hardhat', diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 8c504b8e1..45dd6922c 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -5,6 +5,7 @@ import { ethers, event, exp, wait } from '../test/helpers'; import CometActor from './context/CometActor'; import { CometInterface, OnChainLiquidator } from '../build/types'; import { getPoolConfig, flashLoanPools } from '../scripts/liquidation_bot/liquidateUnderwaterBorrowers'; +import { getConfigForScenario } from './utils/scenarioHelper'; interface LiquidationAddresses { balancerVault: string; @@ -537,16 +538,20 @@ 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' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), - tokenBalances: { - $comet: { $base: 100000 }, - }, - cometBalances: { - albert: { - $asset0: ' == 200', - }, - betty: { $base: 1000 }, - }, + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + tokenBalances: async (ctx) => ( + { + $comet: { $base: getConfigForScenario(ctx).liquidationBase }, + } + ), + cometBalances: async (ctx) => ( + { + albert: { + $asset0: ` == ${getConfigForScenario(ctx).liquidationAsset}`, + }, + betty: { $base: getConfigForScenario(ctx).liquidationBase1 }, + } + ) }, async ({ comet, actors }, _context, world) => { const { albert, betty } = actors; @@ -583,7 +588,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, @@ -648,16 +653,20 @@ 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' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth', network: 'mainnet'}]), - tokenBalances: { - $comet: { $base: 100000 }, - }, - cometBalances: { - albert: { - $asset0: ' == 200', - }, - betty: { $base: 1000 }, - }, + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }, { network: 'polygon' }, { network: 'arbitrum' }]), + tokenBalances: async (ctx) => ( + { + $comet: { $base: getConfigForScenario(ctx).liquidationBase }, + } + ), + cometBalances: async (ctx) => ( + { + albert: { + $asset0: ` == ${getConfigForScenario(ctx).liquidationAsset}}`, + }, + betty: { $base: getConfigForScenario(ctx).liquidationBase1 }, + } + ) }, async ({ comet, actors }, _context, world) => { const { albert, betty } = actors; @@ -694,7 +703,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, @@ -761,7 +770,8 @@ scenario( mainnet: { usdc: 2250000, weth: 20, - usdt: 2250000 + usdt: 2250000, + usds: 225000, }, }; const assetAmounts = { @@ -769,6 +779,7 @@ scenario( usdc: ' == 5000', // COMP weth: ' == 7000', // CB_ETH usdt: ' == 5000', // COMP + usds: ' == 850', // WETH }, }; @@ -778,7 +789,7 @@ scenario( upgrade: { targetReserves: exp(20_000, 18) }, - filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}]), + filter: async (ctx) => matchesDeployment(ctx, [{ network: 'mainnet' }]) && !matchesDeployment(ctx, [{deployment: 'wsteth'}, {deployment: 'usds'}]), tokenBalances: async (ctx) => ( { $comet: { @@ -829,7 +840,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(90n)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); await albert.withdrawAsset({ asset: baseToken, diff --git a/scenario/MainnetBulkerScenario.ts b/scenario/MainnetBulkerScenario.ts index 590aacaba..fdfa20a5e 100644 --- a/scenario/MainnetBulkerScenario.ts +++ b/scenario/MainnetBulkerScenario.ts @@ -52,7 +52,7 @@ scenario( const toSupplyStEth = exp(.1, 18); - await context.sourceTokens(toSupplyStEth + 2n, new CometAsset(stETH), albert); + await context.sourceTokens(toSupplyStEth + 3n, new CometAsset(stETH), albert); expect(await stETH.balanceOf(albert.address)).to.be.greaterThanOrEqual(toSupplyStEth); @@ -68,7 +68,7 @@ scenario( await albert.invoke({ actions, calldata }); - expect(await stETH.balanceOf(albert.address)).to.be.approximately(0n, 1n); + expectApproximately((await stETH.balanceOf(albert.address)).toBigInt(), 0n, 2n); expectApproximately( (await comet.collateralBalanceOf(albert.address, wstETH.address)).toBigInt(), (await wstETH.getWstETHByStETH(toSupplyStEth)).toBigInt(), diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index da4899ea3..5c507707f 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,15 +62,15 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 339 - if (proposal.id.eq(339)) { - console.log('Skipping proposal 339'); + // temporary hack to skip proposal 348 + if (proposal.id.eq(348)) { + console.log('Skipping proposal 348'); continue; } - // temporary hack to skip proposal 340 - if (proposal.id.eq(340)) { - console.log('Skipping proposal 340'); + // temporary hack to skip proposal 349 + if (proposal.id.eq(349)) { + console.log('Skipping proposal 349'); continue; } diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 9ef7b4d12..5d63f4599 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -33,6 +33,7 @@ export function getConfigForScenario(ctx: CometContext) { config.transferAsset = 500; config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'wsteth') { config.liquidationBase = 10000; config.liquidationBase1 = 1000; @@ -41,6 +42,10 @@ export function getConfigForScenario(ctx: CometContext) { config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'usds') { + config.liquidationAsset = 100; + } + if (ctx.world.base.network === 'base' && ctx.world.base.deployment === 'aero') { config.interestSeconds = 110; } diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index b2d5d4e0b..29a70303e 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -39,6 +39,7 @@ const addresses = { WETH9: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', CB_ETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', WST_ETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + RS_ETH: '0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7', USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, goerli: { @@ -94,6 +95,14 @@ export const flashLoanPools = { usdt: { tokenAddress: addresses.mainnet.DAI, poolFee: 100 + }, + wsteth: { + tokenAddress: addresses.mainnet.WETH9, + poolFee: 100 + }, + usds: { + tokenAddress: addresses.mainnet.DAI, + poolFee: 3000, } }, goerli: { @@ -189,6 +198,13 @@ export function getPoolConfig(tokenAddress: string) { uniswapPoolFee: 500 } }, + [addresses.mainnet.RS_ETH.toLowerCase()]: { + ...defaultPoolConfig, + ...{ + exchange: Exchange.Balancer, + balancerPoolId: '0x58aadfb1afac0ad7fca1148f3cde6aedf5236b6d00000000000000000000067f' + } + }, [addresses.mainnet.WST_ETH.toLowerCase()]: { ...defaultPoolConfig, ...{ diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 7381208a9..8719843f9 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -89,6 +89,8 @@ export const WHALES = { '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', + '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale + '0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' ], polygon: [