Skip to content

Commit

Permalink
Rewards proposal 124 (compound-finance#557)
Browse files Browse the repository at this point in the history
* Potential rewards proposal to migrate v2 rewards on collateral

* Add verify step to migrations and verify rewards proposal

This should also be done statically when we can.

* Update the description for the proposal and remove cBAT/cZRX speeds

* Add scenario for testing borrow-side rewards; fix isRewardSupported filter; improve reward scenarios

* Remove extra line

Co-authored-by: kevincheng96 <[email protected]>
  • Loading branch information
jflatow and kevincheng96 authored Sep 13, 2022
1 parent f8fc505 commit d547bc3
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 28 deletions.
110 changes: 110 additions & 0 deletions deployments/mainnet/usdc/migrations/1661899622_rewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { DeploymentManager, migration } from '../../../../plugins/deployment_manager';
import { calldata, exp, proposal } from '../../../../src/deploy';

import { expect } from 'chai';

const cETHAddress = '0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5';
const cBATAddress = '0x6C8c6b02E7b2BE14d4fA6022Dfd6d75921D90E4E';
const cCOMPAddress = '0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4';
const cLINKAddress = '0xface851a4921ce59e912d19329929ce6da6eb0c7';
const cUNIAddress = '0x35a18000230da775cac24873d00ff85bccded550';
const cWBTC2Address = '0xccF4429DB6322D5C611ee964527D42E5d685DD6a';
const cZRXAddress = '0xB3319f5D18Bc0D84dD1b4825Dcde5d5f7266d407';

export default migration('1661899622_rewards', {
async prepare(deploymentManager: DeploymentManager) {
return {};
},

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

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

const actions = [
// 1. Set v2 collateral asset speeds to 0
{
contract: comptrollerV2,
signature: '_setCompSpeeds(address[],uint256[],uint256[])',
args: [
[cETHAddress, cBATAddress, cCOMPAddress, cLINKAddress, cUNIAddress, cWBTC2Address, cZRXAddress],
[exp(5.375, 15), 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
],
},

// 2. Increase borrow reward speed
{
contract: configurator,
signature: 'setBaseTrackingBorrowSpeed(address,uint64)',
args: [comet.address, exp(161.42 / 86400, 15, 18)], // ~ 161.42 COMP / day cut from v2
},

// 3. Deploy and upgrade to a new version of Comet
{
contract: cometAdmin,
signature: 'deployAndUpgradeTo(address,address)',
args: [configurator.address, comet.address],
},

// 4. Transfer COMP
{
contract: comptrollerV2,
signature: '_grantComp(address,uint256)',
args: [rewards.address, exp(25_000, 18)],
},
];
const description = "# Initialize Compound III COMP Distribution\nThis proposal includes Compound III users in the [COMP Distribution](https://compound.finance/governance/comp), which is accomplished by re-allocating 161.42 COMP per day (14.17% of the total) from v2 markets. The total COMP Distribution is unchanged by this proposal.\n\n- ETH borrowers: -35.31\n- WBTC suppliers and borrowers: -70.63\n- LINK suppliers and borrowers: -9.60\n- UNI suppliers and borrowers: -9.60\n- COMP suppliers: -16.42\n- BAT suppliers and borrowers: -9.60\n- ZRX suppliers and borrowers: -9.60\n- Compound III USDC: +161.42\n\nLastly, 25,000 COMP are transferred from the Comptroller to the Compound III Rewards contract. Based on the parameters set earlier, this equals 154 days of distribution before governance would need to replenish or modify the distribution to Compound III users.\n\nIf successful, this proposal will take effect after Proposal 119 cures the Compound v2 price feed, and prior to the Merge.\n\n[Full proposal and forum discussion](https://www.comp.xyz/t/initialize-compound-iii-usdc-on-ethereum/3499/5)\n";
const txn = await deploymentManager.retry(
async () => governor.propose(...await proposal(actions, description))
);
trace(txn);

const event = (await txn.wait()).events.find(event => event.event === 'ProposalCreated');
const [proposalId] = event.args;
trace(`Created proposal ${proposalId}.`);
},

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

// 1.
expect(await comptrollerV2.compSupplySpeeds(cETHAddress)).to.be.equal(5375000000000000);
expect(await comptrollerV2.compBorrowSpeeds(cETHAddress)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cBATAddress)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cBATAddress)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cCOMPAddress)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cCOMPAddress)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cLINKAddress)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cLINKAddress)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cUNIAddress)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cUNIAddress)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cWBTC2Address)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cWBTC2Address)).to.be.equal(0);
expect(await comptrollerV2.compSupplySpeeds(cZRXAddress)).to.be.equal(0);
expect(await comptrollerV2.compBorrowSpeeds(cZRXAddress)).to.be.equal(0);

// 2. & 3.
expect(await comet.baseTrackingSupplySpeed()).to.be.equal(0);
expect(await comet.baseTrackingBorrowSpeed()).to.be.equal(1868287037037n);

// 4.
expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(25_000, 18));
},
});
1 change: 1 addition & 0 deletions deployments/mainnet/usdc/roots.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"comptrollerV2": "0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b",
"comet": "0xc3d688B66703497DAA19211EEdff47f25384cdc3",
"configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3",
"rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40",
Expand Down
6 changes: 6 additions & 0 deletions deployments/relations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { RelationConfigMap } from '../plugins/deployment_manager/RelationConfig';

const relationConfigMap: RelationConfigMap = {
comptrollerV2: {
delegates: {
field: async (comptroller) => comptroller.comptrollerImplementation(),
},
},

comet: {
delegates: {
field: {
Expand Down
1 change: 1 addition & 0 deletions plugins/deployment_manager/Migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FileSpec } from './Cache';
interface Actions<T> {
prepare: (dm: DeploymentManager) => Promise<T>;
enact: (dm: DeploymentManager, t: T) => Promise<void>;
verify?: (dm: DeploymentManager) => Promise<void>;
}

export class Migration<T> {
Expand Down
1 change: 1 addition & 0 deletions plugins/deployment_manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Deployed, DeploymentManager } from './DeploymentManager';
export { Migration, migration } from './Migration';
export { VerifyArgs } from './Verify';
export { debug } from './Utils';
17 changes: 10 additions & 7 deletions scenario/BulkerScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ scenario(
const baseAssetAddress = await comet.baseToken();
const baseAsset = context.getAssetByAddress(baseAssetAddress);
const baseScale = (await comet.baseScale()).toBigInt();
const { asset: assetAddress, scale: scaleBN } = await comet.getAssetInfo(0);
const collateralAsset = context.getAssetByAddress(assetAddress);
const { asset: collateralAssetAddress, scale: scaleBN } = await comet.getAssetInfo(0);
const collateralAsset = context.getAssetByAddress(collateralAssetAddress);
const collateralScale = scaleBN.toBigInt();
const toSupplyCollateral = 100n * collateralScale;
const toBorrowBase = 1500n * baseScale;
Expand Down Expand Up @@ -93,8 +93,8 @@ scenario(
const baseAssetAddress = await comet.baseToken();
const baseAsset = context.getAssetByAddress(baseAssetAddress);
const baseScale = (await comet.baseScale()).toBigInt();
const { asset: assetAddress, scale: scaleBN } = await comet.getAssetInfo(0);
const collateralAsset = context.getAssetByAddress(assetAddress);
const { asset: collateralAssetAddress, scale: scaleBN } = await comet.getAssetInfo(0);
const collateralAsset = context.getAssetByAddress(collateralAssetAddress);
const collateralScale = scaleBN.toBigInt();
const [rewardTokenAddress] = await rewards.rewardConfig(comet.address);
const toSupplyCollateral = 100n * collateralScale;
Expand All @@ -117,8 +117,11 @@ scenario(
expect(await collateralAsset.balanceOf(albert.address)).to.be.equal(toSupplyCollateral);
expect(await baseAsset.balanceOf(albert.address)).to.be.equal(0n);
expect(await comet.balanceOf(albert.address)).to.be.equal(0n);
expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(0n);
expect(await rewards.callStatic.getRewardOwed(comet.address, albert.address)).to.be.gt(0n);
const startingRewardBalance = await albert.getErc20Balance(rewardTokenAddress);
const rewardOwed = ((await rewards.callStatic.getRewardOwed(comet.address, albert.address)).owed).toBigInt();
const expectedFinalRewardBalance = collateralAssetAddress === rewardTokenAddress ?
startingRewardBalance + rewardOwed - toSupplyCollateral :
startingRewardBalance + rewardOwed;

// Albert's actions:
// 1. Supplies 100 units of collateral
Expand Down Expand Up @@ -160,7 +163,7 @@ scenario(
expect(await comet.balanceOf(betty.address)).to.be.equal(baseTransferred);
expect(await comet.borrowBalanceOf(albert.address)).to.be.equal(toBorrowBase + toTransferBase);
expect(await comet.collateralBalanceOf(albert.address, WETH.address)).to.be.equal(toSupplyEth - toWithdrawEth);
expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.gt(0n);
expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance);

return txn; // return txn to measure gas
}
Expand Down
93 changes: 84 additions & 9 deletions scenario/RewardsScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ scenario(
const rewardToken = context.getAssetByAddress(rewardTokenAddress);
const rewardScale = exp(1, await rewardToken.decimals());

expect(await rewardToken.balanceOf(albert.address)).to.be.equal(0n);

await baseAsset.approve(albert, comet.address);
await albert.safeSupplyAsset({ asset: baseAssetAddress, amount: 1_000_000n * baseScale })

expect(await rewardToken.balanceOf(albert.address)).to.be.equal(0n);

const supplyTimestamp = await world.timestamp();
const albertBalance = await albert.getCometBaseBalance();
const totalSupplyBalance = (await comet.totalSupply()).toBigInt();
Expand All @@ -58,8 +58,14 @@ scenario(
const supplySpeed = (await comet.baseTrackingSupplySpeed()).toBigInt();
const trackingIndexScale = (await comet.trackingIndexScale()).toBigInt();
const timestampDelta = preTxnTimestamp - supplyTimestamp;
const expectedRewardsOwed = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
const expectedRewardsReceived = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta + timeElapsed, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
const totalSupplyPrincipal = (await comet.totalsBasic()).totalSupplyBase.toBigInt();
const baseMinForRewards = (await comet.baseMinForRewards()).toBigInt();
let expectedRewardsOwed = 0n;
let expectedRewardsReceived = 0n;
if (totalSupplyPrincipal >= baseMinForRewards) {
expectedRewardsOwed = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
expectedRewardsReceived = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta + timeElapsed, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
}

// Occasionally `timestampDelta` is equal to 86401
expect(timestampDelta).to.be.greaterThanOrEqual(86400);
Expand Down Expand Up @@ -89,12 +95,12 @@ scenario(
const rewardToken = context.getAssetByAddress(rewardTokenAddress);
const rewardScale = exp(1, await rewardToken.decimals());

expect(await rewardToken.balanceOf(albert.address)).to.be.equal(0n);

await albert.allow(betty, true); // Albert allows Betty to manage his account
await baseAsset.approve(albert, comet.address);
await albert.safeSupplyAsset({ asset: baseAssetAddress, amount: 1_000_000n * baseScale });

expect(await rewardToken.balanceOf(albert.address)).to.be.equal(0n);

const supplyTimestamp = await world.timestamp();
const albertBalance = await albert.getCometBaseBalance();
const totalSupplyBalance = (await comet.totalSupply()).toBigInt();
Expand All @@ -112,8 +118,14 @@ scenario(
const supplySpeed = (await comet.baseTrackingSupplySpeed()).toBigInt();
const trackingIndexScale = (await comet.trackingIndexScale()).toBigInt();
const timestampDelta = preTxnTimestamp - supplyTimestamp;
const expectedRewardsOwed = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
const expectedRewardsReceived = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta + timeElapsed, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
const totalSupplyPrincipal = (await comet.totalsBasic()).totalSupplyBase.toBigInt();
const baseMinForRewards = (await comet.baseMinForRewards()).toBigInt();
let expectedRewardsOwed = 0n;
let expectedRewardsReceived = 0n;
if (totalSupplyPrincipal >= baseMinForRewards) {
expectedRewardsOwed = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
expectedRewardsReceived = calculateRewardsOwed(albertBalance, totalSupplyBalance, supplySpeed, timestampDelta + timeElapsed, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
}

// Occasionally `timestampDelta` is equal to 86401
expect(timestampDelta).to.be.greaterThanOrEqual(86400);
Expand All @@ -125,4 +137,67 @@ scenario(
}
);

// XXX add borrow side rewards, which is trickier because of supply caps
scenario(
'Comet#rewards > can claim borrow rewards for self',
{
filter: async (ctx) => await isRewardSupported(ctx),
tokenBalances: {
albert: { $asset0: ' == 10000' }, // in units of asset, not wei
$comet: { $base: ' >= 1000 ' }
},
},
async ({ comet, rewards, actors }, context, world) => {
const { albert } = actors;
const { asset: collateralAssetAddress, scale: scaleBN } = await comet.getAssetInfo(0);
const collateralAsset = context.getAssetByAddress(collateralAssetAddress);
const scale = scaleBN.toBigInt();
const toSupply = 10_000n * scale;
const baseAssetAddress = await comet.baseToken();
const baseScale = (await comet.baseScale()).toBigInt();
const toBorrow = 1_000n * baseScale;

const [rewardTokenAddress, rescaleFactor] = await rewards.rewardConfig(comet.address);
const rewardToken = context.getAssetByAddress(rewardTokenAddress);
const rewardScale = exp(1, await rewardToken.decimals());

await collateralAsset.approve(albert, comet.address);
await albert.safeSupplyAsset({ asset: collateralAssetAddress, amount: toSupply })
await albert.withdrawAsset({ asset: baseAssetAddress, amount: toBorrow })

expect(await rewardToken.balanceOf(albert.address)).to.be.equal(0n);

const borrowTimestamp = await world.timestamp();
const albertBalance = await albert.getCometBaseBalance();
const totalBorrowBalance = (await comet.totalBorrow()).toBigInt();

await world.increaseTime(86400); // fast forward a day
const preTxnTimestamp = await world.timestamp();

const rewardsOwedBefore = (await rewards.callStatic.getRewardOwed(comet.address, albert.address)).owed.toBigInt();
const txn = await (await rewards.connect(albert.signer).claim(comet.address, albert.address, true)).wait();
const rewardsOwedAfter = (await rewards.callStatic.getRewardOwed(comet.address, albert.address)).owed.toBigInt();

const postTxnTimestamp = await world.timestamp();
const timeElapsed = postTxnTimestamp - preTxnTimestamp;

const borrowSpeed = (await comet.baseTrackingBorrowSpeed()).toBigInt();
const trackingIndexScale = (await comet.trackingIndexScale()).toBigInt();
const timestampDelta = preTxnTimestamp - borrowTimestamp;
const totalBorrowPrincipal = (await comet.totalsBasic()).totalBorrowBase.toBigInt();
const baseMinForRewards = (await comet.baseMinForRewards()).toBigInt();
let expectedRewardsOwed = 0n;
let expectedRewardsReceived = 0n;
if (totalBorrowPrincipal >= baseMinForRewards) {
expectedRewardsOwed = calculateRewardsOwed(-albertBalance, totalBorrowBalance, borrowSpeed, timestampDelta, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
expectedRewardsReceived = calculateRewardsOwed(-albertBalance, totalBorrowBalance, borrowSpeed, timestampDelta + timeElapsed, trackingIndexScale, rewardScale, rescaleFactor.toBigInt());
}

// Occasionally `timestampDelta` is equal to 86401
expect(timestampDelta).to.be.greaterThanOrEqual(86400);
expect(rewardsOwedBefore).to.be.equal(expectedRewardsOwed);
expect(await rewardToken.balanceOf(albert.address)).to.be.equal(expectedRewardsReceived);
expect(rewardsOwedAfter).to.be.equal(0n);

return txn; // return txn to measure gas
}
);
30 changes: 25 additions & 5 deletions scenario/constraints/MigrationConstraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ async function getMigrations<T>(context: CometContext, requirements: Requirement
return await loadMigrations((await modifiedPaths(pattern)).map(p => '../../' + p));
}

async function asyncFilter<T>(els: T[], f: (T) => Promise<boolean>): Promise<T[]> {
let filterResults = await Promise.all(els.map((el) => f(el)));
return els.filter((el, i) => filterResults[i]);
}

export class MigrationConstraint<T extends CometContext, R extends Requirements> implements Constraint<T, R> {
async solve(requirements: R, context: T, world: World) {
const label = `[${world.base.name}] {MigrationConstraint}`;
Expand All @@ -30,7 +25,12 @@ export class MigrationConstraint<T extends CometContext, R extends Requirements>
// Make proposer the default signer
ctx.deploymentManager._signers.unshift(proposer);

// Order migrations deterministically and store in the context (i.e. for verification)
migrationList.sort((a, b) => a.name.localeCompare(b.name))
ctx.migrations = migrationList;

// XXX This should check that a migration has not already been run/proposed on-chain.
// Otherwise the scenario could be running the same proposal twice.
debug(`${label} Running scenario with migrations: ${JSON.stringify(migrationList.map((m) => m.name))}`);
for (const migration of migrationList) {
const artifact = await migration.actions.prepare(ctx.deploymentManager);
Expand All @@ -54,3 +54,23 @@ export class MigrationConstraint<T extends CometContext, R extends Requirements>
return; // XXX
}
}

export class VerifyMigrationConstraint<T extends CometContext, R extends Requirements> implements Constraint<T, R> {
async solve(requirements: R, context: T, world: World) {
const label = `[${world.base.name}] {VerifyMigrationConstraint}`;
return [
async function (ctx: T): Promise<T> {
for (const migration of ctx.migrations) {
// XXX does verify get the 'gov' deployment manager as well as the 'local' one?
if (migration.actions.verify) {
await migration.actions.verify(ctx.deploymentManager);
debug(`${label} Verified migration "${migration.name}"`);
}
}
return ctx;
}
];
}

async check(requirements: R, context: T, world: World) { }
}
5 changes: 3 additions & 2 deletions scenario/constraints/ProposalConstraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ export class ProposalConstraint<T extends CometContext, R extends Requirements>
debug(`${label} Processing pending proposal ${proposal.id}`);
await ctx.executeOpenProposal(proposal);
debug(`${label} Open proposal ${proposal.id} was executed`);
} catch (err) {
debug(`${label} Failed to execute proposal ${proposal.id}`, err);
} catch(err) {
debug(`${label} Failed to execute proposal ${proposal.id}`, err.message);
throw(err);
}
}
return ctx;
Expand Down
Loading

0 comments on commit d547bc3

Please sign in to comment.