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

Add FBTC as collateral to USDC market on Mainnet #952

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions deployments/mainnet/usdc/migrations/1733936182_add_fbtc_collateral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { expect } from 'chai';
import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager';
import { migration } from '../../../../plugins/deployment_manager/Migration';
import { exp, proposal } from '../../../../src/deploy';

const FBTC_ADDRESS = '0xC96dE26018A54D51c097160568752c4E3BD6C364';
const FBTC_TO_BTC_PRICE_FEED = '0xe5346a4Fd329768A99455d969724768a00CA63FB';
const BTC_TO_USD_PRICE_FEED = '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c';

let newPriceFeedAddress: string;

export default migration('1733936182_add_fbtc_collateral', {
async prepare(deploymentManager: DeploymentManager) {
const FBTCMultiplicativePriceFeed = await deploymentManager.deploy(
'FBTC:priceFeed',
'pricefeeds/MultiplicativePriceFeed.sol',
[
FBTC_TO_BTC_PRICE_FEED, // FBTC / BTC price feed
BTC_TO_USD_PRICE_FEED, // BTC / USD price feed
8, // decimals
'FBTC / USD price feed' // description
]
);
return { FBTCPriceFeedAddress: FBTCMultiplicativePriceFeed.address };
},

async enact(deploymentManager: DeploymentManager, _, { FBTCPriceFeedAddress }) {

const trace = deploymentManager.tracer();

const FBTC = await deploymentManager.existing(
'FBTC',
FBTC_ADDRESS,
'mainnet',
'contracts/ERC20.sol:ERC20'
);
const FBTCPriceFeed = await deploymentManager.existing(
'FBTC:priceFeed',
FBTCPriceFeedAddress,
'mainnet'
);

newPriceFeedAddress = FBTCPriceFeedAddress;

const {
governor,
comet,
cometAdmin,
configurator,
} = await deploymentManager.getContracts();

const FBTCAssetConfig = {
asset: FBTC.address,
priceFeed: FBTCPriceFeed.address,
decimals: await FBTC.decimals(),
borrowCollateralFactor: exp(0.8, 18),
liquidateCollateralFactor: exp(0.85, 18),
liquidationFactor: exp(0.9, 18),
supplyCap: exp(90, 8),
};

const mainnetActions = [
// 1. Add FBTC as asset
{
contract: configurator,
signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))',
args: [comet.address, FBTCAssetConfig],
},
// 2. Deploy and upgrade to a new version of Comet
{
contract: cometAdmin,
signature: 'deployAndUpgradeTo(address,address)',
args: [configurator.address, comet.address],
},
];

const description = '# Add FBTC as collateral into cUSDCv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add FBTC into cUSDCv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDC market on Ethereum. 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/add-collateral-fbtc-on-eth-usdt-usdc-markets-on-mainnet/5936/2).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/952) and [forum discussion](https://www.comp.xyz/t/add-collateral-fbtc-on-eth-usdt-usdc-markets-on-mainnet/5936).\n\n\n## Proposal Actions\n\nThe first action adds FBTC asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.';
const txn = await deploymentManager.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(): Promise<boolean> {
return false;
},

async verify(deploymentManager: DeploymentManager) {
const { comet, configurator } = await deploymentManager.getContracts();

const FBTCAssetIndex = Number(await comet.numAssets()) - 1;

const FBTCAssetConfig = {
asset: FBTC_ADDRESS,
priceFeed: newPriceFeedAddress,
decimals: 8,
borrowCollateralFactor: exp(0.8, 18),
liquidateCollateralFactor: exp(0.85, 18),
liquidationFactor: exp(0.9, 18),
supplyCap: exp(90, 8),
};

// 1. Compare FBTC asset config with Comet and Configurator asset info
const cometFBTCAssetInfo = await comet.getAssetInfoByAddress(FBTC_ADDRESS);
expect(FBTCAssetIndex).to.be.equal(cometFBTCAssetInfo.offset);
expect(FBTCAssetConfig.asset).to.be.equal(cometFBTCAssetInfo.asset);
expect(FBTCAssetConfig.priceFeed).to.be.equal(cometFBTCAssetInfo.priceFeed);
expect(exp(1, FBTCAssetConfig.decimals)).to.be.equal(cometFBTCAssetInfo.scale);
expect(FBTCAssetConfig.borrowCollateralFactor).to.be.equal(cometFBTCAssetInfo.borrowCollateralFactor);
expect(FBTCAssetConfig.liquidateCollateralFactor).to.be.equal(cometFBTCAssetInfo.liquidateCollateralFactor);
expect(FBTCAssetConfig.liquidationFactor).to.be.equal(cometFBTCAssetInfo.liquidationFactor);
expect(FBTCAssetConfig.supplyCap).to.be.equal(cometFBTCAssetInfo.supplyCap);

const configuratorFBTCAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[FBTCAssetIndex];
expect(FBTCAssetConfig.asset).to.be.equal(configuratorFBTCAssetConfig.asset);
expect(FBTCAssetConfig.priceFeed).to.be.equal(configuratorFBTCAssetConfig.priceFeed);
expect(FBTCAssetConfig.decimals).to.be.equal(configuratorFBTCAssetConfig.decimals);
expect(FBTCAssetConfig.borrowCollateralFactor).to.be.equal(configuratorFBTCAssetConfig.borrowCollateralFactor);
expect(FBTCAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorFBTCAssetConfig.liquidateCollateralFactor);
expect(FBTCAssetConfig.liquidationFactor).to.be.equal(configuratorFBTCAssetConfig.liquidationFactor);
expect(FBTCAssetConfig.supplyCap).to.be.equal(configuratorFBTCAssetConfig.supplyCap);
},
});
15 changes: 10 additions & 5 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,16 @@ interface NetworkConfig {
}

const networkConfigs: NetworkConfig[] = [
{ network: 'mainnet', chainId: 1 },
{ network: 'ropsten', chainId: 3 },
{ network: 'rinkeby', chainId: 4 },
{ network: 'goerli', chainId: 5 },
{ network: 'sepolia', chainId: 11155111 },
{
network: 'mainnet',
chainId: 1,
url: `https://rpc.ankr.com/eth/${ANKR_KEY}`,
},
{
network: 'sepolia',
chainId: 11155111,
url: `https://rpc.ankr.com/eth_sepolia/${ANKR_KEY}`,
},
{
network: 'polygon',
chainId: 137,
Expand Down
55 changes: 45 additions & 10 deletions plugins/scenario/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,37 @@ export class Runner<T, U, R> {
}
}


async function retry(fn: () => Promise<any>, retries: number = 10, timeLimit?: number, wait: number = 100) {
try {
return await asyncCallWithTimeout(fn(), timeLimit);
} catch (e) {
if (retries === 0) throw e;
if(e.reason !== 'could not detect network')
throw e;

console.warn(`Retrying in ${wait}ms...`);

await new Promise(ok => setTimeout(ok, wait));
return retry(fn, retries - 1, timeLimit, wait >= 10000 ? 10000 : wait * 2);
}
}
async function asyncCallWithTimeout(asyncPromise: Promise<any>, timeLimit: number = 5000_000) {
let timeoutHandle: string | number | NodeJS.Timeout;

const timeoutPromise = new Promise((_resolve, reject) => {
timeoutHandle = setTimeout(
() => reject(new Error('Async call timeout limit reached')),
timeLimit
);
});

return Promise.race([asyncPromise, timeoutPromise]).then(result => {
clearTimeout(timeoutHandle);
return result;
});
}

export async function runScenarios(bases: ForkSpec[]) {
const loader = await Loader.load();
const [runningScenarios, skippedScenarios] = loader.splitScenarios();
Expand All @@ -161,16 +192,20 @@ export async function runScenarios(bases: ForkSpec[]) {
const results: Result[] = [];

for (const base of bases) {
const world = new World(base), dm = world.deploymentManager;
const delta = await dm.runDeployScript({ allMissing: true });
console.log(`[${base.name}] Deployed ${dm.counter} contracts, spent ${dm.spent} to initialize world 🗺`);
console.log(`[${base.name}]\n${dm.diffDelta(delta)}`);

if (world.auxiliaryDeploymentManager) {
await world.auxiliaryDeploymentManager.spider();
}

const runner = new Runner(base, world);
let runner: Runner<unknown, unknown, unknown>;
await retry(async () => {
const world = new World(base);
await world.initialize(base);
const dm = world.deploymentManager;
const delta = await dm.runDeployScript({ allMissing: true });
console.log(`[${base.name}] Deployed ${dm.counter} contracts, spent ${dm.spent} to initialize world 🗺`);
console.log(`[${base.name}]\n${dm.diffDelta(delta)}`);

if (world.auxiliaryDeploymentManager) {
await world.auxiliaryDeploymentManager.spider();
}
runner = new Runner(base, world);
});

// NB: contexts are (still) a bit awkward
// they prob dont even really need to get passed through here currently
Expand Down
10 changes: 6 additions & 4 deletions plugins/scenario/World.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ export class World {
snapshotAuxiliaryDeploymentManager?: DeploymentManager;

constructor(base: ForkSpec) {
// Q: should we really need to fork/snapshot the deployment manager?
const hre = hreForBase(base);
this.base = base;
}

async initialize(base: ForkSpec) {
const hre = await hreForBase(base);
this.deploymentManager = new DeploymentManager(base.network, base.deployment, hre);
// Q: should we really need to fork/snapshot the deployment manager?
this.snapshotDeploymentManager = this.deploymentManager;

if (this.base.auxiliaryBase) {
const auxiliaryBase = hre.config.scenario.bases.find(b => b.name === this.base.auxiliaryBase);
this.auxiliaryDeploymentManager = new DeploymentManager(auxiliaryBase.network, auxiliaryBase.deployment, hreForBase(auxiliaryBase));
this.auxiliaryDeploymentManager = new DeploymentManager(auxiliaryBase.network, auxiliaryBase.deployment, await hreForBase(auxiliaryBase));
this.snapshotAuxiliaryDeploymentManager = this.auxiliaryDeploymentManager;
}
}
Expand Down
16 changes: 11 additions & 5 deletions plugins/scenario/utils/hreForBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ethers } from 'ethers';
import { ethers } from 'ethers';
import type { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { HardhatContext } from 'hardhat/internal/context';
Expand Down Expand Up @@ -36,7 +36,7 @@ declare module 'hardhat/internal/core/runtime-environment' {
}
}

export function nonForkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {
export async function nonForkedHreForBase(base: ForkSpec): Promise<HardhatRuntimeEnvironment> {
const ctx: HardhatContext = HardhatContext.getHardhatContext();

const hardhatArguments = getEnvHardhatArguments(
Expand All @@ -61,7 +61,7 @@ export function nonForkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {
);
}

export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {
export async function forkedHreForBase(base: ForkSpec): Promise<HardhatRuntimeEnvironment> {
const ctx: HardhatContext = HardhatContext.getHardhatContext();

const hardhatArguments = getEnvHardhatArguments(HARDHAT_PARAM_DEFINITIONS, process.env);
Expand All @@ -73,6 +73,12 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {

const baseNetwork = networks[base.network] as HttpNetworkUserConfig;

const provider = new ethers.providers.JsonRpcProvider(baseNetwork.url);

// noNetwork otherwise
if(!base.blockNumber)
base.blockNumber = await provider.getBlockNumber() - 210; // arbitrary number of blocks to go back, about 1 hour

if (!baseNetwork) {
throw new Error(`cannot find network config for network: ${base.network}`);
}
Expand All @@ -96,7 +102,7 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {
defaultNetwork: 'hardhat',
networks: {
hardhat: forkedNetwork,
localhost
localhost: localhost
},
},
};
Expand All @@ -111,7 +117,7 @@ export function forkedHreForBase(base: ForkSpec): HardhatRuntimeEnvironment {
);
}

export default function hreForBase(base: ForkSpec, fork = true): HardhatRuntimeEnvironment {
export default async function hreForBase(base: ForkSpec, fork = true): Promise<HardhatRuntimeEnvironment> {
if (fork) {
return forkedHreForBase(base);
} else {
Expand Down
7 changes: 5 additions & 2 deletions src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ export const WHALES = {
'0x426c4966fC76Bf782A663203c023578B744e4C5E', // wUSDM whale
'0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale
'0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale
'0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b'
'0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b',
'0x83F8E5Bdc5953276DfDDc0FcC6A7C11Bf9242764', // FBTC whale
'0x4ADF44961a7CBe8EE46B2bf04E7198e879677825', // FBTC whale
],
polygon: [
'0x2093b4281990a568c9d588b8bce3bfd7a1557ebd', // WETH whale
Expand All @@ -115,7 +117,8 @@ export const WHALES = {
'0xee3273f6d29ddfff08ffd9d513cff314734f01a2', // COMP whale
'0x9e786a8fc88ee74b758b125071d45853356024c3', // COMP whale
'0xd93f76944e870900779c09ddf1c46275f9d8bf9b', // COMP whale
'0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9' // native USDC whale
'0xe68ee8a12c611fd043fb05d65e1548dc1383f2b9', // native USDC whale
'0x1c6b5795be43ddff8812b3e577ac752764635bc5', // native COMP whale
],
base: [
'0x6D3c5a4a7aC4B1428368310E4EC3bB1350d01455', // USDbC whale
Expand Down
14 changes: 7 additions & 7 deletions tasks/deployment_manager/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { impersonateAddress } from '../../plugins/scenario/utils';
import hreForBase from '../../plugins/scenario/utils/hreForBase';

// TODO: Don't depend on scenario's hreForBase
function getForkEnv(env: HardhatRuntimeEnvironment, deployment: string): HardhatRuntimeEnvironment {
async function getForkEnv(env: HardhatRuntimeEnvironment, deployment: string): Promise<HardhatRuntimeEnvironment> {
const base = env.config.scenario.bases.find(b => b.network == env.network.name && b.deployment == deployment);
if (!base) {
throw new Error(`No fork spec for ${env.network.name}`);
}
return hreForBase(base);
return await hreForBase(base);
}

function getDefaultDeployment(config: HardhatConfig, network: string): string {
Expand Down Expand Up @@ -65,7 +65,7 @@ task('deploy', 'Deploys market')
.addFlag('overwrite', 'overwrites cache')
.addParam('deployment', 'The deployment to deploy')
.setAction(async ({ simulate, noDeploy, noVerify, noVerifyImpl, overwrite, deployment }, env) => {
const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env;
const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env;
const network = env.network.name;
const tag = `${network}/${deployment}`;
const dm = new DeploymentManager(
Expand Down Expand Up @@ -174,7 +174,7 @@ task('migrate', 'Runs migration')
.addFlag('overwrite', 'overwrites artifact if exists, fails otherwise')
.setAction(
async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate }, env) => {
const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env;
const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env;
const network = env.network.name;
const dm = new DeploymentManager(
network,
Expand All @@ -193,7 +193,7 @@ task('migrate', 'Runs migration')
const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined;

if (governanceBase) {
const governanceEnv = hreForBase(governanceBase, simulate);
const governanceEnv = await hreForBase(governanceBase, simulate);
governanceDm = new DeploymentManager(
governanceBase.network,
governanceBase.deployment,
Expand Down Expand Up @@ -246,7 +246,7 @@ task('deploy_and_migrate', 'Runs deploy and migration')
.addParam('deployment', 'The deployment to deploy')
.setAction(
async ({ migration: migrationName, prepare, enact, noEnacted, simulate, overwrite, deployment, impersonate, noDeploy, noVerify, noVerifyImpl }, env) => {
const maybeForkEnv = simulate ? getForkEnv(env, deployment) : env;
const maybeForkEnv = simulate ? await getForkEnv(env, deployment) : env;
const network = env.network.name;
const tag = `${network}/${deployment}`;
const dm = new DeploymentManager(
Expand Down Expand Up @@ -314,7 +314,7 @@ task('deploy_and_migrate', 'Runs deploy and migration')
const governanceBase = isBridgedDeployment ? env.config.scenario.bases.find(b => b.name === base.auxiliaryBase) : undefined;

if (governanceBase) {
const governanceEnv = hreForBase(governanceBase, simulate);
const governanceEnv = await hreForBase(governanceBase, simulate);
governanceDm = new DeploymentManager(
governanceBase.network,
governanceBase.deployment,
Expand Down
2 changes: 1 addition & 1 deletion tasks/scenario/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ task('scenario:spider', 'Runs spider in preparation for scenarios')
const bases: ForkSpec[] = getBasesFromTaskArgs(taskArgs.bases, env);
await Promise.all(bases.map(async (base) => {
if (base.network !== 'hardhat') {
let hre = hreForBase(base);
let hre = await hreForBase(base);
let dm = new DeploymentManager(
base.name,
base.deployment,
Expand Down