Skip to content

Commit

Permalink
Add wstETH as collateral to USDC market on Mainnet (#884)
Browse files Browse the repository at this point in the history
Co-authored-by: dmitriy-woof-software <dmitriy@woof.software>
  • Loading branch information
MishaShWoof and dmitriy-woof-software committed Aug 16, 2024
1 parent b1ea9b0 commit f68439d
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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 WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0';

let priceFeedAddress: string;

export default migration('1720623615_add_wsteth_as_collateral', {
async prepare() {
return {};
},

enact: async (deploymentManager: DeploymentManager) => {
const trace = deploymentManager.tracer();

const wstETH = await deploymentManager.existing(
'wstETH',
WSTETH_ADDRESS,
'mainnet',
'contracts/ERC20.sol:ERC20'
);
const wstETHPricefeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'usdt');
priceFeedAddress = wstETHPricefeed.address;
const {
governor,
comet,
cometAdmin,
configurator
} = await deploymentManager.getContracts();

const newAssetConfig = {
asset: wstETH.address,
priceFeed: wstETHPricefeed.address,
decimals: await wstETH.decimals(),
borrowCollateralFactor: exp(0.82, 18),
liquidateCollateralFactor: exp(0.87, 18),
liquidationFactor: exp(0.92, 18),
supplyCap: exp(15_000, 18),
};

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

const description = '# Add wstETH as collateral into cUSDCv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wstETH 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 off of the [recommendations from Gauntlet](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404/1).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/884) and [forum discussion](https://www.comp.xyz/t/gauntlet-wsteth-and-ezeth-asset-listing/5404).\n\n\n## Proposal Actions\n\nThe first proposal action adds wstETH 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 true;
},

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

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

const wstETHAssetConfig = {
asset: WSTETH_ADDRESS,
priceFeed: priceFeedAddress,
decimals: 18,
borrowCollateralFactor: exp(0.82, 18),
liquidateCollateralFactor: exp(0.87, 18),
liquidationFactor: exp(0.92, 18),
supplyCap: exp(15_000, 18),
};

// 1. Compare proposed asset config with Comet asset info
const wstETHAssetInfo = await comet.getAssetInfoByAddress(
WSTETH_ADDRESS
);
expect(wstETHAssetIndex).to.be.equal(wstETHAssetInfo.offset);
expect(wstETHAssetConfig.asset).to.be.equal(wstETHAssetInfo.asset);
expect(wstETHAssetConfig.priceFeed).to.be.equal(
wstETHAssetInfo.priceFeed
);
expect(exp(1, wstETHAssetConfig.decimals)).to.be.equal(
wstETHAssetInfo.scale
);
expect(wstETHAssetConfig.borrowCollateralFactor).to.be.equal(
wstETHAssetInfo.borrowCollateralFactor
);
expect(wstETHAssetConfig.liquidateCollateralFactor).to.be.equal(
wstETHAssetInfo.liquidateCollateralFactor
);
expect(wstETHAssetConfig.liquidationFactor).to.be.equal(
wstETHAssetInfo.liquidationFactor
);
expect(wstETHAssetConfig.supplyCap).to.be.equal(
wstETHAssetInfo.supplyCap
);

// 2. Compare proposed asset config with Configurator asset config
const configuratorWstETHAssetConfig = (
await configurator.getConfiguration(comet.address)
).assetConfigs[wstETHAssetIndex];
expect(wstETHAssetConfig.asset).to.be.equal(
configuratorWstETHAssetConfig.asset
);
expect(wstETHAssetConfig.priceFeed).to.be.equal(
configuratorWstETHAssetConfig.priceFeed
);
expect(wstETHAssetConfig.decimals).to.be.equal(
configuratorWstETHAssetConfig.decimals
);
expect(wstETHAssetConfig.borrowCollateralFactor).to.be.equal(
configuratorWstETHAssetConfig.borrowCollateralFactor
);
expect(wstETHAssetConfig.liquidateCollateralFactor).to.be.equal(
configuratorWstETHAssetConfig.liquidateCollateralFactor
);
expect(wstETHAssetConfig.liquidationFactor).to.be.equal(
configuratorWstETHAssetConfig.liquidationFactor
);
expect(wstETHAssetConfig.supplyCap).to.be.equal(
configuratorWstETHAssetConfig.supplyCap
);
},
});
11 changes: 11 additions & 0 deletions deployments/mainnet/usdc/relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ import baseRelationConfig from '../../relations';

export default {
...baseRelationConfig,
'wstETH': {
artifact: 'contracts/bulkers/IWstETH.sol',
relations: {
stETH: {
field: async (wstETH) => wstETH.stETH()
}
}
},
'AppProxyUpgradeable': {
artifact: 'contracts/ERC20.sol:ERC20',
},
fxRoot: {
relations: {
stateSender: {
Expand Down
107 changes: 71 additions & 36 deletions scenario/MainnetBulkerScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,45 @@ import {
import { exp } from '../test/helpers';
import { expectApproximately, isBulkerSupported, matchesDeployment } from './utils';

const MAINNET_WSTETH_ADDRESS = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0';
const MAINNET_STETH_ADDRESS = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84';

async function getWstETHIndex(context: any): Promise<number> {
const comet = await context.getComet();
const totalAssets = await comet.numAssets();
for (let i = 0; i < totalAssets; i++) {
const asset = await comet.getAssetInfo(i);
if (asset.asset.toLowerCase() === MAINNET_WSTETH_ADDRESS) {
return i;
}
}
return -1;
}

async function hasWstETH(context: any): Promise<boolean> {
return (await getWstETHIndex(context) > -1);
}

scenario(
'MainnetBulker > wraps stETH before supplying',
{
filter: async (ctx) => await isBulkerSupported(ctx) && matchesDeployment(ctx, [{network: 'mainnet', deployment: 'weth'}]),
supplyCaps: {
$asset1: 1,
},
tokenBalances: {
albert: { $asset1: '== 0' },
},
filter: async (ctx) => await hasWstETH(ctx) && await isBulkerSupported(ctx) && matchesDeployment(ctx, [{ network: 'mainnet' }]),
supplyCaps: async (ctx) => (
{
[`$asset${await getWstETHIndex(ctx)}`]: 1,
}
),
tokenBalances: async (ctx) => (
{
albert: { [`$asset${await getWstETHIndex(ctx)}`]: '== 0' },
}
),
},
async ({ comet, actors, bulker }, context) => {
const { albert } = actors;

const stETH = await context.world.deploymentManager.contract('stETH') as ERC20;
const wstETH = await context.world.deploymentManager.contract('wstETH') as IWstETH;
const stETH = await context.world.deploymentManager.hre.ethers.getContractAt('ERC20', MAINNET_STETH_ADDRESS) as ERC20;
const wstETH = await context.world.deploymentManager.hre.ethers.getContractAt('IWstETH', MAINNET_WSTETH_ADDRESS) as IWstETH;

const toSupplyStEth = exp(.1, 18);

Expand Down Expand Up @@ -57,23 +80,29 @@ scenario(
scenario(
'MainnetBulker > unwraps wstETH before withdrawing',
{
filter: async (ctx) => await isBulkerSupported(ctx) && matchesDeployment(ctx, [{network: 'mainnet', deployment: 'weth'}]),
supplyCaps: {
$asset1: 2,
},
tokenBalances: {
albert: { $asset1: 2 },
$comet: { $asset1: 5 },
},
cometBalances: {
albert: { $asset1: 1 }
}
filter: async (ctx) => await hasWstETH(ctx) && await isBulkerSupported(ctx) && matchesDeployment(ctx, [{ network: 'mainnet' }]),
supplyCaps: async (ctx) => (
{
[`$asset${await getWstETHIndex(ctx)}`]: 2,
}
),
tokenBalances: async (ctx) => (
{
albert: { [`$asset${await getWstETHIndex(ctx)}`]: 2 },
$comet: { [`$asset${await getWstETHIndex(ctx)}`]: 5 },
}
),
cometBalances: async (ctx) => (
{
albert: { [`$asset${await getWstETHIndex(ctx)}`]: 1 }
}
)
},
async ({ comet, actors, bulker }, context) => {
const { albert } = actors;

const stETH = await context.world.deploymentManager.getContractOrThrow('stETH');
const wstETH = await context.world.deploymentManager.getContractOrThrow('wstETH');
const stETH = await context.world.deploymentManager.hre.ethers.getContractAt('ERC20', MAINNET_STETH_ADDRESS) as ERC20;
const wstETH = await context.world.deploymentManager.hre.ethers.getContractAt('IWstETH', MAINNET_WSTETH_ADDRESS) as IWstETH;

await albert.allow(bulker.address, true);

Expand Down Expand Up @@ -105,23 +134,29 @@ scenario(
scenario(
'MainnetBulker > withdraw max stETH leaves no dust',
{
filter: async (ctx) => await isBulkerSupported(ctx) && matchesDeployment(ctx, [{network: 'mainnet', deployment: 'weth'}]),
supplyCaps: {
$asset1: 2,
},
tokenBalances: {
albert: { $asset1: 2 },
$comet: { $asset1: 5 },
},
cometBalances: {
albert: { $asset1: 1 }
}
filter: async (ctx) => await hasWstETH(ctx) && await isBulkerSupported(ctx) && matchesDeployment(ctx, [{ network: 'mainnet' }]),
supplyCaps: async (ctx) => (
{
[`$asset${await getWstETHIndex(ctx)}`]: 2,
}
),
tokenBalances: async (ctx) => (
{
albert: { [`$asset${await getWstETHIndex(ctx)}`]: 2 },
$comet: { [`$asset${await getWstETHIndex(ctx)}`]: 5 },
}
),
cometBalances: async (ctx) => (
{
albert: { [`$asset${await getWstETHIndex(ctx)}`]: 1 }
}
)
},
async ({ comet, actors, bulker }, context) => {
const { albert } = actors;

const stETH = await context.world.deploymentManager.contract('stETH') as ERC20;
const wstETH = await context.world.deploymentManager.contract('wstETH') as IWstETH;
const stETH = await context.world.deploymentManager.hre.ethers.getContractAt('ERC20', MAINNET_STETH_ADDRESS) as ERC20;
const wstETH = await context.world.deploymentManager.hre.ethers.getContractAt('IWstETH', MAINNET_WSTETH_ADDRESS) as IWstETH;

await albert.allow(bulker.address, true);

Expand All @@ -147,7 +182,7 @@ scenario(
scenario(
'MainnetBulker > it reverts when passed an action that does not exist',
{
filter: async (ctx) => await isBulkerSupported(ctx) && matchesDeployment(ctx, [{network: 'mainnet', deployment: 'weth'}]),
filter: async (ctx) => await hasWstETH(ctx) && await isBulkerSupported(ctx) && matchesDeployment(ctx, [{ network: 'mainnet' }]),
},
async ({ comet, actors }) => {
const { betty } = actors;
Expand Down

0 comments on commit f68439d

Please sign in to comment.