From 74e29fc56f45bac0b5a3e2d1389f177920ad29dc Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Tue, 28 May 2024 16:51:03 -0500 Subject: [PATCH 1/5] Add funding.json for passing OP grant (#853) --- funding.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 funding.json diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..cf4e36d45 --- /dev/null +++ b/funding.json @@ -0,0 +1,5 @@ +{ + "opRetro": { + "projectId": "0xd3d00c946f62ac8cf49a85e3ab7d295d3b56f68d0b9f1fa4d595742fa372cf78" + } +} \ No newline at end of file From e740b4228f75a1f38d30da809197c1ff57013fb6 Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Wed, 29 May 2024 00:09:21 +0200 Subject: [PATCH 2/5] Update id for OP market (#854) --- funding.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funding.json b/funding.json index cf4e36d45..39e5fbe6d 100644 --- a/funding.json +++ b/funding.json @@ -1,5 +1,5 @@ { "opRetro": { - "projectId": "0xd3d00c946f62ac8cf49a85e3ab7d295d3b56f68d0b9f1fa4d595742fa372cf78" + "projectId": "0x76bc7b1df06fc0fa2cd2406591907d462eead9655c938cd95eca8f133e324c85" } } \ No newline at end of file From 3ed7bbf6d142bdcf3abeb0b823abda6c7f3d2213 Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Wed, 29 May 2024 00:17:56 +0200 Subject: [PATCH 3/5] Remove funding.json (#855) --- funding.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 funding.json diff --git a/funding.json b/funding.json deleted file mode 100644 index 39e5fbe6d..000000000 --- a/funding.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "opRetro": { - "projectId": "0x76bc7b1df06fc0fa2cd2406591907d462eead9655c938cd95eca8f133e324c85" - } -} \ No newline at end of file From caa66a32717fde2f92afa861f3d3d63e6cf9fb4e Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:46:11 +0200 Subject: [PATCH 4/5] Deploy compound v3 usdt market (#848) Forum thread - https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975 --------- Co-authored-by: Mikhailo Shabodyash <67977488+EviLord032@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> --- .github/workflows/enact-migration.yaml | 17 +- .github/workflows/run-scenarios.yaml | 2 +- deployments/optimism/usdt/configuration.json | 57 ++++ deployments/optimism/usdt/deploy.ts | 71 ++++ .../1713012100_configurate_and_ens.ts | 316 ++++++++++++++++++ deployments/optimism/usdt/relations.ts | 30 ++ deployments/optimism/usdt/roots.json | 11 + hardhat.config.ts | 10 +- scenario/BulkerScenario.ts | 6 +- 9 files changed, 516 insertions(+), 4 deletions(-) create mode 100644 deployments/optimism/usdt/configuration.json create mode 100644 deployments/optimism/usdt/deploy.ts create mode 100644 deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts create mode 100644 deployments/optimism/usdt/relations.ts create mode 100644 deployments/optimism/usdt/roots.json diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index 1b1558688..6842ba074 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -36,6 +36,10 @@ on: description: Run ID for Artifact eth_pk: description: Ignore if you plan to use WalletConnect, otherwise, you can paste in a Ethereum private key + impersonateAccount: + description: Impersonate Account + required: false + default: '' jobs: enact-migration: name: Enact Migration @@ -114,7 +118,18 @@ 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 == '' + - 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 }} + 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 != '' - name: Commit changes if: ${{ github.event.inputs.simulate == 'false' }} run: | diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 7c551a883..9c2990382 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, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/optimism/usdt/configuration.json b/deployments/optimism/usdt/configuration.json new file mode 100644 index 000000000..a6e5e3665 --- /dev/null +++ b/deployments/optimism/usdt/configuration.json @@ -0,0 +1,57 @@ +{ + "name": "Compound USDT", + "symbol": "cUSDTv3", + "baseToken": "USDT", + "baseTokenAddress": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", + "baseTokenPriceFeed": "0xECef79E109e997bCA29c1c0897ec9d7b03647F5E", + "pauseGuardian": "0x3fFd6c073a4ba24a113B18C8F373569640916A45", + "borrowMin": "1e5", + "storeFrontPriceFactor": 0.6, + "targetReserves": "20000000e6", + "rates": { + "supplyKink": 0.9, + "supplySlopeLow": 0.059, + "supplySlopeHigh": 2.9, + "supplyBase": 0, + "borrowKink": 0.9, + "borrowSlopeLow": 0.061, + "borrowSlopeHigh": 3.2, + "borrowBase": 0.015 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "0e15", + "baseBorrowSpeed": "0e15", + "baseMinForRewards": "1000e6" + }, + "assets": { + "OP": { + "address": "0x4200000000000000000000000000000000000042", + "priceFeed": "0x0D276FC14719f9292D5C1eA2198673d1f4269246", + "decimals": "18", + "borrowCF": 0.65, + "liquidateCF": 0.7, + "liquidationFactor": 0.8, + "supplyCap": "0e18" + }, + "WETH": { + "address": "0x4200000000000000000000000000000000000006", + "priceFeed": "0x13e3Ee699D1909E989722E753853AE30b17e08c5", + "decimals": "18", + "borrowCF": 0.83, + "liquidateCF": 0.9, + "liquidationFactor": 0.95, + "supplyCap": "0e18" + }, + "WBTC": { + "address": "0x68f180fcCe6836688e9084f035309E29Bf0A2095", + "priceFeed": "0x718A5788b89454aAE3A028AE9c111A29Be6c2a6F", + "decimals": "8", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "0e8" + } + } + } + \ No newline at end of file diff --git a/deployments/optimism/usdt/deploy.ts b/deployments/optimism/usdt/deploy.ts new file mode 100644 index 000000000..ce633038b --- /dev/null +++ b/deployments/optimism/usdt/deploy.ts @@ -0,0 +1,71 @@ +import { + Deployed, + DeploymentManager, +} from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +const HOUR = 60 * 60; +const DAY = 24 * HOUR; + +export default async function deploy( + deploymentManager: DeploymentManager, + deploySpec: DeploySpec +): Promise { + const deployed = await deployContracts(deploymentManager, deploySpec); + return deployed; +} + +async function deployContracts( + deploymentManager: DeploymentManager, + deploySpec: DeploySpec +): Promise { + const trace = deploymentManager.tracer(); + + const USDT = await deploymentManager.existing( + 'USDT', + '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', + 'optimism' + ); + const WETH = await deploymentManager.existing( + 'WETH', + '0x4200000000000000000000000000000000000006', + 'optimism' + ); + const WBTC = await deploymentManager.existing( + 'WBTC', + '0x68f180fcCe6836688e9084f035309E29Bf0A2095', + 'optimism' + ); + const OP = await deploymentManager.existing( + 'OP', + '0x4200000000000000000000000000000000000042', + 'optimism' + ); + + const COMP = await deploymentManager.existing( + 'COMP', + '0x7e7d4467112689329f7E06571eD0E8CbAd4910eE', + 'optimism' + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'optimism', 'usdc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'optimism', 'usdc'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'optimism', 'usdc'); + const configurator = await deploymentManager.fromDep('configurator', 'optimism', 'usdc'); + const rewards = await deploymentManager.fromDep('rewards', 'optimism', 'usdc'); + const bulker = await deploymentManager.fromDep('bulker', 'optimism', 'usdc'); + const localTimelock = await deploymentManager.fromDep('timelock', 'optimism', 'usdc'); + const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'optimism', 'usdc'); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bridgeReceiver, + bulker, + rewards, + COMP + }; +} diff --git a/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts b/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts new file mode 100644 index 000000000..d457597e7 --- /dev/null +++ b/deployments/optimism/usdt/migrations/1713012100_configurate_and_ens.ts @@ -0,0 +1,316 @@ +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { Contract, ethers } from 'ethers'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState'; +import { + calldata, + 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 opCOMPAddress = '0x7e7d4467112689329f7E06571eD0E8CbAd4910eE'; +const USDTAmountToBridge = exp(10_000, 6); +const cUSDTAddress = '0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9'; +const mainnetUSDTAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'; + +export default migration('1713012100_configurate_and_ens', { + prepare: async (deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager + ) => { + const trace = deploymentManager.tracer(); + const { utils } = ethers; + + const cometFactory = await deploymentManager.fromDep( + 'cometFactory', + 'optimism', + 'usdc' + ); + const { + bridgeReceiver, + timelock: localTimelock, + comet, + cometAdmin, + configurator, + rewards, + USDT + } = await deploymentManager.getContracts(); + + const { + opL1CrossDomainMessenger, + opL1StandardBridge, + governor, + timelock + } = await govDeploymentManager.getContracts(); + + // ENS Setup + // See also: https://docs.ens.domains/contract-api-reference/name-processing + const ENSResolver = await govDeploymentManager.existing( + 'ENSResolver', + ENSResolverAddress + ); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const opChainId = ( + await deploymentManager.hre.ethers.provider.getNetwork() + ).chainId.toString(); + const newMarketObject = { baseSymbol: 'USDT', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse( + await ENSResolver.text(subdomainHash, ENSTextRecordKey) + ); + if (officialMarketsJSON[opChainId]) { + officialMarketsJSON[opChainId].push(newMarketObject); + } else { + officialMarketsJSON[opChainId] = [newMarketObject]; + } + + const configuration = await getConfigurationStruct(deploymentManager); + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory( + comet.address, + cometFactory.address + ) + ); + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration( + comet.address, + configuration + ) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, opCOMPAddress] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + configurator.address, + cometAdmin.address, + rewards.address, + ], + [0, 0, 0, 0], + [ + 'setFactory(address,address)', + '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)[]))', + 'deployAndUpgradeTo(address,address)', + 'setRewardConfig(address,address)', + ], + [ + setFactoryCalldata, + setConfigurationCalldata, + deployAndUpgradeToCalldata, + setRewardConfigCalldata, + ], + ] + ); + + const USDTMainnet = new Contract( + mainnetUSDTAddress, + [ + 'function balanceOf(address account) external view returns (uint256)', + 'function approve(address,uint256) external' + ], + govDeploymentManager.hre.ethers.provider + ); + + const notEnoughUSDT = (await USDTMainnet.balanceOf(timelock.address)).lt(USDTAmountToBridge); + const amountToSupply = notEnoughUSDT ? ethers.BigNumber.from(USDTAmountToBridge).sub(await USDTMainnet.balanceOf(timelock.address)) : 0; + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [amountToSupply] + ); + + // COMP speeds for rewards are setted, but the bridge of tokens does not happen, because it was bridged in the USDC market proposal + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet, set Reward Config on Optimism + { + contract: opL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 3_000_000], + }, + // 2. Get USDT reserves from cUSDT contract + { + target: cUSDTAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 3. Approve USDT to L1StandardBridge + { + contract: USDTMainnet, + signature: 'approve(address,uint256)', + args: [opL1StandardBridge.address, USDTAmountToBridge], + }, + // 4. Bridge USDT from Ethereum to OP Comet using L1StandardBridge + { + contract: opL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + 'depositERC20To(address,address,address,uint256,uint32,bytes)', + args: [ + USDTMainnet.address, + USDT.address, + comet.address, + USDTAmountToBridge, + 200_000, + '0x', + ], + }, + // 5. 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)] + ), + }, + ]; + + // the description has speeds. speeds will be set up on on-chain proposal + const description = "# Initialize cUSDTv3 on Optimism\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes the deployment of Compound III to the Optimism network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDT market on Optimism; upon execution, cUSDTv3 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 on the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975/6).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/848) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-optimism/4975).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration and deploys a new Comet implementation on Optimism. This sends the encoded `setFactory`, `setConfiguration` and `deployAndUpgradeTo` calls across the bridge to the governance receiver on Optimism. It also calls `setRewardConfig` on the Optimism rewards contract, to establish Optimism’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 5 COMP/day and borrow speed to be 5 COMP/day.\n\nThe second action reduces Compound [cUSDT](https://etherscan.io/address/0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9) reserves to Timelock, in order to seed the market reserves through the Optimism L1StandardBridge.\n\nThe third action approves Optimism’s [L1StandardBridge](https://etherscan.io/address/0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1) to take Timelock's USDT, in order to seed the reserves through the bridge.\n\nThe fourth action deposits 10K USDT from mainnet to the Optimism L1StandardBridge contract to bridge to Comet.\n\nThe fifth 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 Optimism cUSDTv3 market"; + const txn = await govDeploymentManager.retry(async () =>{ + return 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 false; + }, + + async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) { + const ethers = deploymentManager.hre.ethers; + + const { + comet, + rewards, + WBTC, + WETH, + OP, + COMP + } = await deploymentManager.getContracts(); + + const { + timelock + } = await govDeploymentManager.getContracts(); + + const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber); + + // uncomment on on-chain proposal PR + // expect(stateChanges).to.deep.equal({ + // WBTC: { + // supplyCap: exp(400, 8) + // }, + // WETH: { + // supplyCap: exp(11_000, 18) + // }, + // OP: { + // supplyCap: exp(10_000_000, 18) + // }, + // baseTrackingSupplySpeed: exp(5 / 86400, 15, 18), + // baseTrackingBorrowSpeed: exp(5 / 86400, 15, 18), + // }); + + 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); + + // 2. & 3 & 4. + expect(await comet.getReserves()).to.be.equal(USDTAmountToBridge); + + // 5. + const ENSResolver = await govDeploymentManager.existing( + "ENSResolver", + ENSResolverAddress + ); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text( + subdomainHash, + ENSTextRecordKey + ); + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: comet.address, + }, + ], + }); + } +}); \ No newline at end of file diff --git a/deployments/optimism/usdt/relations.ts b/deployments/optimism/usdt/relations.ts new file mode 100644 index 000000000..281057307 --- /dev/null +++ b/deployments/optimism/usdt/relations.ts @@ -0,0 +1,30 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: + 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver', + }, + Proxy: { + artifact: 'contracts/ERC20.sol:ERC20', + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + l2StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, +}; \ No newline at end of file diff --git a/deployments/optimism/usdt/roots.json b/deployments/optimism/usdt/roots.json new file mode 100644 index 000000000..1b7b5cb67 --- /dev/null +++ b/deployments/optimism/usdt/roots.json @@ -0,0 +1,11 @@ +{ + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "CCTPMessageTransmitter": "0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8", + "comet": "0x995E394b8B2437aC8Ce61Ee0bC610D617962B214", + "configurator": "0x84E93EC6170ED630f5ebD89A1AAE72d4F63f2713", + "rewards": "0x443EA0340cb75a160F31A440722dec7b5bc3C2E9", + "bridgeReceiver": "0xC3a73A70d1577CD5B02da0bA91C0Afc8fA434DAF", + "bulker": "0xcb3643CC8294B23171272845473dEc49739d4Ba3", + "COMP": "0x7e7d4467112689329f7E06571eD0E8CbAd4910eE" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 284c8cb5c..a78992283 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -37,6 +37,7 @@ import baseGoerliRelationConfigMap from './deployments/base-goerli/usdc/relation import baseGoerliWethRelationConfigMap from './deployments/base-goerli/weth/relations'; import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relations'; import optimismRelationConfigMap from './deployments/optimism/usdc/relations'; +import optimismUsdtRelationConfigMap from './deployments/optimism/usdt/relations'; import scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; @@ -372,7 +373,8 @@ const config: HardhatUserConfig = { usdc: lineaGoerliRelationConfigMap }, optimism: { - usdc: optimismRelationConfigMap + usdc: optimismRelationConfigMap, + usdt: optimismUsdtRelationConfigMap }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap @@ -504,6 +506,12 @@ const config: HardhatUserConfig = { deployment: 'usdc', auxiliaryBase: 'mainnet' }, + { + name: 'optimism-usdt', + network: 'optimism', + deployment: 'usdt', + auxiliaryBase: 'mainnet', + }, { name: 'scroll-goerli', network: 'scroll-goerli', diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index 91e2608f8..a73d84d2c 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -165,6 +165,7 @@ scenario( filter: async (ctx) => await isBulkerSupported(ctx) && await isRewardSupported(ctx) && !matchesDeployment(ctx, [{deployment: 'weth'}, { network: 'linea-goerli' }]), supplyCaps: { $asset0: 3000, + $asset1: 3000, }, tokenBalances: { albert: { $base: '== 1000000', $asset0: 3000 }, @@ -177,7 +178,10 @@ scenario( const baseAssetAddress = await comet.baseToken(); const baseAsset = context.getAssetByAddress(baseAssetAddress); const baseScale = (await comet.baseScale()).toBigInt(); - const { asset: collateralAssetAddress, scale: scaleBN } = await comet.getAssetInfo(0); + // 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); From 854ec58acc56e000f7e201cc16c2aa363dde904c Mon Sep 17 00:00:00 2001 From: Dmitriy Babenko <159453675+dmitriy-woof-software@users.noreply.github.com> Date: Mon, 24 Jun 2024 21:53:32 +0200 Subject: [PATCH 5/5] On-chain Compound v3 WETH Arbitrum market (#860) Co-authored-by: Mikhailo Shabodyash <67977488+EviLord032@users.noreply.github.com> Co-authored-by: GitHub Actions Bot <> Co-authored-by: Mikhailo Shabodyash --- .github/workflows/run-scenarios.yaml | 2 +- deployments/arbitrum/weth/configuration.json | 54 ++++ deployments/arbitrum/weth/deploy.ts | 71 +++++ .../1716912328_configure_and_ens.ts | 296 ++++++++++++++++++ deployments/arbitrum/weth/relations.ts | 53 ++++ deployments/arbitrum/weth/roots.json | 8 + hardhat.config.ts | 12 +- scenario/utils/relayArbitrumMessage.ts | 20 ++ .../liquidateUnderwaterBorrowers.ts | 35 ++- src/deploy/index.ts | 14 +- 10 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 deployments/arbitrum/weth/configuration.json create mode 100644 deployments/arbitrum/weth/deploy.ts create mode 100644 deployments/arbitrum/weth/migrations/1716912328_configure_and_ens.ts create mode 100644 deployments/arbitrum/weth/relations.ts create mode 100644 deployments/arbitrum/weth/roots.json diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 9c2990382..0d9805358 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, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/arbitrum/weth/configuration.json b/deployments/arbitrum/weth/configuration.json new file mode 100644 index 000000000..aacebfc58 --- /dev/null +++ b/deployments/arbitrum/weth/configuration.json @@ -0,0 +1,54 @@ +{ + "name": "Compound WETH", + "symbol": "cWETHv3", + "baseToken": "WETH", + "baseTokenAddress": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + "borrowMin": "0.1e18", + "governor": "0x3fB4d38ea7EC20D91917c09591490Eeda38Cf88A", + "pauseGuardian": "0x78E6317DD6D43DdbDa00Dce32C2CbaFc99361a9d", + "rewardTokenAddress": "0x354A6dA3fcde098F8389cad84b0182725c6C91dE", + "storeFrontPriceFactor": 0.7, + "targetReserves": "5000e18", + "rates": { + "supplyKink": 0.85, + "supplySlopeLow": 0.0185, + "supplySlopeHigh": 1, + "supplyBase": 0, + "borrowKink": 0.85, + "borrowSlopeLow": 0.014, + "borrowSlopeHigh": 1.15, + "borrowBase": 0.01 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "69444444444e0", + "baseBorrowSpeed": "46296296296e0", + "baseMinForRewards": "1000e18" + }, + "assets": { + "weETH": { + "address": "0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe", + "decimals": "18", + "borrowCF": 0.82, + "liquidateCF": 0.87, + "liquidationFactor": 0.92, + "supplyCap": "550e18" + }, + "rETH": { + "address": "0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8", + "decimals": "18", + "borrowCF": 0.90, + "liquidateCF": 0.93, + "liquidationFactor": 0.97, + "supplyCap": "800e18" + }, + "wstETH": { + "address": "0x5979D7b546E38E414F7E9822514be443A4800529", + "decimals": "18", + "borrowCF": 0.88, + "liquidateCF": 0.93, + "liquidationFactor": 0.97, + "supplyCap": "2000e18" + } + } +} \ No newline at end of file diff --git a/deployments/arbitrum/weth/deploy.ts b/deployments/arbitrum/weth/deploy.ts new file mode 100644 index 000000000..661241d30 --- /dev/null +++ b/deployments/arbitrum/weth/deploy.ts @@ -0,0 +1,71 @@ +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 WETH = await deploymentManager.existing('WETH', '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', 'arbitrum'); + const rETH = await deploymentManager.existing('rETH', '0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8', 'arbitrum'); + const wstETH = await deploymentManager.existing('wstETH', '0x5979D7b546E38E414F7E9822514be443A4800529', 'arbitrum'); + const COMP = await deploymentManager.existing('COMP', '0x354A6dA3fcde098F8389cad84b0182725c6C91dE', 'arbitrum'); + + // Deploy WstETHPriceFeed + const wstETHPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0xb523AE262D20A936BC152e6023996e46FDC2A95D', // wstETH / ETH price feed + 8 // decimals + ] + ); + + // Deploy constant price feed for WETH + const wethConstantPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ConstantPriceFeed.sol', + [ + 8, // decimals + exp(1, 8) // constantPrice + ] + ); + + // Deploy scaling price feed for rETH + const rETHScalingPriceFeed = await deploymentManager.deploy( + 'rETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0xD6aB2298946840262FcC278fF31516D39fF611eF', // rETH / ETH price feed + 8 // decimals + ] + ); + + // Deploy scaling price feed for weETH + const weETHScalingPriceFeed = await deploymentManager.deploy( + 'weETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0xE141425bc1594b8039De6390db1cDaf4397EA22b', // weETH / ETH price feed + 8 // decimals + ] + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'arbitrum', 'usdc.e'); + const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum', 'usdc.e'); + const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'arbitrum', 'usdc.e'); + const configurator = await deploymentManager.fromDep('configurator', 'arbitrum', 'usdc.e'); + const rewards = await deploymentManager.fromDep('rewards', 'arbitrum', 'usdc.e'); + const bulker = await deploymentManager.fromDep('bulker', 'arbitrum', 'usdc.e'); + const localTimelock = await deploymentManager.fromDep('timelock', 'arbitrum', 'usdc.e'); + const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'arbitrum', 'usdc.e'); + + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + + return { + ...deployed, + bridgeReceiver, + bulker, + rewards, + COMP + }; +} \ No newline at end of file diff --git a/deployments/arbitrum/weth/migrations/1716912328_configure_and_ens.ts b/deployments/arbitrum/weth/migrations/1716912328_configure_and_ens.ts new file mode 100644 index 000000000..57c70b163 --- /dev/null +++ b/deployments/arbitrum/weth/migrations/1716912328_configure_and_ens.ts @@ -0,0 +1,296 @@ +import { Contract, ethers } from 'ethers'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy'; +import { expect } from 'chai'; +import { applyL1ToL2Alias, estimateL2Transaction, estimateTokenBridge } from '../../../../scenario/utils/arbitrumUtils'; + +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 WETHAmountToBridge = ethers.BigNumber.from(exp(10, 18)); +const arbitrumCOMPAddress = '0x354A6dA3fcde098F8389cad84b0182725c6C91dE'; + +export default migration('1713517203_configurate_and_ens', { + prepare: async (_deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + const { utils } = ethers; + + const cometFactory = await deploymentManager.fromDep('cometFactory', 'arbitrum', 'usdc.e'); + const { + bridgeReceiver, + timelock: l2Timelock, + comet, + cometAdmin, + configurator, + rewards + } = await deploymentManager.getContracts(); + const { + arbitrumInbox, + arbitrumL1GatewayRouter, + timelock, + governor, + WETH, + COMP + } = await govDeploymentManager.getContracts(); + const refundAddress = l2Timelock.address; + const wethGatewayAddress = await arbitrumL1GatewayRouter.getGateway(WETH.address); + + const wethGasParams = await estimateTokenBridge( + { + token: COMP.address, + from: timelock.address, + to: comet.address, + amount: WETHAmountToBridge.toBigInt() + }, + govDeploymentManager, + deploymentManager + ); + + const configuration = await getConfigurationStruct(deploymentManager); + const setFactoryCalldata = await calldata( + configurator.populateTransaction.setFactory(comet.address, cometFactory.address) + ); + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration(comet.address, configuration) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, arbitrumCOMPAddress] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [configurator.address, configurator.address, cometAdmin.address, rewards.address], + [0, 0, 0, 0], + [ + 'setFactory(address,address)', + '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)[]))', + 'deployAndUpgradeTo(address,address)', + 'setRewardConfig(address,address)' + ], + [ + setFactoryCalldata, + setConfigurationCalldata, + deployAndUpgradeToCalldata, + setRewardConfigCalldata + ] + ] + ); + + const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const polygonChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString(); + const newMarketObject = { baseSymbol: 'WETH', cometAddress: comet.address }; + const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey)); + + if (officialMarketsJSON[polygonChainId]) { + officialMarketsJSON[polygonChainId].push(newMarketObject); + } else { + officialMarketsJSON[polygonChainId] = [newMarketObject]; + } + + const createRetryableTicketGasParams = await estimateL2Transaction( + { + from: applyL1ToL2Alias(timelock.address), + to: bridgeReceiver.address, + data: l2ProposalData + }, + deploymentManager + ); + + const outboundTransferCalldata = utils.defaultAbiCoder.encode( + ['address', 'address', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + WETH.address, + comet.address, + WETHAmountToBridge.toBigInt(), + wethGasParams.gasLimit, + wethGasParams.maxFeePerGas, + utils.defaultAbiCoder.encode( + ['uint256', 'bytes'], + [wethGasParams.maxSubmissionCost, '0x'] + ) + ] + ); + + const mainnetActions = [ + // 1. Set Comet configuration and deployAndUpgradeTo new 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 + }, + // 2. Wrap some ETH as WETH + { + contract: WETH, + signature: 'deposit()', + args: [], + value: WETHAmountToBridge, + }, + // 3. Approve the WETH gateway to take Timelock's WETH for bridging + { + contract: WETH, + signature: 'approve(address,uint256)', + args: [wethGatewayAddress, WETHAmountToBridge] + }, + // 4. Bridge WETH from mainnet to Arbitrum Comet + { + target: arbitrumL1GatewayRouter.address, + signature: 'outboundTransfer(address,address,uint256,uint256,uint256,bytes)', + calldata: outboundTransferCalldata, + value: wethGasParams.deposit + }, + // 5. 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 cWETHv3 on Arbitrum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes deployment of Compound III to the Arbitrum network. This proposal takes the governance steps recommended and necessary to initialize a Compound III WETH market on Arbitrum; upon execution, cWETHv3 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-eth-market-on-arbitrum/5252/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/860), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/9419069237/job/25948008428) and [forum discussion](https://www.comp.xyz/t/add-eth-market-on-arbitrum/5252).\n\nThe market migration to create a new WETH Comet market on Arbitrum also includes a new collateral asset weETH which has not been used in any other Compound market previously. In prior analysis of weETH as a collateral asset, Gauntlet identified oracle risks which [could expose the protocol to exaggerated market movements](https://www.comp.xyz/t/add-weeth-market-on-ethereum/5179/3#oracle-risk-7) and a yield risk which could [cause yield shocks and consequentially elevate slippage magnitude and liquidity on DEXs](https://www.comp.xyz/t/add-weeth-market-on-ethereum/5179/3#yield-risk-8).\n\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration and deploys a new Comet implementation on Arbitrum. This sends the encoded `setFactory`, `setConfiguration`, `deployAndUpgradeTo` calls across the bridge to the governance receiver on Arbitrum. It also calls `setRewardConfig` on the Arbitrum rewards contract, to establish Artitrum’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 6 COMP/day and borrow speed to be 4 COMP/day.\n\nThe second action wraps ETH as WETH so it can be then transferred.\n\nThe third action approves (ArbitrumL1GatewayRouter) [TokenMessenger](https://etherscan.io/address/0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef) to take the Timelock's WETH on Mainnet, in order to seed the market reserves through the arbitrumL1GatewayRouter.\n\nThe fourth action bridges WETH from mainnet via ‘outboundTransfer’ function on ArbitrumL1GatewayRouter’s contract to mint native WETH to Comet on Arbitrum.\n\nThe fifth 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 Arbitrum cWETHv3 market.\n"; + const txn = await govDeploymentManager.retry(async () => + trace(await governor.propose(...(await proposal(mainnetActions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return true; + }, + + async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager) { + const ethers = deploymentManager.hre.ethers; + + const { + comet, + rewards, + wstETH, + rETH, + weETH + } = await deploymentManager.getContracts(); + + const { + timelock + } = await govDeploymentManager.getContracts(); + + // 1. + const weETHInfo = await comet.getAssetInfoByAddress(weETH.address); + const rETHInfo = await comet.getAssetInfoByAddress(rETH.address); + const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); + expect(weETHInfo.supplyCap).to.be.eq(exp(550, 18)); + expect(wstETHInfo.supplyCap).to.be.eq(exp(2000, 18)); + expect(rETHInfo.supplyCap).to.be.eq(exp(800, 18)); + + expect(await comet.pauseGuardian()).to.be.eq('0x78E6317DD6D43DdbDa00Dce32C2CbaFc99361a9d'); + + // 2. & 3. & 4. + expect(await comet.getReserves()).to.be.equal(WETHAmountToBridge); + + // 5. + const ENSResolver = await govDeploymentManager.existing('ENSResolver', ENSResolverAddress); + const ENSRegistry = await govDeploymentManager.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', + }, + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + ], + 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: comet.address, + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + ], + }); + + // 8. + expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(6 / 86400, 15, 18)); + expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(4 / 86400, 15, 18)); + } +}); \ No newline at end of file diff --git a/deployments/arbitrum/weth/relations.ts b/deployments/arbitrum/weth/relations.ts new file mode 100644 index 000000000..b42cc0bf2 --- /dev/null +++ b/deployments/arbitrum/weth/relations.ts @@ -0,0 +1,53 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + 'governor': { + artifact: 'contracts/bridges/polygon/PolygonBridgeReceiver.sol:PolygonBridgeReceiver', + }, + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + OssifiableProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + ClonableBeaconProxy: { + artifact: 'contracts/ERC20.sol:ERC20' + }, + // WETH + '0x82af49447d8a07e3bd95bd0d56f35241523fbab1': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // rETH + '0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // wstETH + '0x5979D7b546E38E414F7E9822514be443A4800529': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, + // weETH + '0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + } +}; \ No newline at end of file diff --git a/deployments/arbitrum/weth/roots.json b/deployments/arbitrum/weth/roots.json new file mode 100644 index 000000000..b82141555 --- /dev/null +++ b/deployments/arbitrum/weth/roots.json @@ -0,0 +1,8 @@ +{ + "comet": "0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486", + "configurator": "0xb21b06D71c75973babdE35b49fFDAc3F82Ad3775", + "rewards": "0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae", + "bridgeReceiver": "0x42480C37B249e33aABaf4c22B20235656bd38068", + "bulker": "0xbdE8F31D2DdDA895264e27DD990faB3DC87b372d", + "COMP": "0x354A6dA3fcde098F8389cad84b0182725c6C91dE" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index a78992283..02b603d6e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -28,6 +28,7 @@ import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; import arbitrumNativeUsdcRelationConfigMap from './deployments/arbitrum/usdc/relations'; +import arbitrumWETHRelationConfigMap from './deployments/arbitrum/weth/relations'; import arbitrumBridgedUsdcGoerliRelationConfigMap from './deployments/arbitrum-goerli/usdc.e/relations'; import arbitrumGoerliNativeUsdcRelationConfigMap from './deployments/arbitrum-goerli/usdc/relations'; import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations'; @@ -354,7 +355,8 @@ const config: HardhatUserConfig = { }, arbitrum: { 'usdc.e': arbitrumBridgedUsdcRelationConfigMap, - usdc: arbitrumNativeUsdcRelationConfigMap + usdc: arbitrumNativeUsdcRelationConfigMap, + weth: arbitrumWETHRelationConfigMap }, 'arbitrum-goerli': { 'usdc.e': arbitrumBridgedUsdcGoerliRelationConfigMap, @@ -452,6 +454,12 @@ const config: HardhatUserConfig = { deployment: 'usdc', auxiliaryBase: 'mainnet' }, + { + name: 'arbitrum-weth', + network: 'arbitrum', + deployment: 'weth', + auxiliaryBase: 'mainnet' + }, { name: 'arbitrum-goerli-usdc.e', network: 'arbitrum-goerli', @@ -559,4 +567,4 @@ const config: HardhatUserConfig = { setupDefaultNetworkProviders(config); -export default config; +export default config; \ No newline at end of file diff --git a/scenario/utils/relayArbitrumMessage.ts b/scenario/utils/relayArbitrumMessage.ts index 4929b9533..287ec7b5d 100644 --- a/scenario/utils/relayArbitrumMessage.ts +++ b/scenario/utils/relayArbitrumMessage.ts @@ -3,6 +3,7 @@ import { impersonateAddress } from '../../plugins/scenario/utils'; import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; import { utils, BigNumber } from 'ethers'; import { Log } from '@ethersproject/abstract-provider'; +import { sourceTokens } from '../../plugins/scenario/utils/TokenSourcer'; export async function relayArbitrumMessage( governanceDeploymentManager: DeploymentManager, @@ -81,6 +82,25 @@ export async function relayArbitrumMessage( bridgeDeploymentManager, sender ); + // if method name == finalizeInboundTransfer(address,address,address,uint256,bytes) + if(data.slice(0, 10) == '0x2e567b36'){ + const _data = '0x' + data.slice(10, 266); + const [token,, to, amount] = utils.defaultAbiCoder.decode( + ['address', 'address', 'address', 'uint256'], + _data + ); + // if token is mainnet ETH -> than source arbitrum weth + if(token == '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'){ + await sourceTokens({ + dm: bridgeDeploymentManager, + amount: amount, + asset: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', + address: to, + blacklist: [], + }); + continue; + } + } const transactionRequest = await arbitrumSigner.populateTransaction({ to: toAddress, from: sender, diff --git a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts index d073fb587..577bf2fef 100644 --- a/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts +++ b/scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts @@ -57,7 +57,10 @@ const addresses = { WBTC: '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f', USDT: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', USDC: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', - USDC_E: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8' + USDC_E: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', + rETH: '0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8', + wstETH: '0x5979D7b546E38E414F7E9822514be443A4800529', + weETH: '0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe' } }; @@ -110,6 +113,10 @@ export const flashLoanPools = { tokenAddress: addresses.arbitrum.USDC_E, poolFee: 100 }, + weth: { + tokenAddress: addresses.arbitrum.USDC, + poolFee: 500 + } } }; @@ -233,6 +240,28 @@ export function getPoolConfig(tokenAddress: string) { uniswapPoolFee: 500 } }, + [addresses.arbitrum.rETH.toLowerCase()]: { + ...defaultPoolConfig, + ...{ + exchange: Exchange.Balancer, + balancerPoolId: '0xd0ec47c54ca5e20aaae4616c25c825c7f48d40690000000000000000000004ef' + } + }, + [addresses.arbitrum.wstETH.toLowerCase()]: { + ...defaultPoolConfig, + ...{ + exchange: Exchange.Balancer, + balancerPoolId: '0x9791d590788598535278552eecd4b211bfc790cb000000000000000000000498' + } + }, + [addresses.arbitrum.weETH.toLowerCase()]: { + ...defaultPoolConfig, + ...{ + exchange: Exchange.Uniswap, + swapViaWeth: false, + uniswapPoolFee: 100 + } + }, }; const poolConfig = poolConfigs[tokenAddress.toLowerCase()]; @@ -260,7 +289,9 @@ function getMaxAmountToPurchase(tokenAddress: string): bigint { [addresses.arbitrum.ARB.toLowerCase()]: exp(500000, 18), [addresses.arbitrum.GMX.toLowerCase()]: exp(4000, 18), [addresses.arbitrum.WETH.toLowerCase()]: exp(2000, 18), - [addresses.arbitrum.WBTC.toLowerCase()]: exp(100, 8) + [addresses.arbitrum.WBTC.toLowerCase()]: exp(100, 8), + [addresses.arbitrum.rETH.toLowerCase()]: exp(2000, 18), + [addresses.arbitrum.wstETH.toLowerCase()]: exp(2000, 18) }; const max = maxAmountsToPurchase[tokenAddress.toLowerCase()]; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 209bfff19..f912c38bf 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -96,9 +96,21 @@ export const WHALES = { '0x167384319b41f7094e62f7506409eb38079abff8' // WMATIC whale ], arbitrum: [ + '0x8eb270e296023e9d92081fdf967ddd7878724424', // rETH whale + '0x78e88887d80451cb08fdc4b9046c9d01fb8d048d', // rETH whale + '0xc0cf4b266be5b3229c49590b59e67a09c15b22f4', // rETH whale + '0x84446698694b348eaece187b55df06ab4ce72b35', // rETH whale + '0x42c248d137512907048021b30d9da17f48b5b7b2', // wstETH whale + '0xc3e5607cd4ca0d5fe51e09b60ed97a0ae6f874dd', // WETH whale '0xf89d7b9c864f589bbf53a82105107622b35eaa40', // USDC whale '0x7b7b957c284c2c227c980d6e2f804311947b84d0', // WBTC whale - '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae', // COMP whale + '0x1c6b5795be43ddff8812b3e577ac752764635bc5', // COMP whale + '0xdead767ba9f8072c986a4619c27ae46bcc226c13', // COMP whale + '0xde5167c19a5286889752cb0f31a1c7f28a99fefb', // COMP whale + '0xdfa19e743421c394d904f5a113121c2227d2364b', // COMP whale + '0xee3273f6d29ddfff08ffd9d513cff314734f01a2', // COMP whale + '0x9e786a8fc88ee74b758b125071d45853356024c3', // COMP whale + '0xd93f76944e870900779c09ddf1c46275f9d8bf9b', // COMP whale '0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9' // native USDC whale ], base: [