Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create native USDC market on Base #828

Merged
merged 12 commits into from
Mar 19, 2024
2 changes: 1 addition & 1 deletion .github/workflows/run-scenarios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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-goerli, base-goerli-weth, linea-goerli]
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]
name: Run scenarios
env:
ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }}
Expand Down
47 changes: 47 additions & 0 deletions deployments/base/usdc/configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "Compound USDC",
"symbol": "cUSDCv3",
"baseToken": "USDC",
"baseTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"baseTokenPriceFeed": "0x7e860098F58bBFC8648a4311b374B1D669a2bc6B",
"borrowMin": "1e0",
"pauseGuardian": "0x3cb4653f3b45f448d9100b118b75a1503281d2ee",
"storeFrontPriceFactor": 0.6,
"targetReserves": "5000000e6",
"rates": {
"supplyKink": 0.85,
"supplySlopeLow": 0.048,
"supplySlopeHigh": 1.6,
"supplyBase": 0,
"borrowKink": 0.85,
"borrowSlopeLow": 0.053,
"borrowSlopeHigh": 1.8,
"borrowBase": 0.015
},
"tracking": {
"indexScale": "1e15",
"baseSupplySpeed": "231_481_481_481e0",
"baseBorrowSpeed": "92_592_592_592e0",
"baseMinForRewards": "1000e6"
},
"assets": {
"cbETH": {
"address": "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
"priceFeed": "0x4687670f5f01716fAA382E2356C103BaD776752C",
"decimals": "18",
"borrowCF": 0.75,
"liquidateCF": 0.8,
"liquidationFactor": 0.85,
"supplyCap": "7500e18"
},
"WETH": {
"address": "0x4200000000000000000000000000000000000006",
"priceFeed": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70",
"decimals": "18",
"borrowCF": 0.80,
"liquidateCF": 0.90,
"liquidationFactor": 0.95,
"supplyCap": "11000e18"
}
}
}
42 changes: 42 additions & 0 deletions deployments/base/usdc/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager';
import { DeploySpec, deployComet } from '../../../src/deploy';

const HOUR = 60 * 60;
const DAY = 24 * HOUR;

const MAINNET_TIMELOCK = '0x6d903f6003cca6255d85cca4d3b5e5146dc33925';

export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise<Deployed> {
const deployed = await deployContracts(deploymentManager, deploySpec);
return deployed;
}

async function deployContracts(
deploymentManager: DeploymentManager,
deploySpec: DeploySpec
): Promise<Deployed> {
// Pull in existing assets
const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'base', 'usdbc');
const cometFactory = await deploymentManager.fromDep('cometFactory', 'base', 'usdbc');
const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'base', 'usdbc');
const configurator = await deploymentManager.fromDep('configurator', 'base', 'usdbc');
const rewards = await deploymentManager.fromDep('rewards', 'base', 'usdbc');
const bulker = await deploymentManager.fromDep('bulker', 'base', 'usdbc');
const l2CrossDomainMessenger = await deploymentManager.fromDep('l2CrossDomainMessenger', 'base', 'usdbc');
const l2StandardBridge = await deploymentManager.fromDep('l2StandardBridge', 'base', 'usdbc');
const localTimelock = await deploymentManager.fromDep('timelock', 'base', 'usdbc');
const bridgeReceiver = await deploymentManager.fromDep('bridgeReceiver', 'base', 'usdbc');
const cbETHMultiplicativePriceFeed = await deploymentManager.fromDep('cbETH:priceFeed','base', 'usdbc');


// Deploy Comet
const deployed = await deployComet(deploymentManager, deploySpec);

return {
...deployed,
bridgeReceiver,
l2CrossDomainMessenger, // TODO: don't have to part of roots. can be pulled via relations
l2StandardBridge,
bulker,
};
}
205 changes: 205 additions & 0 deletions deployments/base/usdc/migrations/1689892563_configurate_and_ens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager';
import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState';
import { migration } from '../../../../plugins/deployment_manager/Migration';
import { calldata, exp, getConfigurationStruct, proposal } from '../../../../src/deploy';
import { expect } from 'chai';

const ENSName = 'compound-community-licenses.eth';
const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41';
const ENSSubdomainLabel = 'v3-additional-grants';
const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`;
const ENSTextRecordKey = 'v3-official-markets';
const baseCOMPAddress = '0x9e1028F5F1D5eDE59748FFceE5532509976840E0';

export default migration('1689892563_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', 'base', 'usdbc');
const {
bridgeReceiver,
comet,
cometAdmin,
configurator,
rewards
} = await deploymentManager.getContracts();

const {
baseL1CrossDomainMessenger,
governor,
COMP: mainnetCOMP
} = 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 baseChainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString();
const newMarketObject = { baseSymbol: 'USDC', cometAddress: comet.address };
const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey));
if (officialMarketsJSON[baseChainId]) {
officialMarketsJSON[baseChainId].push(newMarketObject);
} else {
officialMarketsJSON[baseChainId] = [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, baseCOMPAddress]
);
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 actions = [
// 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Base.
{
contract: baseL1CrossDomainMessenger,
signature: 'sendMessage(address,bytes,uint32)',
args: [bridgeReceiver.address, l2ProposalData, 2_500_000]
},

// 2. 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)]
)
},

// 3. Send 108 COMP to arr00
{
contract: mainnetCOMP,
signature: 'transfer(address,uint256)',
args: ['0x2B384212EDc04Ae8bB41738D05BA20E33277bf33', exp(115, 18)]
}
];

const description = '# Initialize cUSDCv3 on Base\n\nThis proposal initializes the Compound III USDC market on Base as a first step to fully migrating to native USDC on base. The entirety of this proposal was simulated and indicates success. The [parameters for this deployment](https://www.comp.xyz/t/gauntlet-usdc-native-comet-market-to-compound-base-02-09-24/4982) and the [rewards configuration](https://www.comp.xyz/t/gauntlet-native-usdc-base-comet-incentives-recommendations/5009) were recommended by Gauntlet.\n\nSee links for more details:\n- [proposal pull request](https://github.com/compound-finance/comet/pull/828)\n- [forum discussion](https://www.comp.xyz/t/gauntlet-usdc-native-comet-market-to-compound-base-02-09-24/4982)\n\n## Proposal Actions\n1) The first proposal action sets the configurator factory address, sets Comet configuration, and deploys a new Comet implementation on Base. It also sets the rewards config for the new market to 20 COMP/Day for suppliers and 8 COMP/Day for borrowers.\n2) The second 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 Base cUSDCv3 market.\n3) The third action pays arr00 115 COMP for the development effort for this proposal.';
const txn = await govDeploymentManager.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<boolean> {
return true;
},

async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) {
const ethers = deploymentManager.hre.ethers;
await deploymentManager.spider(); // We spider here to pull in Base COMP now that reward config has been set

const {
comet,
rewards,
COMP
} = await deploymentManager.getContracts();

// 1.
const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber);
expect(stateChanges).to.deep.equal({
baseTrackingSupplySpeed: exp(20 / 86400, 15, 18),
baseTrackingBorrowSpeed: exp(8 / 86400, 15, 18),
WETH: {
supplyCap: exp(11000, 18)
},
cbETH: {
supplyCap: exp(7500, 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.
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',
},
],
42161: [
{
baseSymbol: 'USDC.e',
cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA',
},
{
baseSymbol: 'USDC',
cometAddress:'0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf',
}
],
8453: [
{
baseSymbol: 'USDbC',
cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf',
},
{
baseSymbol: 'WETH',
cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf',
},
{
baseSymbol: 'USDC',
cometAddress: comet.address,
}
],
});

// 20 comp per day to suppliers
expect(await comet.baseTrackingSupplySpeed()).to.be.equal(exp(20 / 86400, 15, 18));
// 8 comp per day to borrowers
expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(exp(8 / 86400, 15, 18));
}
});
28 changes: 28 additions & 0 deletions deployments/base/usdc/relations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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'
}
}
}
};
9 changes: 9 additions & 0 deletions deployments/base/usdc/roots.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"comet": "0xb125E6687d4313864e53df431d5425969c15Eb2F",
"configurator": "0x45939657d1CA34A8FA39A924B71D28Fe8431e581",
"rewards": "0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1",
"bridgeReceiver": "0x18281dfC4d00905DA1aaA6731414EABa843c468A",
"l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007",
"l2StandardBridge": "0x4200000000000000000000000000000000000010",
"bulker": "0x78D0677032A35c63D142a48A2037048871212a8C"
}
10 changes: 9 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import arbitrumBridgedUsdcGoerliRelationConfigMap from './deployments/arbitrum-g
import arbitrumGoerliNativeUsdcRelationConfigMap from './deployments/arbitrum-goerli/usdc/relations';
import baseUsdbcRelationConfigMap from './deployments/base/usdbc/relations';
import baseWethRelationConfigMap from './deployments/base/weth/relations';
import baseUsdcRelationConfigMap from './deployments/base/usdc/relations';
import baseGoerliRelationConfigMap from './deployments/base-goerli/usdc/relations';
import baseGoerliWethRelationConfigMap from './deployments/base-goerli/weth/relations';
import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relations';
Expand Down Expand Up @@ -319,7 +320,8 @@ const config: HardhatUserConfig = {
},
'base': {
usdbc: baseUsdbcRelationConfigMap,
weth: baseWethRelationConfigMap
weth: baseWethRelationConfigMap,
usdc: baseUsdcRelationConfigMap
},
'base-goerli': {
usdc: baseGoerliRelationConfigMap,
Expand Down Expand Up @@ -422,6 +424,12 @@ const config: HardhatUserConfig = {
deployment: 'weth',
auxiliaryBase: 'mainnet'
},
{
name: 'base-usdc',
network: 'base',
deployment: 'usdc',
auxiliaryBase: 'mainnet'
},
{
name: 'base-goerli',
network: 'base-goerli',
Expand Down
Loading