Skip to content

Commit

Permalink
Add MATICX as collateral in Polygon USDC market (compound-finance#780)
Browse files Browse the repository at this point in the history
* Add MATICx as collateral against polygon usdc (compound-finance#777)

* initial migration script to add maticx as collateral for polygon usdc

* add verify block

* formatting: white spaces removal

* Linting fixes (compound-finance#783)

* minor fix

* linting fix

* Clean up

* maticx proposal fixes (compound-finance#786)

* maticx yaml permissions fix

* Upgrade git action to use new seacrest (compound-finance#782)

* upgrade git action to use new seacrest

* updated asset config values

* added description

* Fix liquidition bot scenarios

* Fix lint

* Maticx refactor (compound-finance#796)

* code refactor

* lint fix

* removed unwanted code

* maticx description changes (compound-finance#798)

---------

Co-authored-by: Manoj Patra <[email protected]>
  • Loading branch information
kevincheng96 and Manoj Patra authored Aug 24, 2023
1 parent 55eb52c commit 658f451
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { expect } from "chai";
import { DeploymentManager } from "../../../../plugins/deployment_manager/DeploymentManager";
import { migration } from "../../../../plugins/deployment_manager/Migration";
import { calldata, exp, proposal } from "../../../../src/deploy";
import { utils } from "ethers";

interface Vars {}

const MATICX_ADDRESS = "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6";
const MATICX_PRICE_FEED_ADDRESS = "0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403";

export default migration("1689168483_add_maticx_collateral", {
prepare: async (deploymentManager: DeploymentManager) => {
return {};
},

enact: async (
deploymentManager: DeploymentManager,
govDeploymentManager: DeploymentManager,
vars: Vars
) => {
const trace = deploymentManager.tracer();

const maticx = await deploymentManager.existing(
"MATICX",
MATICX_ADDRESS,
"polygon",
"contracts/ERC20.sol:ERC20"
);
const maticxPricefeed = await deploymentManager.existing(
"MATICX:priceFeed",
MATICX_PRICE_FEED_ADDRESS,
"polygon"
);

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

const { fxRoot, governor } = await govDeploymentManager.getContracts();

const newAssetConfig = {
asset: maticx.address,
priceFeed: maticxPricefeed.address,
decimals: await maticx.decimals(),
borrowCollateralFactor: exp(0.55, 18),
liquidateCollateralFactor: exp(0.60, 18),
liquidationFactor: exp(0.93, 18),
supplyCap: exp(6_000_000, 18),
};

const addAssetCalldata = await calldata(
configurator.populateTransaction.addAsset(comet.address, newAssetConfig)
);
const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode(
["address", "address"],
[configurator.address, comet.address]
);

const l2ProposalData = utils.defaultAbiCoder.encode(
["address[]", "uint256[]", "string[]", "bytes[]"],
[
[configurator.address, cometAdmin.address],
[0, 0],
[
"addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))",
"deployAndUpgradeTo(address,address)",
],
[addAssetCalldata, deployAndUpgradeToCalldata],
]
);

const mainnetActions = [
// 1. Set Comet configuration and deployAndUpgradeTo new Comet on Polygon.
{
contract: fxRoot,
signature: "sendMessageToChild(address,bytes)",
args: [bridgeReceiver.address, l2ProposalData],
},
];

const description = "# Add MaticX as Collateral to USDCv3 Polygon Market\n\n Using MaticX as a collateral asset has added significant value on other lending platforms like Aave v3 (Polygon) and a popular leveraged staking strategy increasing utilization rates for MATIC. For Compound, MaticX offers a good collateral option because of its extensive use and composability on Polygon.\n\n ## MaticX - Key Reasons to List on Compound\n\n MaticX is deeply integrated with DeFI projects such as Aave and Balancer.\n There is $10M+ liquidity in MaticX based pools cross leading DEXs\n ~35M MaticX supplied on Aave against supply cap of 38M MaticX. Proposal to enhance supply cap to 50.6M MaticX underway.\n MaticX TVL has grown to 84M+ MATIC steadily ever since its launch in Apr’22.\n There is 20M+ MaticX on Polygon that haven't been deployed on DeFi yet, which offers a large TVL opportunity for Compound\n\n ## Proposed Parameters\n\n Liquidity - MaticX currently has a TVL of 84M+ MATIC staked and total liquidity of $10M+ in MaticX based liquidity pools on ecosystem DEXs with Balancer being the lead along with QuickSwap & MeshSwap.\n This proposal is to set the parameters for MaticX as below based on [Gauntlet's recommendations](https://www.comp.xyz/t/gauntlet-recommendations-stmatic-and-maticx-listing-on-polygon-compound-v3/4397)\n supplyCap: 6,000,000\n borrowCollateralFactor: 55%\n liquidateCollateralFactor: 60%\n liquidationFactor: 93%\n The proposal is to be made from [this pull request](https://github.com/compound-finance/comet/pull/780/files)\n\n ## Background - MaticX\n\n Stader's staking solution for Polygon is MaticX, a liquid staking solution for MATIC. Stader lets users earn MATIC staking rewards and also enables users to participate in other Defi protocols using MaticX while accruing rewards.\n MaticX is a token that represents your share of the total MATIC pool deposited with Stader. As soon as you deposit MATIC on the Stader smart contract, you receive newly minted MaticX, based on the exchange rate at the time of staking. As the MATIC rewards get added the value of MaticX increases (w.r.t MATIC).\n\n Stader for Polygon gives you\n Liquidity through tokenization\n Ease of staking\n MaticX is the only solution that allows users to natively stake their MATIC on Polygon, allowing users to take advantage of the low transaction fee\n\n ## MaticX Security:\n\n Stader on Polygon protocol contracts are dual audited:\n\n [Here](https://staderlabs-docs.s3.amazonaws.com/audits/polygon/StaderLabs_MaticX_Smart_Contract_Security_Audit_Report_Halborn_Final.pdf) is the link to the Audit completed by Halborn\n [Here](https://staderlabs-docs.s3.amazonaws.com/audits/polygon/StaderLabs_maticX_Audit_Report_Immunebytes.pdf) is the link to the Audit completed by Immunebytes\n Stader's contracts for MaticX are controlled by a multi-sig account (0x91B4139A2FAeaCD4CdbFc3F7B1663F91a54be237) managed by the internal as well as external parties. The confirmation count is 3 out of 5 signatures required $1Mn Bug Bounty on [Immunefi](https://immunefi.com/bounty/StaderforPolygon/)\n Ongoing monitoring and on-chain security tracking by [Forta](https://app.forta.network/agents/stader-labs) External multi-sig and time-lock drive the staking contract\n\n ## References\n\n [MaticX Chainlink Oracle Price Feed](https://polygonscan.com/address/0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403)\n [Polygonscan - MaticX address](https://polygonscan.com/token/0xfa68fb4628dff1028cfec22b4162fccd0d45efb6)\n [Forum Discussion](https://www.comp.xyz/t/listing-maticx-on-compound/4306)\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 verify(deploymentManager: DeploymentManager) {
const { comet, configurator } = await deploymentManager.getContracts();

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

const maticxAssetConfig = {
asset: MATICX_ADDRESS,
priceFeed: MATICX_PRICE_FEED_ADDRESS,
decimals: 18,
borrowCollateralFactor: exp(0.55, 18),
liquidateCollateralFactor: exp(0.60, 18),
liquidationFactor: exp(0.93, 18),
supplyCap: exp(6_000_000, 18),
};

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

// 2. Compare proposed asset config with Configurator asset config
const configuratorMaticxAssetConfig = (
await configurator.getConfiguration(comet.address)
).assetConfigs[maticxAssetIndex];
expect(maticxAssetConfig.asset).to.be.equal(
configuratorMaticxAssetConfig.asset
);
expect(maticxAssetConfig.priceFeed).to.be.equal(
configuratorMaticxAssetConfig.priceFeed
);
expect(maticxAssetConfig.decimals).to.be.equal(
configuratorMaticxAssetConfig.decimals
);
expect(maticxAssetConfig.borrowCollateralFactor).to.be.equal(
configuratorMaticxAssetConfig.borrowCollateralFactor
);
expect(maticxAssetConfig.liquidateCollateralFactor).to.be.equal(
configuratorMaticxAssetConfig.liquidateCollateralFactor
);
expect(maticxAssetConfig.liquidationFactor).to.be.equal(
configuratorMaticxAssetConfig.liquidationFactor
);
expect(maticxAssetConfig.supplyCap).to.be.equal(
configuratorMaticxAssetConfig.supplyCap
);
},
});
108 changes: 108 additions & 0 deletions scenario/AddMaticxCollateralScenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { scenario } from './context/CometContext';
import { expect } from 'chai';
import { utils } from 'ethers';
import { exp } from '../test/helpers';
import { calldata } from '../src/deploy';
import { impersonateAddress } from '../plugins/scenario/utils';
import { createCrossChainProposal, matchesDeployment } from './utils';

const MATICX_ADDRESS = '0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6';
const MATICX_PRICE_FEED_ADDRESS = '0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403';
const MATICX_WHALES = {
polygon: ['0x68B9220B8E617b7700aCAE1a5Ff43F3eb29257F3'],
};

// TODO: add ability to run ad hoc scenarios against a single migration, to avoid needing the scenario to do all this setup of
// listing an asset
scenario(
'add new asset maticx',
{
filter: async (ctx) =>
matchesDeployment(ctx, [{ network: 'polygon' }]),
tokenBalances: {
$comet: { $base: '>= 1' },
},
},
async (
{ comet, configurator, proxyAdmin, actors, bridgeReceiver },
context
) => {
const { albert } = actors;
const dm = context.world.deploymentManager;
const maticx = await dm.existing(
'MATICX',
MATICX_ADDRESS,
context.world.base.network,
'contracts/ERC20.sol:ERC20'
);
const maticxPricefeed = await dm.existing(
'MATICX:priceFeed',
MATICX_PRICE_FEED_ADDRESS,
context.world.base.network
);

// Allocate some tokens to Albert
const maticxWhaleSigner = await impersonateAddress(
dm,
MATICX_WHALES.polygon[0]
);
await maticx
.connect(maticxWhaleSigner)
.transfer(albert.address, exp(9000, 18).toString());

// Execute a governance proposal to:
// 1. Add new asset via Configurator
// 2. Deploy and upgrade to new implementation of Comet
const newAssetConfig = {
asset: maticx.address,
priceFeed: maticxPricefeed.address,
decimals: await maticx.decimals(),
borrowCollateralFactor: exp(0.55, 18),
liquidateCollateralFactor: exp(0.60, 18),
liquidationFactor: exp(0.93, 18),
supplyCap: exp(6_000_000, 18),
};

const addAssetCalldata = await calldata(
configurator.populateTransaction.addAsset(comet.address, newAssetConfig)
);
const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode(
['address', 'address'],
[configurator.address, comet.address]
);
const l2ProposalData = utils.defaultAbiCoder.encode(
['address[]', 'uint256[]', 'string[]', 'bytes[]'],
[
[configurator.address, proxyAdmin.address],
[0, 0],
[
'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))',
'deployAndUpgradeTo(address,address)',
],
[addAssetCalldata, deployAndUpgradeToCalldata],
]
);

await createCrossChainProposal(context, l2ProposalData, bridgeReceiver);


// Try to supply new token and borrow base
const baseAssetAddress = await comet.baseToken();
const borrowAmount = 1000n * (await comet.baseScale()).toBigInt();
const supplyAmount = exp(9000, 18);

await maticx
.connect(albert.signer)
.approve(comet.address, supplyAmount);
await albert.supplyAsset({ asset: maticx.address, amount: supplyAmount });
await albert.withdrawAsset({
asset: baseAssetAddress,
amount: borrowAmount,
});

expect(await albert.getCometCollateralBalance(maticx.address)).to.be.equal(
supplyAmount
);
expect(await albert.getCometBaseBalance()).to.be.equal(-borrowAmount);
}
);
32 changes: 29 additions & 3 deletions scenario/LiquidationBotScenario.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { scenario } from './context/CometContext';
import { CometContext, scenario } from './context/CometContext';
import { expect } from 'chai';
import { isValidAssetIndex, matchesDeployment, MAX_ASSETS, timeUntilUnderwater } from './utils';
import { ethers, event, exp, wait } from '../test/helpers';
Expand Down Expand Up @@ -68,6 +68,26 @@ async function borrowCapacityForAsset(comet: CometInterface, actor: CometActor,
return collateralValue.mul(borrowCollateralFactor).mul(baseScale).div(factorScale).div(priceScale);
}

// Filters out assets on networks that cannot be liquidated by the open-source liquidation bot
async function canBeLiquidatedByBot(ctx: CometContext, assetNum: number): Promise<boolean> {
const unsupportedAssets = {
// Reason: Most liquidity lives in MATICX / MATIC pools, which the liquidation bot cannot use if the base asset is not MATIC
MaticX: {
network: 'polygon',
deployments: ['usdc']
}
};
const comet = await ctx.getComet();
const assetInfo = await comet.getAssetInfo(assetNum);
const asset = await ctx.getAssetByAddress(assetInfo.asset);
const symbol = await asset.token.symbol();
if (symbol in unsupportedAssets) {
if (unsupportedAssets[symbol].network === ctx.world.base.network
&& unsupportedAssets[symbol].deployments.includes(ctx.world.base.deployment)) return false;
}
return true;
}

for (let i = 0; i < MAX_ASSETS; i++) {
const baseTokenBalances = {
mainnet: {
Expand Down Expand Up @@ -110,6 +130,8 @@ for (let i = 0; i < MAX_ASSETS; i++) {
' == 20',
// WMATIC
' == 300000',
// MATICX
' == 0',
],
},
arbitrum: {
Expand All @@ -132,7 +154,7 @@ for (let i = 0; i < MAX_ASSETS; i++) {
upgrade: {
targetReserves: exp(20_000, 18)
},
filter: async (ctx) => await isValidAssetIndex(ctx, i) && matchesDeployment(ctx, [{network: 'mainnet'}, {network: 'polygon'}, {network: 'arbitrum'}]),
filter: async (ctx) => await isValidAssetIndex(ctx, i) && matchesDeployment(ctx, [{network: 'mainnet'}, {network: 'polygon'}, {network: 'arbitrum'}]) && canBeLiquidatedByBot(ctx, i),
tokenBalances: async (ctx) => (
{
$comet: {
Expand Down Expand Up @@ -282,6 +304,8 @@ for (let i = 0; i < MAX_ASSETS; i++) {
' == 100',
// WMATIC
' == 2500000',
// MATICX
' == 0',
]
},
arbitrum: {
Expand Down Expand Up @@ -326,6 +350,8 @@ for (let i = 0; i < MAX_ASSETS; i++) {
exp(20, 8),
// WMATIC
exp(5000, 18),
// MATICX
exp(5, 18)
]
},
arbitrum: {
Expand All @@ -348,7 +374,7 @@ for (let i = 0; i < MAX_ASSETS; i++) {
upgrade: {
targetReserves: exp(20_000, 18)
},
filter: async (ctx) => await isValidAssetIndex(ctx, i) && matchesDeployment(ctx, [{network: 'mainnet'}, {network: 'polygon'}, {network: 'arbitrum'}]),
filter: async (ctx) => await isValidAssetIndex(ctx, i) && matchesDeployment(ctx, [{network: 'mainnet'}, {network: 'polygon'}, {network: 'arbitrum'}]) && canBeLiquidatedByBot(ctx, i),
tokenBalances: async (ctx) => (
{
$comet: {
Expand Down

0 comments on commit 658f451

Please sign in to comment.