diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 7c551a883..32fee3ad8 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, mainnet-usdt, 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] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index 637e34be5..d25e3fbcd 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -15,4 +15,4 @@ "opL1StandardBridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", "scrollMessenger": "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367", "scrollL1USDCGateway": "0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B" -} +} \ No newline at end of file diff --git a/deployments/mainnet/usdt/configuration.json b/deployments/mainnet/usdt/configuration.json new file mode 100644 index 000000000..8d86a6cd7 --- /dev/null +++ b/deployments/mainnet/usdt/configuration.json @@ -0,0 +1,78 @@ +{ + "name": "Compound USDT", + "symbol": "cUSDTv3", + "baseToken": "USDT", + "baseTokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "borrowMin": "100e6", + "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", + "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", + "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": "1000000e6" + }, + "rewardTokenAddress": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "assets": { + "COMP": { + "address": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "decimals": "18", + "borrowCF": 0.65, + "liquidateCF": 0.70, + "liquidationFactor": 0.75, + "supplyCap": "0e18" + }, + "WBTC": { + "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "decimals": "8", + "borrowCF": 0.80, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "0e8" + }, + "WETH": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "decimals": "18", + "borrowCF": 0.83, + "liquidateCF": 0.9, + "liquidationFactor": 0.95, + "supplyCap": "0e18" + }, + "UNI": { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "decimals": "18", + "borrowCF": 0.75, + "liquidateCF": 0.81, + "liquidationFactor": 0.85, + "supplyCap": "0e18" + }, + "LINK": { + "address": "0x514910771af9ca656af840dff83e8264ecf986ca", + "decimals": "18", + "borrowCF": 0.79, + "liquidateCF": 0.85, + "liquidationFactor": 0.93, + "supplyCap": "0e18" + }, + "wstETH": { + "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", + "decimals": "18", + "borrowCF": 0.80, + "liquidateCF": 0.85, + "liquidationFactor": 0.95, + "supplyCap": "0e18" + } + } +} diff --git a/deployments/mainnet/usdt/deploy.ts b/deployments/mainnet/usdt/deploy.ts new file mode 100644 index 000000000..a48250854 --- /dev/null +++ b/deployments/mainnet/usdt/deploy.ts @@ -0,0 +1,109 @@ +import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { + const USDT = await deploymentManager.existing('USDT', '0xdAC17F958D2ee523a2206206994597C13D831ec7'); + const WBTC = await deploymentManager.existing('WBTC', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'); + const WETH = await deploymentManager.existing('WETH', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); + const COMP = await deploymentManager.existing('COMP', '0xc00e94Cb662C3520282E6f5717214004A7f26888'); + const LINK = await deploymentManager.existing('LINK', '0x514910771af9ca656af840dff83e8264ecf986ca'); + const UNI = await deploymentManager.existing('UNI', '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'); + const stETH = await deploymentManager.existing('stETH', '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'); + const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'); + + // Deploy WstETHPriceFeed + const wstETHPriceFeed = await deploymentManager.deploy( + 'wstETH:priceFeed', + 'pricefeeds/WstETHPriceFeed.sol', + [ + '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETHtoUSDPriceFeed + wstETH.address, // wstETH + 8 // decimals + ] + ); + + // deploy scaling price feed for USDT + const usdtScalingPriceFeed = await deploymentManager.deploy( + 'USDT:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x3E7d1eAB13ad0104d2750B8863b489D65364e32D', // USDT / USD price feed + 8 // decimals + ] + ); + + const wbtcScalingPriceFeed = await deploymentManager.deploy( + 'WBTC:priceFeed', + 'pricefeeds/WBTCPriceFeed.sol', + [ + '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // WBTC / BTC price feed + '0xdeb288F737066589598e9214E782fa5A8eD689e8', // BTC / USD price feed + 8 // decimals + ] + ); + + const compScalingPriceFeed = await deploymentManager.deploy( + 'COMP:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5', // COMP / USD price feed + 8 // decimals + ] + ); + + const wethScalingPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', // WETH / USD price feed + 8 // decimals + ] + ); + + const linkScalingPriceFeed = await deploymentManager.deploy( + 'LINK:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c', // LINK / USD price feed + 8 // decimals + ] + ); + + const uniScalingPriceFeed = await deploymentManager.deploy( + 'UNI:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + '0x553303d460EE0afB37EdFf9bE42922D8FF63220e', // UNI / USD price feed + 8 // decimals + ] + ); + + // Import shared contracts from cUSDCv3 + const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc'); + const cometFactory = await deploymentManager.fromDep('cometFactory', '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'); + + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + const { comet } = deployed; + + // Deploy Bulker + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/MainnetBulker.sol', + [ + await comet.governor(), // admin_ + WETH.address, // weth_ + wstETH.address // wsteth_ + ] + ); + return { + ...deployed, + bulker, + rewards, + COMP + }; +} \ No newline at end of file diff --git a/deployments/mainnet/usdt/migrations/1717664323_configurate_and_ens.ts b/deployments/mainnet/usdt/migrations/1717664323_configurate_and_ens.ts new file mode 100644 index 000000000..d654baa29 --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1717664323_configurate_and_ens.ts @@ -0,0 +1,227 @@ +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 cUSDTAddress = '0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9'; +const USDTAmount = ethers.BigNumber.from(exp(500_000, 6)); + +export default migration('1713517203_configurate_and_ens', { + prepare: async (_deploymentManager: DeploymentManager) => { + return {}; + }, + + enact: async (deploymentManager: DeploymentManager) => { + const trace = deploymentManager.tracer(); + const ethers = deploymentManager.hre.ethers; + + const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdc'); + const { + comet, + cometAdmin, + configurator, + rewards, + COMP, + USDT, + governor + } = await deploymentManager.getContracts(); + + const configuration = await getConfigurationStruct(deploymentManager); + + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const currentChainId = (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[currentChainId]) { + officialMarketsJSON[currentChainId].push(newMarketObject); + } else { + officialMarketsJSON[currentChainId] = [newMarketObject]; + } + + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [USDTAmount] + ); + + 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], + }, + // 2. Get USDT reserves from cUSDT contract + { + target: cUSDTAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 6. Transfer USDT to the Comet contract + { + contract: USDT, + signature: 'transfer(address,uint256)', + args: [comet.address, USDTAmount], + }, + // 7. Update the list of official markets + { + target: ENSResolverAddress, + signature: 'setText(bytes32,string,string)', + calldata: ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'string', 'string'], + [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] + ) + } + ]; + + const description = ' DESCRIPTION '; + const txn = await deploymentManager.retry( + async () => trace((await governor.propose(...await proposal(actions, description)))) + ); + + const event = txn.events.find(event => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const ethers = deploymentManager.hre.ethers; + + const { + comet, + timelock, + COMP, + WBTC, + WETH, + UNI, + LINK, + wstETH + } = await deploymentManager.getContracts(); + + + // 1. + const compInfo = await comet.getAssetInfoByAddress(COMP.address); + const wbtcInfo = await comet.getAssetInfoByAddress(WBTC.address); + const wethInfo = await comet.getAssetInfoByAddress(WETH.address); + const uniInfo = await comet.getAssetInfoByAddress(UNI.address); + const linkInfo = await comet.getAssetInfoByAddress(LINK.address); + const wstETHInfo = await comet.getAssetInfoByAddress(wstETH.address); + + // expect(compInfo.supplyCap).to.be.eq(exp(100000, 18)); + // expect(wbtcInfo.borrowCap).to.be.eq(exp(18000, 8)); + // expect(wethInfo.borrowCap).to.be.eq(exp(500000, 18)); + // expect(uniInfo.supplyCap).to.be.eq(exp(3000000, 18)); + // expect(linkInfo.supplyCap).to.be.eq(exp(2000000, 18)); + // expect(wstETHInfo.supplyCap).to.be.eq(exp(9000, 18)); + + expect((await comet.pauseGuardian()).toLowerCase()).to.be.eq('0xbbf3f1421d886e9b2c5d716b5192ac998af2012c'); + + // 2. & 3. & 4. + expect(await comet.getReserves()).to.be.equal(USDTAmount); + + // 5. + const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress); + const ENSRegistry = await deploymentManager.existing('ENSRegistry', ENSRegistryAddress); + const subdomainHash = ethers.utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text(subdomainHash, ENSTextRecordKey); + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(await ENSRegistry.recordExists(subdomainHash)).to.be.equal(true); + expect(await ENSRegistry.owner(subdomainHash)).to.be.equal(timelock.address); + expect(await ENSRegistry.resolver(subdomainHash)).to.be.equal(ENSResolverAddress); + expect(await ENSRegistry.ttl(subdomainHash)).to.be.equal(0); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: comet.address, + }, + ], + 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', + }, + ], + }); + + // 8. + // expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(70 / 86400, 15, 18)); + // expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(50 / 86400, 15, 18)); + } +}); \ No newline at end of file diff --git a/deployments/mainnet/usdt/relations.ts b/deployments/mainnet/usdt/relations.ts new file mode 100644 index 000000000..a81e1fd7a --- /dev/null +++ b/deployments/mainnet/usdt/relations.ts @@ -0,0 +1,20 @@ +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() + } + } + }, + 'USDT': { + artifact: 'contracts/test/NonStandardToken.sol:NonStandardToken', + }, + 'AppProxyUpgradeable': { + artifact: 'contracts/ERC20.sol:ERC20', + } +}; \ No newline at end of file diff --git a/deployments/mainnet/usdt/roots.json b/deployments/mainnet/usdt/roots.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/deployments/mainnet/usdt/roots.json @@ -0,0 +1 @@ +{} diff --git a/hardhat.config.ts b/hardhat.config.ts index 284c8cb5c..2cd344725 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -25,6 +25,7 @@ import sepoliaWethRelationConfigMap from './deployments/sepolia/weth/relations'; import mumbaiRelationConfigMap from './deployments/mumbai/usdc/relations'; import mainnetRelationConfigMap from './deployments/mainnet/usdc/relations'; import mainnetWethRelationConfigMap from './deployments/mainnet/weth/relations'; +import mainnetUsdtRelationConfigMap from './deployments/mainnet/usdt/relations'; import polygonRelationConfigMap from './deployments/polygon/usdc/relations'; import arbitrumBridgedUsdcRelationConfigMap from './deployments/arbitrum/usdc.e/relations'; import arbitrumNativeUsdcRelationConfigMap from './deployments/arbitrum/usdc/relations'; @@ -346,7 +347,8 @@ const config: HardhatUserConfig = { }, mainnet: { usdc: mainnetRelationConfigMap, - weth: mainnetWethRelationConfigMap + weth: mainnetWethRelationConfigMap, + usdt: mainnetUsdtRelationConfigMap }, polygon: { usdc: polygonRelationConfigMap @@ -396,6 +398,11 @@ const config: HardhatUserConfig = { network: 'mainnet', deployment: 'weth', }, + { + name: 'mainnet-usdt', + network: 'mainnet', + deployment: 'usdt' + }, { name: 'development', network: 'hardhat', diff --git a/scenario/ApproveThisScenario.ts b/scenario/ApproveThisScenario.ts index 2c4dc184c..afc8d895e 100644 --- a/scenario/ApproveThisScenario.ts +++ b/scenario/ApproveThisScenario.ts @@ -17,19 +17,27 @@ scenario('Comet#approveThis > allows governor to authorize and rescind authoriza expect(await comet.isAllowed(comet.address, timelock.address)).to.be.false; }); -scenario('Comet#approveThis > allows governor to authorize and rescind authorization for non-Comet ERC20', {}, async ({ comet, timelock, actors }, context) => { +scenario.only('Comet#approveThis > allows governor to authorize and rescind authorization for non-Comet ERC20', {}, async ({ comet, timelock, actors }, context) => { const { admin } = actors; const baseTokenAddress = await comet.baseToken(); const baseToken = context.getAssetByAddress(baseTokenAddress); + console.log('baseToken', baseTokenAddress); const newAllowance = 999_888n; await context.setNextBaseFeeToZero(); - await admin.approveThis(timelock.address, baseTokenAddress, newAllowance, { gasPrice: 0 }); + console.log(admin.address, timelock.address); + console.log('governor', await comet.governor()); + const usdt = context.getAssetByAddress('0xdAC17F958D2ee523a2206206994597C13D831ec7'); + console.log('allowance', await usdt.allowance(comet.address, timelock.address)); + await admin.approveThis(timelock.address, '0xdAC17F958D2ee523a2206206994597C13D831ec7', newAllowance, { gasPrice: 0 }); + console.log('baseToken', baseToken.address); expect(await baseToken.allowance(comet.address, timelock.address)).to.be.equal(newAllowance); + console.log('baseToken', baseToken.address); await context.setNextBaseFeeToZero(); await admin.approveThis(timelock.address, baseTokenAddress, 0, { gasPrice: 0 }); + console.log('baseToken', baseToken.address); expect(await baseToken.allowance(comet.address, timelock.address)).to.be.equal(0n); });