From 87e7a05edf28b9f42672f41cabe68295164ac57d Mon Sep 17 00:00:00 2001 From: felix2feng Date: Tue, 19 May 2020 17:23:09 -0700 Subject: [PATCH] Add checkpoints --- package.json | 2 +- .../scenario/performanceFeeScenarios.spec.ts | 222 ++++++++++++++---- utils/constants.ts | 1 + utils/helpers/rebalanceTestSetup.ts | 44 +++- 4 files changed, 215 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 1bc238e5d..aa5a1a99c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "clean": "rm -rf build; rm -rf transpiled; rm -rf types/generated", "clean-chain": "rm -rf blockchain && cp -r snapshots/0x-Kyber-Compound blockchain", "buidler-new": "yarn buidler clean && yarn buidler-test", - "buidler-test": "yarn buidler-setup && buidler test transpiled/test/contracts/core/liquidators/twapLiquidator.spec.js", + "buidler-test": "yarn buidler-setup && buidler test transpiled/test/contracts/core/scenario/performanceFeeScenarios.spec.js", "buidler-setup": "buidler compile && bash scripts/buidler_deploy_dev.sh && yarn transformJson && yarn generate-typings && yarn transpile", "compile": "./node_modules/.bin/truffle compile --all", "coverage": "yarn coverage-setup && ./node_modules/.bin/solidity-coverage && yarn coverage-cleanup", diff --git a/test/contracts/core/scenario/performanceFeeScenarios.spec.ts b/test/contracts/core/scenario/performanceFeeScenarios.spec.ts index cca3e69cd..23f974b03 100644 --- a/test/contracts/core/scenario/performanceFeeScenarios.spec.ts +++ b/test/contracts/core/scenario/performanceFeeScenarios.spec.ts @@ -10,7 +10,7 @@ import { BigNumber } from 'bignumber.js'; import ChaiSetup from '@utils/chaiSetup'; import { BigNumberSetup } from '@utils/bigNumberSetup'; import { - TWAPLiquidatorContract, + PerformanceFeeCalculatorContract, SetTokenContract, RebalancingSetTokenV3Contract, } from '@utils/contracts'; @@ -21,6 +21,8 @@ import { DEFAULT_GAS, ONE_DAY_IN_SECONDS, ONE_HOUR_IN_SECONDS, + ONE_MONTH_IN_SECONDS, + ONE_YEAR_IN_SECONDS, ZERO, } from '@utils/constants'; import { expectRevertError } from '@utils/tokenAssertions'; @@ -32,7 +34,7 @@ import { OracleHelper } from 'set-protocol-oracles'; import { RebalancingSetV3Helper } from '@utils/helpers/rebalancingSetV3Helper'; import { FeeCalculatorHelper } from '@utils/helpers/feeCalculatorHelper'; import { ValuationHelper } from '@utils/helpers/valuationHelper'; -import { RebalanceTestSetup } from '@utils/helpers/rebalanceTestSetup'; +import { RebalanceTestSetup, PriceUpdate } from '@utils/helpers/rebalanceTestSetup'; BigNumberSetup.configure(); ChaiSetup.configure(); @@ -61,55 +63,92 @@ interface ComponentSettings { component2Decimals: number; } -interface TWAPScenario { +interface PerfFeeScenarios { name: string; rebalancingSet: RebalancingSetDetails; currentSet: SetDetails; - nextSet: SetDetails; components: ComponentSettings; + highWatermarkResetPeriod: BigNumber; profitFee: BigNumber; t1: { - component1Price: BigNumber; - component2Price: BigNumber; - profitFee: BigNumber; + timeElapsed: BigNumber; + prices: PriceUpdate; + newProfitFee: BigNumber; } } +interface CheckPoint { + rebalancingSetValue: BigNumber, + highWatermark: BigNumber; + profitFeePercentage: BigNumber; + lastProfitFeeTimestamp: BigNumber; + unitShares: BigNumber; + totalSupply: BigNumber; + feeRecipientShares: BigNumber; +} -const scenarios: TWAPScenario[] = [ +const scenarios: PerfFeeScenarios[] = [ { - name: 'ETH 20 MA Set Rebalances 100% WETH to 100% USD', + name: 'ETH/BTC 50/50 Set USD-Watermark Scenario 1', rebalancingSet: { - unitShares: new BigNumber(2076796), - naturalUnit: new BigNumber(1000000), - supply: new BigNumber('20556237207015075000000') + unitShares: new BigNumber(800000), + naturalUnit: new BigNumber(100000000), + supply: new BigNumber('14256596210800000000000') }, currentSet: { - components: ['component1'], // ETH - units: [new BigNumber(1000000)], - naturalUnit: new BigNumber(1000000), - }, - nextSet: { - components: ['component2'], // USDC - units: [new BigNumber(307)], - naturalUnit: new BigNumber(1000000000000), + components: ['component1', 'component2'], // ETH and WBTC + units: [new BigNumber(500000000000000), new BigNumber(1000)], + naturalUnit: new BigNumber(10000000000000), }, components: { - component1Price: ether(188), - component2Price: ether(1), + component1Price: ether(200), + component2Price: ether(10000), component1Decimals: 18, - component2Decimals: 6, + component2Decimals: 8, }, profitFee: ether(0.1), + highWatermarkResetPeriod: ONE_YEAR_IN_SECONDS, t1: { + timeElapsed: ONE_YEAR_IN_SECONDS, + prices: { + component2Price: ether(9000), + }, + newProfitFee: ether(0.2) + } + }, + { + name: 'ETH/BTC 50/50 Set USD-Watermark Scenario 13', + rebalancingSet: { + unitShares: new BigNumber(800000), + naturalUnit: new BigNumber(100000000), + supply: new BigNumber('14256596210800000000000') + }, + currentSet: { + components: ['component1', 'component2'], // ETH and WBTC + units: [new BigNumber(500000000000000), new BigNumber(1000)], + naturalUnit: new BigNumber(10000000000000), + }, + components: { component1Price: ether(200), - component2Price: ether(1), - profitFee: ether(0.2) + component2Price: ether(10000), + component1Decimals: 18, + component2Decimals: 8, + }, + profitFee: ether(0.1), + highWatermarkResetPeriod: ONE_YEAR_IN_SECONDS, + t1: { + timeElapsed: ONE_YEAR_IN_SECONDS, + prices: { + component2Price: ether(12000), + }, + newProfitFee: ether(0.2) } }, ]; - +// IMPROVEMENTS TO MAKE + // Add concept of checkpointing of important values + // Add printer of final human-readable results and percentage changes of important key values contract('PerformanceFeeCalculator Scenarios', accounts => { const [ deployerAccount, @@ -120,14 +159,15 @@ contract('PerformanceFeeCalculator Scenarios', accounts => { let rebalancingSetToken: RebalancingSetTokenV3Contract; - let liquidator: TWAPLiquidatorContract; + let performanceFeeCalculator: PerformanceFeeCalculatorContract; - let name: string = 'liquidator'; - let auctionPeriod: BigNumber = ONE_HOUR_IN_SECONDS.mul(4); - let rangeStart: BigNumber = new BigNumber(1); - let rangeEnd: BigNumber = new BigNumber(21); + const maxStreamingFee = ether(0.05); // 5% + const maxProfitFee = ether(0.4); // 40% + const defaultProfitFeePeriod = ONE_MONTH_IN_SECONDS; + const defaultStreamingFeePercentage = ZERO; let setup: RebalanceTestSetup; + let checkPoints: CheckPoint[]; const coreHelper = new CoreHelper(deployerAccount, deployerAccount); const erc20Helper = new ERC20Helper(deployerAccount); @@ -153,39 +193,99 @@ contract('PerformanceFeeCalculator Scenarios', accounts => { blockchain.saveSnapshotAsync(); setup = new RebalanceTestSetup(deployerAccount); + checkPoints = []; }); afterEach(async () => { blockchain.revertAsync(); }); - // Things to test - // watermarks, unitShares, profitFee state describe(`${scenarios[0].name}`, async () => { it('should successfully complete', async () => { await runScenario(scenarios[0]); - - const newRebalanceState = await setup.rebalancingSetToken.rebalanceState.callAsync(); - expect(newRebalanceState).to.be.bignumber.equal(SetUtils.REBALANCING_STATE.DEFAULT); }); }); - async function runScenario(scenario: TWAPScenario): Promise { - await initializeScenario(scenario); + describe.only(`${scenarios[1].name}`, async () => { + it('should successfully complete', async () => { + await runScenario(scenarios[1]); + }); + }); - // Deploy Fee Calculator + async function runScenario(scenario: PerfFeeScenarios): Promise { + await initialize(scenario); - // Adjust some timestamps + await checkPoint(0); - // Adjust some prices + await setup.jumpTimeAndUpdateOracles( + scenario.t1.timeElapsed, + scenario.t1.prices + ); // Adjust some fees + await rebalancingSetToken.adjustFee.sendTransactionAsync( + feeCalculatorHelper.generateAdjustFeeCallData(new BigNumber(1), scenario.t1.newProfitFee), + { from: managerAccount, gas: DEFAULT_GAS } + ); + + await checkPoint(1); - // What are things we want to do / test? + await printResults(); + } + + async function checkPoint(num:number): Promise { + const rebalancingSetValue = await valuationHelper.calculateRebalancingSetTokenValueAsync( + setup.rebalancingSetToken, + setup.oracleWhiteList, + ); + const feeState = await performanceFeeCalculator.feeState.callAsync(setup.rebalancingSetToken.address); + const unitShares = await setup.rebalancingSetToken.unitShares.callAsync(); + const totalSupply = await setup.rebalancingSetToken.totalSupply.callAsync(); + const feeRecipientShares = await setup.rebalancingSetToken.balanceOf.callAsync(feeRecipient); + + checkPoints[num] = { + rebalancingSetValue, + highWatermark: new BigNumber(feeState['highWatermark']), + profitFeePercentage: new BigNumber(feeState['profitFeePercentage']), + lastProfitFeeTimestamp: feeState['lastProfitFeeTimestamp'], + unitShares, + totalSupply, + feeRecipientShares, + } + } + + async function printResults(): Promise { + const t0 = checkPoints[0]; + const tN = checkPoints[checkPoints.length - 1]; + + console.log(`RebalancingSet Value Before: ${deScale(t0.rebalancingSetValue).toString()}`); + console.log(`RebalancingSet Value After: ${deScale(tN.rebalancingSetValue).toString()}`); + + console.log(`Watermark Before: ${deScale(t0.highWatermark)}`); + console.log(`Watermark After: ${deScale(tN.highWatermark)}`); + + console.log(`profitFeePercentage Before: ${deScale(t0.profitFeePercentage)}`); + console.log(`profitFeePercentage After: ${deScale(tN.profitFeePercentage)}`); + + console.log(`lastProfitFeeTimestamp Before: ${deScale(t0.lastProfitFeeTimestamp)}`); + console.log(`lastProfitFeeTimestamp After: ${deScale(tN.lastProfitFeeTimestamp)}`); + + console.log(`unitShares Before: ${deScale(t0.unitShares)}`); + console.log(`unitShares After: ${deScale(tN.unitShares)}`); + + console.log(`totalSupply Before: ${deScale(t0.totalSupply)}`); + console.log(`totalSupply After: ${deScale(tN.totalSupply)}`); + + console.log(`feeRecipientShares Before: ${deScale(t0.feeRecipientShares)}`); + console.log(`feeRecipientShares After: ${deScale(tN.feeRecipientShares)}`); + } + + function deScale(v1: BigNumber): BigNumber { + return new BigNumber(v1).div(ether(1)).round(0, 3); } // Sets up assets, creates and mints rebalancing set - async function initializeScenario(scenario: any): Promise { + async function initialize(scenario: any): Promise { await setup.initializeCore(); await setup.initializeComponents(scenario.components); await setup.initializeBaseSets({ @@ -194,20 +294,42 @@ contract('PerformanceFeeCalculator Scenarios', accounts => { set1NaturalUnit: scenario.currentSet.naturalUnit, }); + performanceFeeCalculator = await feeCalculatorHelper.deployPerformanceFeeCalculatorAsync( + setup.core.address, + setup.oracleWhiteList.address, + maxProfitFee, + maxStreamingFee, + ); + await coreHelper.addAddressToWhiteList(performanceFeeCalculator.address, setup.feeCalculatorWhitelist); + const failPeriod = ONE_DAY_IN_SECONDS; const { timestamp: lastRebalanceTimestamp } = await web3.eth.getBlock('latest'); - rebalancingSetToken = await rebalancingHelper.createDefaultRebalancingSetTokenV3Async( - setup.core, - setup.rebalancingFactory.address, + const performanceFeeSettings = feeCalculatorHelper.generatePerformanceFeeCallDataBuffer( + defaultProfitFeePeriod, + scenario.highWatermarkResetPeriod, + scenario.profitFee, + defaultStreamingFeePercentage + ); + + const callData = rebalancingHelper.generateRebalancingSetTokenV3CallData( managerAccount, setup.linearAuctionLiquidator.address, feeRecipient, - setup.fixedFeeCalculator.address, - setup.set1.address, + performanceFeeCalculator.address, + ONE_DAY_IN_SECONDS, failPeriod, lastRebalanceTimestamp, - ZERO, // entry fee - ZERO, // rebalance fee + ZERO, + performanceFeeSettings, + ); + + rebalancingSetToken = await rebalancingHelper.createRebalancingTokenV3Async( + setup.core, + setup.rebalancingFactory.address, + [setup.set1.address], + [scenario.rebalancingSet.unitShares], + scenario.rebalancingSet.naturalUnit, + callData ); await setup.setRebalancingSet(rebalancingSetToken); diff --git a/utils/constants.ts b/utils/constants.ts index 802cd914a..fdadbc121 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -21,6 +21,7 @@ export const ONE: BigNumber = new BigNumber(1); export const ONE_HUNDRED = new BigNumber(100); export const ONE_DAY_IN_SECONDS = new BigNumber(86400); export const ONE_HOUR_IN_SECONDS = new BigNumber(3600); +export const ONE_MONTH_IN_SECONDS = new BigNumber(86400).mul(30); export const ONE_YEAR_IN_SECONDS = ONE_DAY_IN_SECONDS.mul(365.25); export const SCALE_FACTOR = ether(1); export const STANDARD_COMPONENT_UNIT = ether(1); diff --git a/utils/helpers/rebalanceTestSetup.ts b/utils/helpers/rebalanceTestSetup.ts index 85adb513e..f654f9422 100644 --- a/utils/helpers/rebalanceTestSetup.ts +++ b/utils/helpers/rebalanceTestSetup.ts @@ -20,6 +20,7 @@ import { WhiteListContract, FixedFeeCalculatorContract, } from '../contracts'; +import { Blockchain } from '@utils/blockchain'; import { ether } from '../units'; import { ONE_HOUR_IN_SECONDS } from '@utils/constants'; @@ -30,6 +31,8 @@ import { FeeCalculatorHelper } from './feeCalculatorHelper'; import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; import { ValuationHelper } from '@utils/helpers/valuationHelper'; +const blockchain = new Blockchain(web3); + export interface BaseSetConfig { set1Components?: Address[]; set2Components?: Address[]; @@ -42,6 +45,12 @@ export interface BaseSetConfig { set3NaturalUnit?: BigNumber; } +export interface PriceUpdate { + component1Price?: BigNumber; + component2Price?: BigNumber; + component3Price?: BigNumber; +} + export interface ComponentConfig { component1Price?: BigNumber; component2Price?: BigNumber; @@ -188,9 +197,17 @@ export class RebalanceTestSetup { this.component2Oracle = await this._oracleHelper.deployUpdatableOracleMockAsync(this.component2Price); this.component3Oracle = await this._oracleHelper.deployUpdatableOracleMockAsync(this.component3Price); - this.oracleWhiteList = await this._coreHelper.deployOracleWhiteListAsync( - [this.component1.address, this.component2.address, this.component3.address], - [this.component1Oracle.address, this.component2Oracle.address, this.component3Oracle.address], + await this.oracleWhiteList.addTokenOraclePair.sendTransactionAsync( + this.component1.address, + this.component1Oracle.address, + ); + await this.oracleWhiteList.addTokenOraclePair.sendTransactionAsync( + this.component2.address, + this.component2Oracle.address, + ); + await this.oracleWhiteList.addTokenOraclePair.sendTransactionAsync( + this.component3.address, + this.component3Oracle.address, ); await this._coreHelper.addTokensToWhiteList( @@ -223,6 +240,7 @@ export class RebalanceTestSetup { await this._coreHelper.addModuleAsync(this.core, this.rebalanceAuctionModule.address); this.rebalancingComponentWhiteList = await this._coreHelper.deployWhiteListAsync(); + this.oracleWhiteList = await this._coreHelper.deployOracleWhiteListAsync(); this.liquidatorWhitelist = await this._coreHelper.deployWhiteListAsync(); this.feeCalculatorWhitelist = await this._coreHelper.deployWhiteListAsync(); this.rebalancingFactory = await this._coreHelper.deployRebalancingSetTokenV3FactoryAsync( @@ -286,4 +304,24 @@ export class RebalanceTestSetup { // Use issued currentSetToken to issue rebalancingSetToken await this.core.issue.sendTransactionAsync(this.rebalancingSetToken.address, rebalancingSetQuantity); } + + public async jumpTimeAndUpdateOracles( + timeIncrease: BigNumber, + newPrices: PriceUpdate, + ): Promise { + await blockchain.increaseTimeAsync(timeIncrease); + await blockchain.mineBlockAsync(); + + if (newPrices.component1Price) { + await this.component1Oracle.updatePrice.sendTransactionAsync(newPrices.component1Price); + } + + if (newPrices.component2Price) { + await this.component2Oracle.updatePrice.sendTransactionAsync(newPrices.component2Price); + } + + if (newPrices.component3Price) { + await this.component3Oracle.updatePrice.sendTransactionAsync(newPrices.component3Price); + } + } }