From 419d3782cea2466bd20d964f15a0335699c4ba1d Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:14:24 -0800 Subject: [PATCH 01/35] Refactor --- contracts/core/lib/Rebalance.sol | 16 ----- .../liquidators/LinearAuctionLiquidator.sol | 4 +- contracts/core/liquidators/impl/Auction.sol | 36 ++++------ .../core/liquidators/impl/LinearAuction.sol | 41 +++++------ .../liquidators/impl/LinearAuctionMock.sol | 6 +- .../core/liquidators/impl/auction.spec.ts | 15 +--- .../liquidators/impl/linearAuction.spec.ts | 37 ++++------ .../linearAuctionLiquidator.spec.ts | 70 +++++++++---------- utils/auction.ts | 8 +-- utils/helpers/liquidatorHelper.ts | 26 +++---- 10 files changed, 98 insertions(+), 161 deletions(-) diff --git a/contracts/core/lib/Rebalance.sol b/contracts/core/lib/Rebalance.sol index 2d9d7d77b..30acc4d64 100644 --- a/contracts/core/lib/Rebalance.sol +++ b/contracts/core/lib/Rebalance.sol @@ -25,28 +25,12 @@ pragma solidity 0.5.7; */ library Rebalance { - struct Price { - uint256 numerator; - uint256 denominator; - } - struct TokenFlow { address[] addresses; uint256[] inflow; uint256[] outflow; } - function composePrice( - uint256 _numerator, - uint256 _denominator - ) - internal - pure - returns(Price memory) - { - return Price({ numerator: _numerator, denominator: _denominator }); - } - function composeTokenFlow( address[] memory _addresses, uint256[] memory _inflow, diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index bccffc874..a4e99a896 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -209,8 +209,8 @@ contract LinearAuctionLiquidator is LinearAuction, ILiquidator { return RebalancingLibrary.AuctionPriceParameters({ auctionStartTime: auction(_set).startTime, auctionTimeToPivot: linearAuction(_set).endTime, - auctionStartPrice: linearAuction(_set).startNumerator, - auctionPivotPrice: linearAuction(_set).endNumerator + auctionStartPrice: linearAuction(_set).startPrice, + auctionPivotPrice: linearAuction(_set).endPrice }); } diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index adf52c839..9adb55512 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -42,6 +42,7 @@ import { SetUSDValuation } from "../impl/SetUSDValuation.sol"; */ contract Auction { using SafeMath for uint256; + using CommonMath for uint256; using AddressArrayUtils for address[]; /* ============ Structs ============ */ @@ -157,12 +158,12 @@ contract Auction { * * @param _auction Auction Setup object * @param _quantity Amount of currentSets bidder is seeking to rebalance - * @param _price Struct of auction price numerator and denominator + * @param _price Scaled value representing the auction numeartor */ function calculateTokenFlow( Setup storage _auction, uint256 _quantity, - Rebalance.Price memory _price + uint256 _price ) internal view @@ -241,8 +242,7 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit) - .mul(_pricePrecision); + return Math.max(currentSetNaturalUnit, nextNaturalUnit); } /** @@ -271,7 +271,7 @@ contract Auction { * @param _currentUnit Amount of token i in currentSet per minimum bid amount * @param _nextSetUnit Amount of token i in nextSet per minimum bid amount * @param _unitsMultiplier Bid amount normalized to number of minimum bid amounts - * @param _price Struct of auction price numerator and denominator + * @param _priceScaled Auction price as a scaled value * @return inflowUnit Amount of token i transferred into the system * @return outflowUnit Amount of token i transferred to the bidder */ @@ -279,7 +279,7 @@ contract Auction { uint256 _currentUnit, uint256 _nextSetUnit, uint256 _unitsMultiplier, - Rebalance.Price memory _price + uint256 _priceScaled ) internal pure @@ -316,19 +316,19 @@ contract Auction { uint256 outflowUnit; // Use if statement to check if token inflow or outflow - if (_nextSetUnit.mul(_price.denominator) > _currentUnit.mul(_price.numerator)) { + if (_nextSetUnit.scale() > _currentUnit.mul(_priceScaled)) { // Calculate inflow amount inflowUnit = _unitsMultiplier.mul( - _nextSetUnit.mul(_price.denominator).sub(_currentUnit.mul(_price.numerator)) - ).div(_price.numerator); + _nextSetUnit.scale().sub(_currentUnit.mul(_priceScaled)) + ).div(_priceScaled); // Set outflow amount to 0 for component i, since tokens need to be injected in rebalance outflowUnit = 0; } else { // Calculate outflow amount outflowUnit = _unitsMultiplier.mul( - _currentUnit.mul(_price.numerator).sub(_nextSetUnit.mul(_price.denominator)) - ).div(_price.numerator); + _currentUnit.mul(_priceScaled).sub(_nextSetUnit.scale()) + ).div(_priceScaled); // Set inflow amount to 0 for component i, since tokens need to be returned in rebalance inflowUnit = 0; @@ -358,12 +358,10 @@ contract Auction { { address[] memory combinedTokenArray = _auction.combinedTokenArray; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); - uint256 pricePrecisionMem = _auction.pricePrecision; for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, - pricePrecisionMem, combinedTokenArray[i] ); } @@ -376,14 +374,12 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, uint256 _minimumBid, - uint256 _pricePrecision, address _component ) private @@ -401,8 +397,7 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid, - _pricePrecision + _minimumBid ); } @@ -416,21 +411,18 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid, - uint256 _pricePrecision + uint256 _minimumBid ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) - .div(_pricePrecision); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); } /** diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 28d7cf80f..93faa55d4 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -38,8 +38,8 @@ contract LinearAuction is Auction { struct State { Auction.Setup auction; uint256 endTime; - uint256 startNumerator; - uint256 endNumerator; + uint256 startPrice; + uint256 endPrice; } /* ============ State Variables ============ */ @@ -94,9 +94,9 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - uint256 fairValue = calculateFairValue(_currentSet, _nextSet, _linearAuction.auction.pricePrecision); - _linearAuction.startNumerator = calculateStartNumerator(fairValue); - _linearAuction.endNumerator = calculateEndNumerator(fairValue); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + _linearAuction.startPrice = calculateStartPrice(fairValue); + _linearAuction.endPrice = calculateEndPrice(fairValue); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -134,13 +134,6 @@ contract LinearAuction is Auction { ); } - /** - * Returns the linear price based on the current timestamp - */ - function getPrice(State storage _linearAuction) internal view returns (Rebalance.Price memory) { - return Rebalance.composePrice(getNumerator(_linearAuction), _linearAuction.auction.pricePrecision); - } - /** * Auction failed is defined the timestamp breacnhing the auction end time and * the auction not being complete @@ -159,7 +152,7 @@ contract LinearAuction is Auction { * @param _linearAuction Linear Auction State object * @return price uint representing the current price */ - function getNumerator(State storage _linearAuction) internal view returns (uint256) { + function getPrice(State storage _linearAuction) internal view returns (uint256) { uint256 elapsed = block.timestamp.sub(_linearAuction.auction.startTime); // If current time has elapsed @@ -175,11 +168,11 @@ contract LinearAuction is Auction { /** * Calculates the fair value based on the USD values of the next and current Sets. + * Returns a scaled value */ function calculateFairValue( ISetToken _currentSet, - ISetToken _nextSet, - uint256 _pricePrecision + ISetToken _nextSet ) internal view @@ -188,22 +181,22 @@ contract LinearAuction is Auction { uint256 currentSetUSDValue = Auction.calculateUSDValueOfSet(_currentSet); uint256 nextSetUSDValue = Auction.calculateUSDValueOfSet(_nextSet); - return nextSetUSDValue.mul(_pricePrecision).div(currentSetUSDValue); + return nextSetUSDValue.scale().div(currentSetUSDValue); } /** - * Calculates the linear auction start price + * Calculates the linear auction start price with a scaled value */ - function calculateStartNumerator(uint256 _fairValue) internal view returns(uint256) { - uint256 startRange = _fairValue.mul(rangeStart).div(100); - return _fairValue.sub(startRange); + function calculateStartPrice(uint256 _fairValueScaled) internal view returns(uint256) { + uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); + return _fairValueScaled.sub(startRange); } /** - * Calculates the linear auction end price + * Calculates the linear auction end price with a scaled value */ - function calculateEndNumerator(uint256 _fairValue) internal view returns(uint256) { - uint256 endRange = _fairValue.mul(rangeEnd).div(100); - return _fairValue.add(endRange); + function calculateEndPrice(uint256 _fairValueScaled) internal view returns(uint256) { + uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); + return _fairValueScaled.add(endRange); } } \ No newline at end of file diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 7dfebb1e1..99254651e 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -48,7 +48,7 @@ contract LinearAuctionMock is LinearAuction { return super.hasAuctionFailed(auction); } - function getPrice() external view returns(Rebalance.Price memory) { + function getPrice() external view returns(uint256) { return super.getPrice(auction); } @@ -58,10 +58,6 @@ contract LinearAuctionMock is LinearAuction { return super.getTokenFlow(auction, _quantity); } - function getNumerator() external view returns(uint256) { - return super.getNumerator(auction); - } - function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { return super.calculateUSDValueOfSet(_set); } diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index f93709760..b19a025cf 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -212,9 +212,7 @@ contract('Auction', accounts => { const auctionSetup: any = await auctionMock.auction.callAsync(); - const pricePrecision = auctionSetup.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -262,7 +260,6 @@ contract('Auction', accounts => { set1, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision ); expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -279,7 +276,6 @@ contract('Auction', accounts => { set2, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision ); expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -311,7 +307,6 @@ contract('Auction', accounts => { await coreHelper.getSetInstance(subjectNextSet), oracleWhiteList ); - console.log(auctionSetup.pricePrecision); expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); }); @@ -342,14 +337,13 @@ contract('Auction', accounts => { await coreHelper.getSetInstance(subjectNextSet), oracleWhiteList ); - console.log(auctionSetup.pricePrecision); expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); }); describe('when there is insufficient collateral to rebalance', async () => { beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(10); + subjectStartingCurrentSetQuantity = gWei(1).div(10); }); it('should revert', async () => { @@ -433,10 +427,7 @@ contract('Auction', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auctionSetup: any = await auctionMock.auction.callAsync(); - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(auctionSetup.pricePrecision) - .div(2); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 92c46a158..3d92d93c6 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -23,7 +23,7 @@ import { Blockchain } from '@utils/blockchain'; import { getWeb3 } from '@utils/web3Helper'; import { DEFAULT_GAS, - ONE_DAY_IN_SECONDS + ONE_DAY_IN_SECONDS, } from '@utils/constants'; import { ether, gWei } from '@utils/units'; import { getLinearAuction, TokenFlow } from '@utils/auction'; @@ -210,9 +210,7 @@ contract('LinearAuction', accounts => { const auction: any = await auctionMock.auction.callAsync(); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -241,7 +239,7 @@ contract('LinearAuction', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -254,10 +252,10 @@ contract('LinearAuction', accounts => { ); const rangeStart = await auctionMock.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -270,7 +268,7 @@ contract('LinearAuction', accounts => { ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); describe('when currentSet is greater than 10x the nextSet', async () => { @@ -303,7 +301,7 @@ contract('LinearAuction', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -317,10 +315,10 @@ contract('LinearAuction', accounts => { const rangeStart = await auctionMock.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -334,7 +332,7 @@ contract('LinearAuction', accounts => { const rangeEnd = await auctionMock.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); }); @@ -356,9 +354,9 @@ contract('LinearAuction', accounts => { ); }); - describe('#getNumerator', async () => { + describe('#getPrice', async () => { async function subject(): Promise { - return auctionMock.getNumerator.callAsync(); + return auctionMock.getPrice.callAsync(); } it('returns the correct result', async () => { @@ -423,7 +421,7 @@ contract('LinearAuction', accounts => { } it('returns the correct numerator', async () => { - const { numerator } = await subject(); + const numerator = await subject(); const { timestamp } = await web3.eth.getBlock('latest'); const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); const currentPrice = await liquidatorHelper.calculateCurrentPrice( @@ -433,13 +431,6 @@ contract('LinearAuction', accounts => { ); expect(numerator).to.bignumber.equal(currentPrice); }); - - it('returns the correct denominator', async () => { - const { denominator } = await subject(); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(denominator).to.bignumber.equal(linearAuction.auction.pricePrecision); - }); }); describe('#getTokenFlow', async () => { @@ -464,7 +455,6 @@ contract('LinearAuction', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); @@ -512,7 +502,6 @@ contract('LinearAuction', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 6aa0ef60b..2fead16f4 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,9 +249,7 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -280,7 +278,7 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -293,10 +291,10 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeStart = await liquidator.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -309,7 +307,7 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeEnd = await liquidator.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); describe('when the currentSet is > 10x nextSet', async () => { @@ -342,7 +340,7 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -355,10 +353,10 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeStart = await liquidator.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -371,7 +369,7 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeEnd = await liquidator.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); @@ -426,6 +424,20 @@ contract('LinearAuctionLiquidator', accounts => { }); async function subject(): Promise { + return liquidatorProxy.placeBid.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + async function directCallSubject(): Promise { + return liquidator.placeBid.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + async function setTokenFlows(): Promise { const linearAuction = getLinearAuction(await liquidator.auctions.callAsync(liquidatorProxy.address)); const { timestamp } = await web3.eth.getBlock('latest'); @@ -440,19 +452,6 @@ contract('LinearAuctionLiquidator', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, - ); - - return liquidatorProxy.placeBid.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - async function directCallSubject(): Promise { - return liquidator.placeBid.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, ); } @@ -466,6 +465,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct combinedTokenArray', async () => { await subject(); + await setTokenFlows(); const combinedTokenArray = await liquidatorProxy.getCombinedTokenArray.callAsync(); expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(tokenFlows.addresses)); @@ -473,6 +473,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct inflow', async () => { await subject(); + await setTokenFlows(); const inflow = await liquidatorProxy.getInflow.callAsync(); expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); @@ -480,6 +481,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct outflow', async () => { await subject(); + await setTokenFlows(); const outflow = await liquidatorProxy.getOutflow.callAsync(); expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); @@ -487,12 +489,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); - - const pricePrecision = auction.auction.pricePrecision; - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision) - .div(2); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); @@ -554,7 +551,6 @@ contract('LinearAuctionLiquidator', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); @@ -616,8 +612,8 @@ contract('LinearAuctionLiquidator', accounts => { expect(JSON.stringify(auction.auction.combinedCurrentSetUnits)).to.equal(JSON.stringify([])); expect(JSON.stringify(auction.auction.combinedNextSetUnits)).to.equal(JSON.stringify([])); expect(auction.endTime).to.bignumber.equal(ZERO); - expect(auction.startNumerator).to.bignumber.equal(ZERO); - expect(auction.endNumerator).to.bignumber.equal(ZERO); + expect(auction.startPrice).to.bignumber.equal(ZERO); + expect(auction.endPrice).to.bignumber.equal(ZERO); }); describe('when there is a biddable quantity', async () => { @@ -666,8 +662,8 @@ contract('LinearAuctionLiquidator', accounts => { expect(JSON.stringify(auction.auction.combinedCurrentSetUnits)).to.equal(JSON.stringify([])); expect(JSON.stringify(auction.auction.combinedNextSetUnits)).to.equal(JSON.stringify([])); expect(auction.endTime).to.bignumber.equal(ZERO); - expect(auction.startNumerator).to.bignumber.equal(ZERO); - expect(auction.endNumerator).to.bignumber.equal(ZERO); + expect(auction.startPrice).to.bignumber.equal(ZERO); + expect(auction.endPrice).to.bignumber.equal(ZERO); }); describe('when the caller is not a valid Set', async () => { @@ -777,8 +773,8 @@ contract('LinearAuctionLiquidator', accounts => { const linearAuction = getLinearAuction(await liquidator.auctions.callAsync(subjectSet)); expect(auctionStartTime).to.bignumber.equal(linearAuction.auction.startTime); expect(auctionTimeToPivot).to.bignumber.equal(linearAuction.endTime); - expect(auctionStartPrice).to.bignumber.equal(linearAuction.startNumerator); - expect(auctionPivotPrice).to.bignumber.equal(linearAuction.endNumerator); + expect(auctionStartPrice).to.bignumber.equal(linearAuction.startPrice); + expect(auctionPivotPrice).to.bignumber.equal(linearAuction.endPrice); }); }); }); diff --git a/utils/auction.ts b/utils/auction.ts index c1ac29d0a..319e9e0dd 100644 --- a/utils/auction.ts +++ b/utils/auction.ts @@ -7,8 +7,8 @@ import { export interface LinearAuction { auction: Auction; endTime: BigNumber; - startNumerator: BigNumber; - endNumerator: BigNumber; + startPrice: BigNumber; + endPrice: BigNumber; } export interface Price { @@ -56,7 +56,7 @@ export function getLinearAuction(input: any): LinearAuction { combinedNextSetUnits: combinedNextSetUnits.map(v => new BigNumber(v)), }, endTime: new BigNumber(input.endTime), - startNumerator: new BigNumber(input.startNumerator), - endNumerator: new BigNumber(input.endNumerator), + startPrice: new BigNumber(input.startPrice), + endPrice: new BigNumber(input.endPrice), }; } \ No newline at end of file diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index af5ff22d4..af3df4080 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -15,6 +15,7 @@ import { import { getContractInstance, txnFrom } from '../web3Helper'; import { ZERO, + SCALE_FACTOR, } from '../constants'; import { LinearAuction, @@ -151,21 +152,17 @@ export class LiquidatorHelper { setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, - priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); - // Calculate minimumBidAmount - const maxNaturalUnit = minimumBid.div(priceDivisor); - // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); @@ -180,10 +177,10 @@ export class LiquidatorHelper { auctionPeriod: BigNumber ): BigNumber { const elapsed = timestamp.sub(linearAuction.auction.startTime); - const priceRange = new BigNumber(linearAuction.endNumerator).sub(linearAuction.startNumerator); + const priceRange = new BigNumber(linearAuction.endPrice).sub(linearAuction.startPrice); const elapsedPrice = elapsed.mul(priceRange).div(auctionPeriod).round(0, 3); - return new BigNumber(linearAuction.startNumerator).add(elapsedPrice); + return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } public calculateStartPrice( @@ -212,7 +209,7 @@ export class LiquidatorHelper { const currentSetUSDValue = await this.calculateSetTokenValueAsync(currentSetToken, oracleWhiteList); const nextSetUSDValue = await this.calculateSetTokenValueAsync(nextSetToken, oracleWhiteList); - return nextSetUSDValue.mul(pricePrecision).div(currentSetUSDValue).round(0, 3); + return nextSetUSDValue.mul(SCALE_FACTOR).div(currentSetUSDValue).round(0, 3); } public async calculateSetTokenValueAsync( @@ -292,8 +289,7 @@ export class LiquidatorHelper { linearAuction: LinearAuction, pricePrecision: BigNumber, quantity: BigNumber, - priceNumerator: BigNumber, - priceDenominator: BigNumber, + priceScaled: BigNumber, ): TokenFlow { const inflow: BigNumber[] = []; const outflow: BigNumber[] = []; @@ -309,17 +305,17 @@ export class LiquidatorHelper { const unitsMultiplier = quantity.div(minimumBid).round(0, 3).mul(pricePrecision); for (let i = 0; i < combinedCurrentSetUnits.length; i++) { - const flow = combinedNextSetUnits[i].mul(priceDenominator).sub(combinedCurrentSetUnits[i].mul(priceNumerator)); + const flow = combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)); if (flow.greaterThan(0)) { const inflowUnit = unitsMultiplier.mul( - combinedNextSetUnits[i].mul(priceDenominator).sub(combinedCurrentSetUnits[i].mul(priceNumerator)) - ).div(priceNumerator).round(0, 3); + combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)) + ).div(priceScaled).round(0, 3); inflow.push(inflowUnit); outflow.push(ZERO); } else { const outflowUnit = unitsMultiplier.mul( - combinedCurrentSetUnits[i].mul(priceNumerator).sub(combinedNextSetUnits[i].mul(priceDenominator)) - ).div(priceNumerator).round(0, 3); + combinedCurrentSetUnits[i].mul(priceScaled).sub(combinedNextSetUnits[i].mul(SCALE_FACTOR)) + ).div(priceScaled).round(0, 3); outflow.push(outflowUnit); inflow.push(ZERO); } From b491f896e4e045a3ef75776a1c513ab1dbdffc8a Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:25:08 -0800 Subject: [PATCH 02/35] FIx --- contracts/core/liquidators/impl/LinearAuction.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 93faa55d4..056eb61fe 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -146,7 +146,7 @@ contract LinearAuction is Auction { } /** - * Returns the linear price based on the current timestamp. Returns the endNumerator + * Returns the linear price based on the current timestamp. Returns the endPrice * if time has exceeded the auciton period * * @param _linearAuction Linear Auction State object @@ -157,12 +157,12 @@ contract LinearAuction is Auction { // If current time has elapsed if (elapsed >= auctionPeriod) { - return _linearAuction.endNumerator; + return _linearAuction.endPrice; } else { - uint256 range = _linearAuction.endNumerator.sub(_linearAuction.startNumerator); + uint256 range = _linearAuction.endPrice.sub(_linearAuction.startPrice); uint256 elapsedPrice = elapsed.mul(range).div(auctionPeriod); - return _linearAuction.startNumerator.add(elapsedPrice); + return _linearAuction.startPrice.add(elapsedPrice); } } From 21d5bb7ca096bf4ec553bf42b5ec396254be4e82 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:31:24 -0800 Subject: [PATCH 03/35] Fix compile --- test/contracts/core/liquidators/impl/linearAuction.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 3d92d93c6..f9ec08fd7 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -406,11 +406,11 @@ contract('LinearAuction', accounts => { ); }); - it('returns the correct price / endNumerator', async () => { + it('returns the correct price / endPrice', async () => { const result = await subject(); const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(result).to.bignumber.equal(linearAuction.endNumerator); + expect(result).to.bignumber.equal(linearAuction.endPrice); }); }); }); From cab0ca87faf213cf3da14d9734b1821717d549d8 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 15:55:14 -0800 Subject: [PATCH 04/35] Whats going on --- contracts/core/liquidators/impl/Auction.sol | 16 ++++++++++++---- package.json | 2 +- .../rebalancingLinearLiquidator.spec.ts | 2 +- .../core/liquidators/impl/auction.spec.ts | 12 +++++++++--- .../core/liquidators/impl/linearAuction.spec.ts | 4 +++- .../liquidators/linearAuctionLiquidator.spec.ts | 11 +++++++++-- utils/helpers/liquidatorHelper.ts | 6 +++++- 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 9adb55512..cf4a525d9 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -242,7 +242,8 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit); + return Math.max(currentSetNaturalUnit, nextNaturalUnit) + .mul(_pricePrecision); } /** @@ -357,11 +358,13 @@ contract Auction { returns (uint256[] memory) { address[] memory combinedTokenArray = _auction.combinedTokenArray; + uint256 pricePrecisionMem = _auction.pricePrecision; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, + pricePrecisionMem, combinedTokenArray[i] ); } @@ -374,11 +377,13 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount + * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, + uint256 _pricePrecision, uint256 _minimumBid, address _component ) @@ -397,7 +402,8 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid + _minimumBid, + _pricePrecision ); } @@ -411,18 +417,20 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount + * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid + uint256 _minimumBid, + uint256 _pricePrecision ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit).div(_pricePrecision); } /** diff --git a/package.json b/package.json index c43ee4187..85c5dd693 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-nocompile": "yarn transpile && yarn truffle-test-contracts", "test-continuous": "yarn deploy-development && truffle test", "transpile": "tsc", - "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", + "truffle-test-contracts": "truffle test `find transpiled/test/contracts/core/integration -name '*.spec.js'`", "prepublishOnly": "yarn dist", "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 04ada9ec9..1802c9967 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -428,7 +428,7 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); - describe('#settleRebalance', async () => { + describe.only('#settleRebalance', async () => { let subjectCaller: Address; let nextSetToken: SetTokenContract; diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index b19a025cf..19a50bf31 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -212,7 +212,8 @@ contract('Auction', accounts => { const auctionSetup: any = await auctionMock.auction.callAsync(); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auctionSetup.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -260,6 +261,7 @@ contract('Auction', accounts => { set1, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), + auctionSetup.pricePrecision ); expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -276,6 +278,7 @@ contract('Auction', accounts => { set2, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), + auctionSetup.pricePrecision ); expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -343,7 +346,7 @@ contract('Auction', accounts => { describe('when there is insufficient collateral to rebalance', async () => { beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(1).div(10); + subjectStartingCurrentSetQuantity = gWei(10); }); it('should revert', async () => { @@ -427,7 +430,10 @@ contract('Auction', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + const auctionSetup: any = await auctionMock.auction.callAsync(); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(auctionSetup.pricePrecision) + .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index f9ec08fd7..0d8142beb 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -210,7 +210,9 @@ contract('LinearAuction', accounts => { const auction: any = await auctionMock.auction.callAsync(); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auction.auction.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 2fead16f4..5a91ee524 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,7 +249,9 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auction.auction.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -489,7 +491,12 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); + + const pricePrecision = auction.auction.pricePrecision; + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision) + .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index af3df4080..5380b3f26 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -152,17 +152,21 @@ export class LiquidatorHelper { setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, + priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); + // Calculate minimumBidAmount + const maxNaturalUnit = minimumBid.div(priceDivisor); + // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); From db49bc37e9d3c834cd60ee7cab8a2274577926d4 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 16:02:47 -0800 Subject: [PATCH 05/35] Fix --- contracts/core/liquidators/impl/Auction.sol | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index cf4a525d9..344fb6ea9 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -383,8 +383,8 @@ contract Auction { */ function calculateCombinedUnit( ISetToken _setToken, - uint256 _pricePrecision, uint256 _minimumBid, + uint256 _pricePrecision, address _component ) private @@ -430,7 +430,8 @@ contract Auction { pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit).div(_pricePrecision); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) + .div(_pricePrecision); } /** diff --git a/package.json b/package.json index 85c5dd693..c43ee4187 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-nocompile": "yarn transpile && yarn truffle-test-contracts", "test-continuous": "yarn deploy-development && truffle test", "transpile": "tsc", - "truffle-test-contracts": "truffle test `find transpiled/test/contracts/core/integration -name '*.spec.js'`", + "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, From 1469eb213b5bda8a278fcdd33d2267190670a22e Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 16:24:57 -0800 Subject: [PATCH 06/35] remove.only --- .../core/integration/rebalancingLinearLiquidator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 1802c9967..04ada9ec9 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -428,7 +428,7 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); - describe.only('#settleRebalance', async () => { + describe('#settleRebalance', async () => { let subjectCaller: Address; let nextSetToken: SetTokenContract; From dbe204b22985b6967ad0fca31e903d1ff0a35d7f Mon Sep 17 00:00:00 2001 From: bweick Date: Sun, 5 Jan 2020 19:16:49 -0800 Subject: [PATCH 07/35] WIP. Added math to calculate bounds of auction. Rough integration in LinearAuction contract. --- contracts/core/liquidators/impl/Auction.sol | 1 - .../core/liquidators/impl/LinearAuction.sol | 45 +++- .../impl/TwoAssetAuctionBoundsCalculator.sol | 101 ++++++++ .../TwoAssetAuctionBoundsCalculatorMock.sol | 73 ++++++ .../twoAssetAuctionBoundsCalculator.spec.ts | 223 ++++++++++++++++++ utils/contracts.ts | 3 + utils/helpers/liquidatorHelper.ts | 80 +++++++ 7 files changed, 515 insertions(+), 11 deletions(-) create mode 100644 contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol create mode 100644 contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol create mode 100644 test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 344fb6ea9..fb4158811 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -17,7 +17,6 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; -import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 056eb61fe..5528da9d9 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -23,6 +23,7 @@ import { Auction } from "./Auction.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; +import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculator.sol"; /** @@ -31,7 +32,7 @@ import { Rebalance } from "../../lib/Rebalance.sol"; * * Library containing utility functions for computing auction Price for a linear price auction. */ -contract LinearAuction is Auction { +contract LinearAuction is TwoAssetAuctionBoundsCalculator { using SafeMath for uint256; /* ============ Structs ============ */ @@ -62,7 +63,7 @@ contract LinearAuction is Auction { uint256 _rangeEnd ) public - Auction(_oracleWhiteList) + TwoAssetAuctionBoundsCalculator(_oracleWhiteList) { auctionPeriod = _auctionPeriod; rangeStart = _rangeStart; @@ -95,8 +96,8 @@ contract LinearAuction is Auction { ); uint256 fairValue = calculateFairValue(_currentSet, _nextSet); - _linearAuction.startPrice = calculateStartPrice(fairValue); - _linearAuction.endPrice = calculateEndPrice(fairValue); + _linearAuction.startPrice = calculateStartPrice(_linearAuction, fairValue); + _linearAuction.endPrice = calculateEndPrice(_linearAuction, fairValue); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -187,16 +188,40 @@ contract LinearAuction is Auction { /** * Calculates the linear auction start price with a scaled value */ - function calculateStartPrice(uint256 _fairValueScaled) internal view returns(uint256) { - uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); - return _fairValueScaled.sub(startRange); + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.sub(startDifference); } /** * Calculates the linear auction end price with a scaled value */ - function calculateEndPrice(uint256 _fairValueScaled) internal view returns(uint256) { - uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); - return _fairValueScaled.add(endRange); + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.add(endDifference); } } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol b/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol new file mode 100644 index 000000000..81dd2780d --- /dev/null +++ b/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol @@ -0,0 +1,101 @@ +/* + Copyright 2019 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.5.7; +pragma experimental "ABIEncoderV2"; + +import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; + +import { Auction } from "./Auction.sol"; +import { CommonMath } from "../../../lib/CommonMath.sol"; +import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; + + +/** + * @title TwoAssetAuctionBoundsCalculator + * @author Set Protocol + * + */ +contract TwoAssetAuctionBoundsCalculator is Auction { + using SafeMath for uint256; + using CommonMath for uint256; + + struct AssetInfo { + uint256 assetPrice; + uint256 fullUnit; + } + + uint256 constant private ONE_HUNDRED = 100; + + constructor( + IOracleWhiteList _oracleWhiteList + ) + public + Auction(_oracleWhiteList) + {} + + function calculateAuctionBoundDifference( + Auction.Setup storage _auction, + uint256 _fairValue, + uint256 _boundValue + ) + internal + view + returns (uint256) + { + AssetInfo memory baseAsset = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory quoteAsset = getAssetInfo(_auction.combinedTokenArray[1]); + + uint256 numDifferential; + if (_auction.combinedNextSetUnits[0].scale() > _fairValue.mul(_auction.combinedCurrentSetUnits[0])) { + numDifferential = _auction.combinedNextSetUnits[0].scale().sub(_fairValue.mul(_auction.combinedCurrentSetUnits[0])); + } else { + numDifferential = _fairValue.mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].scale()); + } + + uint256 denomDifferential; + if (_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]) > _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])) { + denomDifferential = _auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]).sub(_auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])); + } else { + denomDifferential = _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1])); + } + + uint256 calcNumerator = quoteAsset.fullUnit + .mul(numDifferential) + .mul(_boundValue) + .mul(baseAsset.assetPrice) + .div(quoteAsset.assetPrice) + .div(ONE_HUNDRED); + + uint256 calcDenominator = baseAsset.fullUnit.mul(denomDifferential).scale(); + + return calcNumerator.scale().div(calcDenominator).mul(numDifferential).deScale(); + } + + function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { + address assetOracle = oracleWhiteList.getOracleAddressByToken(_asset); + uint256 assetPrice = IOracle(assetOracle).read(); + + uint256 decimals = ERC20Detailed(_asset).decimals(); + + return AssetInfo({ + assetPrice: assetPrice, + fullUnit: CommonMath.safePower(10, decimals) + }); + } +} \ No newline at end of file diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol new file mode 100644 index 000000000..972c516ef --- /dev/null +++ b/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol @@ -0,0 +1,73 @@ +/* + Copyright 2019 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.5.7; +pragma experimental "ABIEncoderV2"; + +import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; + +import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; +import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; +import { TwoAssetAuctionBoundsCalculator } from "../../../../core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol"; + +/** + * @title TwoAssetAuctionBoundsCalculator + * @author Set Protocol + * + */ +contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator { + + Auction.Setup public auctionInfo; + + constructor( + IOracleWhiteList _oracleWhiteList + ) + public + TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + {} + + function calculateAuctionBoundDifferenceMock( + uint256 _fairValue, + uint256 _rangeStart + ) + external + view + returns (uint256) + { + return calculateAuctionBoundDifference(auctionInfo, _fairValue, _rangeStart); + } + + function parameterizeAuction( + address[] calldata _combinedTokenArray, + uint256[] calldata _combinedCurrentSetUnits, + uint256[] calldata _combinedNextSetUnits + ) + external + { + auctionInfo.combinedTokenArray = _combinedTokenArray; + auctionInfo.combinedCurrentSetUnits = _combinedCurrentSetUnits; + auctionInfo.combinedNextSetUnits = _combinedNextSetUnits; + } + + function getCombinedTokenArray() + external + view + returns(address[] memory) + { + return auctionInfo.combinedTokenArray; + } +} \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts b/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts new file mode 100644 index 000000000..9a135a15a --- /dev/null +++ b/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts @@ -0,0 +1,223 @@ +require('module-alias/register'); + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + StandardTokenMockContract, + OracleWhiteListContract, + TwoAssetAuctionBoundsCalculatorMockContract, + UpdatableOracleMockContract, +} from '@utils/contracts'; +import { ether } from '@utils/units'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const { expect } = chai; + +contract('TwoAssetAuctionBoundsCalculator', accounts => { + const [ + ownerAccount, + ] = accounts; + + let boundsCalculator: TwoAssetAuctionBoundsCalculatorMockContract; + let oracleWhiteList: OracleWhiteListContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + const libraryMockHelper = new LibraryMockHelper(ownerAccount); + + let wrappedETH: StandardTokenMockContract; + let wrappedBTC: StandardTokenMockContract; + let usdc: StandardTokenMockContract; + + let wrappedETHPrice: BigNumber; + let wrappedBTCPrice: BigNumber; + let usdcPrice: BigNumber; + + let wrappedETHOracle: UpdatableOracleMockContract; + let wrappedBTCOracle: UpdatableOracleMockContract; + let usdcOracle: UpdatableOracleMockContract; + + before(async () => { + wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); + wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); + usdc = await erc20Helper.deployTokenAsync(ownerAccount, 6); + + wrappedETHPrice = ether(128); + wrappedBTCPrice = ether(7500); + usdcPrice = ether(1); + + wrappedETHOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedETHPrice); + wrappedBTCOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedBTCPrice); + usdcOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(usdcPrice); + + oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( + [wrappedETH.address, wrappedBTC.address, usdc.address], + [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address], + ); + + boundsCalculator = await liquidatorHelper.deployTwoAssetAuctionBoundsCalculatorMock( + oracleWhiteList.address + ); + }); + + describe.only('#calculateStartNumerator', async () => { + let subjectFairValue: BigNumber; + let subjectRangeStart: BigNumber; + + let combinedTokenArray: Address[]; + let combinedCurrentSetUnits: BigNumber[]; + let combinedNextSetUnits: BigNumber[]; + + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + }); + + beforeEach(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + + await boundsCalculator.parameterizeAuction.sendTransactionAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits + ); + + subjectFairValue = ether(5); + subjectRangeStart = new BigNumber(3); + }); + + async function subject(): Promise { + return boundsCalculator.calculateAuctionBoundDifferenceMock.callAsync( + subjectFairValue, + subjectRangeStart + ); + } + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + + describe('when asset order is flipped', async () => { + before(async () => { + combinedTokenArray = [usdc.address, wrappedETH.address]; + combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; + combinedNextSetUnits = [new BigNumber(1152), new BigNumber(10 ** 12)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('when other asset is higher allocation', async () => { + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(9 * 10 ** 12), new BigNumber(128)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('when other asset is highest allocation and assets are flipped', async () => { + before(async () => { + combinedTokenArray = [usdc.address, wrappedETH.address]; + combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; + combinedNextSetUnits = [new BigNumber(1152), new BigNumber(9 * 10 ** 12)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('different allocation', async () => { + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(192)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + }); +}); \ No newline at end of file diff --git a/utils/contracts.ts b/utils/contracts.ts index ee4e418f1..ae485610e 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -70,6 +70,9 @@ export { TimeLockUpgradeV2Contract } from '../types/generated/time_lock_upgrade_ export { TimeLockUpgradeV2MockContract } from '../types/generated/time_lock_upgrade_v2_mock'; export { TransferProxyContract } from '../types/generated/transfer_proxy'; export { TradingPoolViewerContract } from '../types/generated/trading_pool_viewer'; +export { + TwoAssetAuctionBoundsCalculatorMockContract +} from '../types/generated/two_asset_auction_bounds_calculator_mock'; export { UpdatableConstantAuctionPriceCurveContract } from '../types/generated/updatable_constant_auction_price_curve'; export { UpdatableOracleMockContract } from '../types/generated/updatable_oracle_mock'; export { VaultContract } from '../types/generated/vault'; diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 5380b3f26..4fc3ffb3a 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -11,6 +11,7 @@ import { LiquidatorProxyContract, OracleWhiteListContract, SetTokenContract, + TwoAssetAuctionBoundsCalculatorMockContract, } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { @@ -21,12 +22,14 @@ import { LinearAuction, TokenFlow } from '../auction'; +import { ether } from '@utils/units'; const AuctionMock = artifacts.require('AuctionMock'); const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); const LinearAuctionMock = artifacts.require('LinearAuctionMock'); const LiquidatorMock = artifacts.require('LiquidatorMock'); const LiquidatorProxy = artifacts.require('LiquidatorProxy'); +const TwoAssetAuctionBoundsCalculatorMock = artifacts.require('TwoAssetAuctionBoundsCalculatorMock'); import { ERC20Helper } from './erc20Helper'; import { LibraryMockHelper } from './libraryMockHelper'; @@ -110,6 +113,21 @@ export class LiquidatorHelper { ); } + public async deployTwoAssetAuctionBoundsCalculatorMock( + oracleWhiteList: Address, + from: Address = this._contractOwnerAddress + ): Promise { + const twoAssetAuctionBoundsCalculatorMock = await TwoAssetAuctionBoundsCalculatorMock.new( + oracleWhiteList, + txnFrom(from) + ); + + return new TwoAssetAuctionBoundsCalculatorMockContract( + getContractInstance(twoAssetAuctionBoundsCalculatorMock), + txnFrom(from) + ); + } + public async deployLiquidatorMockAsync( from: Address = this._contractOwnerAddress ): Promise { @@ -175,6 +193,68 @@ export class LiquidatorHelper { return combinedSetTokenUnits; } + public async calculateAuctionBoundsAsync( + combinedTokenArray: Address[], + combinedCurrentUnitArray: BigNumber[], + combinedNextUnitArray: BigNumber[], + fairValue: BigNumber, + startBound: BigNumber, + endBound: BigNumber, + oracleWhiteList: OracleWhiteListContract + ): Promise<[BigNumber, BigNumber]> { + const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync(combinedTokenArray); + + const assetOneFullUnit = new BigNumber(10 ** assetOneDecimals.toNumber()); + const assetTwoFullUnit = new BigNumber(10 ** assetTwoDecimals.toNumber()); + + const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync(combinedTokenArray, oracleWhiteList); + + const startValue = this.calculateAuctionBound( + combinedCurrentUnitArray, + combinedNextUnitArray, + assetOneFullUnit, + assetTwoFullUnit, + assetOnePrice.div(assetTwoPrice), + fairValue, + startBound + ); + + const endValue = this.calculateAuctionBound( + combinedCurrentUnitArray, + combinedNextUnitArray, + assetOneFullUnit, + assetTwoFullUnit, + assetOnePrice.div(assetTwoPrice), + fairValue, + endBound + ); + + return [startValue, endValue]; + } + + public calculateAuctionBound( + combinedCurrentUnitArray: BigNumber[], + combinedNextUnitArray: BigNumber[], + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + assetPairPrice: BigNumber, + fairValue: BigNumber, + boundValue: BigNumber + ): BigNumber { + const numerator = (combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0]))).pow(2) + .mul(assetTwoFullUnit) + .mul(boundValue) + .mul(assetPairPrice) + .div(100); + + const denominator = combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]).sub( + combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) + .mul(assetOneFullUnit) + .mul(10 ** 18); + + return numerator.div(denominator).abs(); + } + public calculateCurrentPrice( linearAuction: LinearAuction, timestamp: BigNumber, From 406569cba9a0a5ecf462ba8ae672c6b059f0e5f5 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Sun, 5 Jan 2020 20:55:20 -0800 Subject: [PATCH 08/35] Changes (#560) --- .../liquidators/LinearAuctionLiquidator.sol | 5 +- .../core/liquidators/impl/LinearAuction.sol | 29 ++-------- ... => TwoAssetPriceBoundedLinearAuction.sol} | 58 +++++++++++++++++-- .../liquidators/impl/LinearAuctionMock.sol | 24 ++++++++ ...TwoAssetPriceBoundedLinearAuctionMock.sol} | 32 ++++++---- package.json | 2 +- ...twoAssetPriceBoundedLinearAuction.spec.ts} | 23 ++++++-- utils/contracts.ts | 4 +- utils/helpers/liquidatorHelper.ts | 20 ++++--- 9 files changed, 140 insertions(+), 57 deletions(-) rename contracts/core/liquidators/impl/{TwoAssetAuctionBoundsCalculator.sol => TwoAssetPriceBoundedLinearAuction.sol} (72%) rename contracts/mocks/core/liquidators/impl/{TwoAssetAuctionBoundsCalculatorMock.sol => TwoAssetPriceBoundedLinearAuctionMock.sol} (59%) rename test/contracts/core/liquidators/impl/{twoAssetAuctionBoundsCalculator.spec.ts => twoAssetPriceBoundedLinearAuction.spec.ts} (91%) diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index a4e99a896..bcffb51e1 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -27,6 +27,7 @@ import { Auction } from "./impl/Auction.sol"; import { LinearAuction } from "./impl/LinearAuction.sol"; import { Rebalance } from "../lib/Rebalance.sol"; import { RebalancingLibrary } from "../lib/RebalancingLibrary.sol"; +import { TwoAssetPriceBoundedLinearAuction } from "./impl/TwoAssetPriceBoundedLinearAuction.sol"; /** @@ -36,7 +37,7 @@ import { RebalancingLibrary } from "../lib/RebalancingLibrary.sol"; * Contract that holds all the state and functionality required for setting up, returning prices, and tearing * down linear auction rebalances for RebalancingSetTokens. */ -contract LinearAuctionLiquidator is LinearAuction, ILiquidator { +contract LinearAuctionLiquidator is TwoAssetPriceBoundedLinearAuction, ILiquidator { using SafeMath for uint256; ICore public core; @@ -68,7 +69,7 @@ contract LinearAuctionLiquidator is LinearAuction, ILiquidator { string memory _name ) public - LinearAuction( + TwoAssetPriceBoundedLinearAuction( _oracleWhiteList, _auctionPeriod, _rangeStart, diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 5528da9d9..1526bef05 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -23,7 +23,6 @@ import { Auction } from "./Auction.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; -import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculator.sol"; /** @@ -32,7 +31,7 @@ import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculat * * Library containing utility functions for computing auction Price for a linear price auction. */ -contract LinearAuction is TwoAssetAuctionBoundsCalculator { +contract LinearAuction is Auction { using SafeMath for uint256; /* ============ Structs ============ */ @@ -63,7 +62,7 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { uint256 _rangeEnd ) public - TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + Auction(_oracleWhiteList) { auctionPeriod = _auctionPeriod; rangeStart = _rangeStart; @@ -186,6 +185,7 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { } /** + * Abstract function that must be implemented. * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( @@ -194,18 +194,10 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { ) internal view - returns(uint256) - { - uint256 startDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart - ); - - return _fairValueScaled.sub(startDifference); - } + returns(uint256); /** + * Abstract function that must be implemented. * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( @@ -214,14 +206,5 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { ) internal view - returns(uint256) - { - uint256 endDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart - ); - - return _fairValueScaled.add(endDifference); - } + returns(uint256); } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol similarity index 72% rename from contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol rename to contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 81dd2780d..6505d2d28 100644 --- a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -22,16 +22,17 @@ import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { Auction } from "./Auction.sol"; +import { LinearAuction } from "./LinearAuction.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; /** - * @title TwoAssetAuctionBoundsCalculator + * @title TwoAssetPriceBoundedLinearAuction * @author Set Protocol * */ -contract TwoAssetAuctionBoundsCalculator is Auction { +contract TwoAssetPriceBoundedLinearAuction is LinearAuction { using SafeMath for uint256; using CommonMath for uint256; @@ -43,12 +44,61 @@ contract TwoAssetAuctionBoundsCalculator is Auction { uint256 constant private ONE_HUNDRED = 100; constructor( - IOracleWhiteList _oracleWhiteList + IOracleWhiteList _oracleWhiteList, + uint256 _auctionPeriod, + uint256 _rangeStart, + uint256 _rangeEnd ) public - Auction(_oracleWhiteList) + LinearAuction( + _oracleWhiteList, + _auctionPeriod, + _rangeStart, + _rangeEnd + ) {} + /** + * Calculates the linear auction start price with a scaled value + */ + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startDifference = calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.sub(startDifference); + } + + /** + * Calculates the linear auction end price with a scaled value + */ + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endDifference = calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.add(endDifference); + } + + function calculateAuctionBoundDifference( Auction.Setup storage _auction, uint256 _fairValue, diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 99254651e..db67331b5 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -26,6 +26,30 @@ contract LinearAuctionMock is LinearAuction { ) {} + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); + return _fairValueScaled.sub(startRange); + } + + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); + return _fairValueScaled.add(endRange); + } + function initializeLinearAuction( ISetToken _currentSet, ISetToken _nextSet, diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol similarity index 59% rename from contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol rename to contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 972c516ef..4fd8935c9 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -20,24 +20,32 @@ pragma experimental "ABIEncoderV2"; import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; -import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; +import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; -import { TwoAssetAuctionBoundsCalculator } from "../../../../core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol"; +import { TwoAssetPriceBoundedLinearAuction } from "../../../../core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol"; /** - * @title TwoAssetAuctionBoundsCalculator + * @title TwoAssetPriceBoundedLinearAuction * @author Set Protocol * */ -contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator { +contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuction { - Auction.Setup public auctionInfo; + LinearAuction.State public auctionInfo; constructor( - IOracleWhiteList _oracleWhiteList + IOracleWhiteList _oracleWhiteList, + uint256 _auctionPeriod, + uint256 _rangeStart, + uint256 _rangeEnd ) public - TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + TwoAssetPriceBoundedLinearAuction( + _oracleWhiteList, + _auctionPeriod, + _rangeStart, + _rangeEnd + ) {} function calculateAuctionBoundDifferenceMock( @@ -48,7 +56,7 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator view returns (uint256) { - return calculateAuctionBoundDifference(auctionInfo, _fairValue, _rangeStart); + return calculateAuctionBoundDifference(auctionInfo.auction, _fairValue, _rangeStart); } function parameterizeAuction( @@ -58,9 +66,9 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator ) external { - auctionInfo.combinedTokenArray = _combinedTokenArray; - auctionInfo.combinedCurrentSetUnits = _combinedCurrentSetUnits; - auctionInfo.combinedNextSetUnits = _combinedNextSetUnits; + auctionInfo.auction.combinedTokenArray = _combinedTokenArray; + auctionInfo.auction.combinedCurrentSetUnits = _combinedCurrentSetUnits; + auctionInfo.auction.combinedNextSetUnits = _combinedNextSetUnits; } function getCombinedTokenArray() @@ -68,6 +76,6 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator view returns(address[] memory) { - return auctionInfo.combinedTokenArray; + return auctionInfo.auction.combinedTokenArray; } } \ No newline at end of file diff --git a/package.json b/package.json index c43ee4187..86f8e15b9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "transpile": "tsc", "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", - "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" + "flatten": "truffle-flattener contracts/core/tokens/RebalancingSetTokenV2Factory.sol" }, "repository": "git@github.com:SetProtocol/set-protocol-contracts.git", "author": "Felix Feng ", diff --git a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts similarity index 91% rename from test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts rename to test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 9a135a15a..c15426856 100644 --- a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -10,7 +10,7 @@ import { BigNumberSetup } from '@utils/bigNumberSetup'; import { StandardTokenMockContract, OracleWhiteListContract, - TwoAssetAuctionBoundsCalculatorMockContract, + TwoAssetPriceBoundedLinearAuctionMockContract, UpdatableOracleMockContract, } from '@utils/contracts'; import { ether } from '@utils/units'; @@ -24,12 +24,12 @@ BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; -contract('TwoAssetAuctionBoundsCalculator', accounts => { +contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ ownerAccount, ] = accounts; - let boundsCalculator: TwoAssetAuctionBoundsCalculatorMockContract; + let boundsCalculator: TwoAssetPriceBoundedLinearAuctionMockContract; let oracleWhiteList: OracleWhiteListContract; const coreHelper = new CoreHelper(ownerAccount, ownerAccount); @@ -49,6 +49,10 @@ contract('TwoAssetAuctionBoundsCalculator', accounts => { let wrappedBTCOracle: UpdatableOracleMockContract; let usdcOracle: UpdatableOracleMockContract; + let auctionPeriod: BigNumber; + let rangeStart: BigNumber; + let rangeEnd: BigNumber; + before(async () => { wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); @@ -67,12 +71,19 @@ contract('TwoAssetAuctionBoundsCalculator', accounts => { [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address], ); - boundsCalculator = await liquidatorHelper.deployTwoAssetAuctionBoundsCalculatorMock( - oracleWhiteList.address + auctionPeriod = new BigNumber(14400); // 4 hours + rangeStart = new BigNumber(3); // 3% + rangeEnd = new BigNumber(21); // 21% + + boundsCalculator = await liquidatorHelper.deployTwoAssetPriceBoundedLinearAuctionMock( + oracleWhiteList.address, + auctionPeriod, + rangeStart, + rangeEnd, ); }); - describe.only('#calculateStartNumerator', async () => { + describe('#calculateAuctionBoundDifference', async () => { let subjectFairValue: BigNumber; let subjectRangeStart: BigNumber; diff --git a/utils/contracts.ts b/utils/contracts.ts index ae485610e..dd43296bb 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -71,8 +71,8 @@ export { TimeLockUpgradeV2MockContract } from '../types/generated/time_lock_upgr export { TransferProxyContract } from '../types/generated/transfer_proxy'; export { TradingPoolViewerContract } from '../types/generated/trading_pool_viewer'; export { - TwoAssetAuctionBoundsCalculatorMockContract -} from '../types/generated/two_asset_auction_bounds_calculator_mock'; + TwoAssetPriceBoundedLinearAuctionMockContract +} from '../types/generated/two_asset_price_bounded_linear_auction_mock'; export { UpdatableConstantAuctionPriceCurveContract } from '../types/generated/updatable_constant_auction_price_curve'; export { UpdatableOracleMockContract } from '../types/generated/updatable_oracle_mock'; export { VaultContract } from '../types/generated/vault'; diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 4fc3ffb3a..1dff2a668 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -11,7 +11,7 @@ import { LiquidatorProxyContract, OracleWhiteListContract, SetTokenContract, - TwoAssetAuctionBoundsCalculatorMockContract, + TwoAssetPriceBoundedLinearAuctionMockContract, } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { @@ -29,7 +29,7 @@ const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); const LinearAuctionMock = artifacts.require('LinearAuctionMock'); const LiquidatorMock = artifacts.require('LiquidatorMock'); const LiquidatorProxy = artifacts.require('LiquidatorProxy'); -const TwoAssetAuctionBoundsCalculatorMock = artifacts.require('TwoAssetAuctionBoundsCalculatorMock'); +const TwoAssetPriceBoundedLinearAuctionMock = artifacts.require('TwoAssetPriceBoundedLinearAuctionMock'); import { ERC20Helper } from './erc20Helper'; import { LibraryMockHelper } from './libraryMockHelper'; @@ -113,17 +113,23 @@ export class LiquidatorHelper { ); } - public async deployTwoAssetAuctionBoundsCalculatorMock( + public async deployTwoAssetPriceBoundedLinearAuctionMock( oracleWhiteList: Address, + auctionPeriod: BigNumber, + rangeStart: BigNumber, + rangeEnd: BigNumber, from: Address = this._contractOwnerAddress - ): Promise { - const twoAssetAuctionBoundsCalculatorMock = await TwoAssetAuctionBoundsCalculatorMock.new( + ): Promise { + const mockContract = await TwoAssetPriceBoundedLinearAuctionMock.new( oracleWhiteList, + auctionPeriod, + rangeStart, + rangeEnd, txnFrom(from) ); - return new TwoAssetAuctionBoundsCalculatorMockContract( - getContractInstance(twoAssetAuctionBoundsCalculatorMock), + return new TwoAssetPriceBoundedLinearAuctionMockContract( + getContractInstance(mockContract), txnFrom(from) ); } From 4a67ee02eeb39cb9f4c35f732b71f42154fac541 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Sun, 5 Jan 2020 21:34:35 -0800 Subject: [PATCH 09/35] Push tests --- package.json | 2 +- .../liquidators/impl/linearAuction.spec.ts | 13 ++++-- .../linearAuctionLiquidator.spec.ts | 28 +++++++++++-- utils/helpers/liquidatorHelper.ts | 40 ++++++++++++++----- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 86f8e15b9..c43ee4187 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "transpile": "tsc", "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", - "flatten": "truffle-flattener contracts/core/tokens/RebalancingSetTokenV2Factory.sol" + "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, "repository": "git@github.com:SetProtocol/set-protocol-contracts.git", "author": "Felix Feng ", diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 0d8142beb..f7bfa03d8 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -253,7 +253,9 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeStart = await auctionMock.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -269,7 +271,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); @@ -315,7 +318,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeStart = await auctionMock.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -332,7 +336,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 5a91ee524..1325a627a 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -292,7 +292,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + getLinearAuction(auction), + fairValue, + rangeStart, + oracleWhiteList, + ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -308,7 +313,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + getLinearAuction(auction), + fairValue, + rangeEnd, + oracleWhiteList, + ); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); @@ -354,7 +364,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + getLinearAuction(auction), + fairValue, + rangeStart, + oracleWhiteList, + ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -370,7 +385,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + getLinearAuction(auction), + fairValue, + rangeEnd, + oracleWhiteList, + ); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 1dff2a668..8a446e666 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -273,20 +273,40 @@ export class LiquidatorHelper { return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } - public calculateStartPrice( + public async calculateTwoAssetStartPrice( + linearAuction: LinearAuction, fairValue: BigNumber, - rangeStart: BigNumber, - ): BigNumber { - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - return fairValue.sub(negativeRange); + rangeStartPercentage: BigNumber, + oracleWhiteList: OracleWhiteListContract, + ): Promise { + const [startDifference] = await this.calculateAuctionBoundsAsync( + linearAuction.auction.combinedTokenArray, + linearAuction.auction.combinedCurrentSetUnits, + linearAuction.auction.combinedNextSetUnits, + fairValue, + rangeStartPercentage, + rangeStartPercentage, // Dummy value, unused + oracleWhiteList + ); + return fairValue.sub(startDifference); } - public calculateEndPrice( + public async calculateTwoAssetEndPrice( + linearAuction: LinearAuction, fairValue: BigNumber, - rangeEnd: BigNumber, - ): BigNumber { - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - return fairValue.add(positiveRange); + rangeEndPercentage: BigNumber, + oracleWhiteList: OracleWhiteListContract, + ): Promise { + const [, endDifference] = await this.calculateAuctionBoundsAsync( + linearAuction.auction.combinedTokenArray, + linearAuction.auction.combinedCurrentSetUnits, + linearAuction.auction.combinedNextSetUnits, + fairValue, + rangeEndPercentage, // Dummy value, unused + rangeEndPercentage, + oracleWhiteList + ); + return fairValue.add(endDifference); } public async calculateFairValueAsync( From a98a61670a1d25bd12ddaeb84c3def5ce5e51735 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Mon, 6 Jan 2020 01:44:56 -0800 Subject: [PATCH 10/35] [Liquidator] Add 2 component validation (#561) * Validations * Fix formula * Add validate tests --- .../liquidators/LinearAuctionLiquidator.sol | 2 +- .../TwoAssetPriceBoundedLinearAuction.sol | 27 ++- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 14 ++ .../rebalancingLinearLiquidator.spec.ts | 25 +- .../twoAssetPriceBoundedLinearAuction.spec.ts | 218 +++++++++++++++++- .../linearAuctionLiquidator.spec.ts | 30 ++- utils/helpers/liquidatorHelper.ts | 45 +++- 7 files changed, 329 insertions(+), 32 deletions(-) diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index bcffb51e1..263ab293c 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -101,7 +101,7 @@ contract LinearAuctionLiquidator is TwoAssetPriceBoundedLinearAuction, ILiquidat { _liquidatorData; // Pass linting - LinearAuction.validateRebalanceComponents( + TwoAssetPriceBoundedLinearAuction.validateTwoAssetPriceBoundedAuction( _currentSet, _nextSet ); diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 6505d2d28..da14ba246 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -22,9 +22,10 @@ import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { Auction } from "./Auction.sol"; -import { LinearAuction } from "./LinearAuction.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; +import { ISetToken } from "../../interfaces/ISetToken.sol"; +import { LinearAuction } from "./LinearAuction.sol"; /** @@ -58,6 +59,28 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ) {} + /** + * Validates that the auction only includes two components and the components are valid. + */ + function validateTwoAssetPriceBoundedAuction( + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + { + address[] memory combinedTokenArray = Auction.getCombinedTokenArray(_currentSet, _nextSet); + require( + combinedTokenArray.length == 2, + "TwoAssetPriceBoundedLinearAuction: Only two components are allowed." + ); + + LinearAuction.validateRebalanceComponents( + _currentSet, + _nextSet + ); + } + /** * Calculates the linear auction start price with a scaled value */ @@ -92,7 +115,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 endDifference = calculateAuctionBoundDifference( _linearAuction.auction, _fairValueScaled, - rangeStart + rangeEnd ); return _fairValueScaled.add(endDifference); diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 4fd8935c9..b77df428b 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -21,6 +21,7 @@ import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20 import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; +import { ISetToken } from "../../../../core/interfaces/ISetToken.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; import { TwoAssetPriceBoundedLinearAuction } from "../../../../core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol"; @@ -48,6 +49,19 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct ) {} + // To test + function validateTwoAssetPriceBoundedAuctionMock(ISetToken _currentSet,ISetToken _nextSet) external view { + validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); + } + + function calculateStartPriceMock(uint256 _fairValueScaled) external view returns(uint256) { + return super.calculateStartPrice(auctionInfo, _fairValueScaled); + } + + function calculateEndPriceMock(uint256 _fairValueScaled) external view returns(uint256) { + return super.calculateEndPrice(auctionInfo, _fairValueScaled); + } + function calculateAuctionBoundDifferenceMock( uint256 _fairValue, uint256 _rangeStart diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 04ada9ec9..2c7d1d386 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -173,8 +173,8 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { set1NaturalUnit, ); - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; + set2Components = [component1.address, component2.address]; + set2Units = [gWei(1), gWei(2)]; set2NaturalUnit = customSet2NaturalUnit || gWei(2); set2 = await coreHelper.createSetTokenAsync( coreMock, @@ -355,6 +355,27 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); + describe('when the union of currentSet and nextSet is not 2 components', async () => { + beforeEach(async () => { + const set3Components = [component1.address, component3.address]; + const set3Units = [gWei(1), gWei(1)]; + const set3NaturalUnit = customSet1NaturalUnit || gWei(1); + const set3 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set3Components, + set3Units, + set3NaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + describe('when the rebalance interval has not elapsed', async () => { beforeEach(async () => { subjectTimeFastForward = ONE_DAY_IN_SECONDS.sub(10); diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index c15426856..7fc64be47 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -2,40 +2,54 @@ require('module-alias/register'); import * as _ from 'lodash'; import * as chai from 'chai'; +import * as ABIDecoder from 'abi-decoder'; import { BigNumber } from 'bignumber.js'; import { Address } from 'set-protocol-utils'; import ChaiSetup from '@utils/chaiSetup'; import { BigNumberSetup } from '@utils/bigNumberSetup'; import { - StandardTokenMockContract, + CoreMockContract, OracleWhiteListContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, TwoAssetPriceBoundedLinearAuctionMockContract, + TransferProxyContract, UpdatableOracleMockContract, + VaultContract, } from '@utils/contracts'; -import { ether } from '@utils/units'; +import { ether, gWei } from '@utils/units'; +import { LinearAuction } from '@utils/auction'; import { CoreHelper } from '@utils/helpers/coreHelper'; import { ERC20Helper } from '@utils/helpers/erc20Helper'; import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; +import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; +const CoreMock = artifacts.require('CoreMock'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ - ownerAccount, + deployerAccount, ] = accounts; + let coreMock: CoreMockContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let boundsCalculator: TwoAssetPriceBoundedLinearAuctionMockContract; let oracleWhiteList: OracleWhiteListContract; - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); + const coreHelper = new CoreHelper(deployerAccount, deployerAccount); + const erc20Helper = new ERC20Helper(deployerAccount); + const liquidatorHelper = new LiquidatorHelper(deployerAccount, erc20Helper); + const libraryMockHelper = new LibraryMockHelper(deployerAccount); let wrappedETH: StandardTokenMockContract; let wrappedBTC: StandardTokenMockContract; @@ -54,9 +68,17 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { - wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); - wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); - usdc = await erc20Helper.deployTokenAsync(ownerAccount, 6); + ABIDecoder.addABI(CoreMock.abi); + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + coreMock = await coreHelper.deployCoreMockAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(coreMock.address); + await coreHelper.setDefaultStateAndAuthorizationsAsync(coreMock, vault, transferProxy, setTokenFactory); + + wrappedETH = await erc20Helper.deployTokenAsync(deployerAccount, 18); + wrappedBTC = await erc20Helper.deployTokenAsync(deployerAccount, 8); + usdc = await erc20Helper.deployTokenAsync(deployerAccount, 6); wrappedETHPrice = ether(128); wrappedBTCPrice = ether(7500); @@ -83,6 +105,182 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { ); }); + after(async () => { + ABIDecoder.removeABI(CoreMock.abi); + }); + + describe('#validateTwoAssetPriceBoundedAuction', async () => { + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + let customComponents1: Address[]; + let customComponents2: Address[]; + + let customUnits1: BigNumber[]; + let customUnits2: BigNumber[]; + + let subjectCurrentSet: Address; + let subjectNextSet: Address; + + beforeEach(async () => { + set1Components = customComponents1 || [wrappedETH.address, wrappedBTC.address]; + set1Units = customUnits1 || [gWei(1), gWei(1)]; + set1NaturalUnit = new BigNumber(10 ** 12); + set1 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = customComponents2 || [wrappedETH.address, wrappedBTC.address]; + set2Units = customUnits2 || [gWei(1), gWei(2)]; + set2NaturalUnit = new BigNumber(10 ** 12); + set2 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + }); + + async function subject(): Promise { + return boundsCalculator.validateTwoAssetPriceBoundedAuctionMock.callAsync( + subjectCurrentSet, + subjectNextSet + ); + } + + it('does not revert', async () => { + await subject(); + }); + + describe('when the union is 3 components', async () => { + before(async () => { + customComponents2 = [wrappedETH.address, usdc.address]; + }); + + after(async () => { + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('when the union is 1 components', async () => { + before(async () => { + customComponents1 = [wrappedETH.address]; + customComponents2 = [wrappedETH.address]; + + customUnits1 = [gWei(1)]; + customUnits2 = [gWei(2)]; + }); + + after(async () => { + customComponents1 = undefined; + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#calculateStartPrice and calculateEndPrice', async () => { + let subjectFairValue: BigNumber; + + let combinedTokenArray: Address[]; + let combinedCurrentSetUnits: BigNumber[]; + let combinedNextSetUnits: BigNumber[]; + + let linearAuction: LinearAuction; + + beforeEach(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + + await boundsCalculator.parameterizeAuction.sendTransactionAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits + ); + + linearAuction = { + auction: { + pricePrecision: new BigNumber(0), + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray: [wrappedETH.address, usdc.address], + combinedCurrentSetUnits: [new BigNumber(10 ** 12), new BigNumber(128)], + combinedNextSetUnits: [new BigNumber(10 ** 12), new BigNumber(1152)], + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; + + subjectFairValue = ether(5); + }); + + async function startPriceSubject(): Promise { + return boundsCalculator.calculateStartPriceMock.callAsync( + subjectFairValue, + ); + } + + async function endPriceSubject(): Promise { + return boundsCalculator.calculateEndPriceMock.callAsync( + subjectFairValue, + ); + } + + it('calculates the correct start price value', async () => { + const result = await startPriceSubject(); + + const expectedResult = await liquidatorHelper.calculateTwoAssetStartPrice( + linearAuction, + subjectFairValue, + rangeStart, + oracleWhiteList, + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + + it('calculates the correct end price value', async () => { + const result = await endPriceSubject(); + + const expectedResult = await liquidatorHelper.calculateTwoAssetEndPrice( + linearAuction, + subjectFairValue, + rangeEnd, + oracleWhiteList, + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + }); + describe('#calculateAuctionBoundDifference', async () => { let subjectFairValue: BigNumber; let subjectRangeStart: BigNumber; diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 1325a627a..7ba0d52de 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -120,8 +120,8 @@ contract('LinearAuctionLiquidator', accounts => { set1NaturalUnit, ); - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; + set2Components = [component1.address, component2.address]; + set2Units = [gWei(1), gWei(0.5)]; set2NaturalUnit = gWei(2); set2 = await coreHelper.createSetTokenAsync( core, @@ -325,7 +325,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the currentSet is > 10x nextSet', async () => { beforeEach(async () => { const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; + const setUnits = [gWei(1), gWei(2)]; const setNaturalUnit = gWei(100); const set3 = await coreHelper.createSetTokenAsync( core, @@ -340,7 +340,6 @@ contract('LinearAuctionLiquidator', accounts => { it('sets the correct pricePrecision', async () => { await subject(); - const auction: any = await liquidator.auctions.callAsync(subjectCaller); const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( @@ -408,7 +407,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when a token does not have a supported oracle', async () => { beforeEach(async () => { await oracleWhiteList.removeTokenOraclePair.sendTransactionAsync( - component3.address, + component1.address, { from: ownerAccount, gas: DEFAULT_GAS }, ); }); @@ -417,6 +416,27 @@ contract('LinearAuctionLiquidator', accounts => { await expectRevertError(subject()); }); }); + + describe('when the union of the current and next Set is not 2 components', async () => { + beforeEach(async () => { + const set3Components = [component1.address, component3.address]; + const set3Units = [gWei(1), gWei(2)]; + const set3NaturalUnit = gWei(2); + const set3 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set3Components, + set3Units, + set3NaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('[CONTEXT] Initialized auction', async () => { diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 8a446e666..46415af2f 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -247,18 +247,39 @@ export class LiquidatorHelper { fairValue: BigNumber, boundValue: BigNumber ): BigNumber { - const numerator = (combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0]))).pow(2) - .mul(assetTwoFullUnit) - .mul(boundValue) - .mul(assetPairPrice) - .div(100); - - const denominator = combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]).sub( - combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) - .mul(assetOneFullUnit) - .mul(10 ** 18); - - return numerator.div(denominator).abs(); + let numDifferential; + if (combinedNextUnitArray[0].mul(ether(1)).gt(fairValue.mul(combinedCurrentUnitArray[0]))) { + numDifferential = combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0])); + } else { + numDifferential = fairValue.mul(combinedCurrentUnitArray[0]).sub(combinedNextUnitArray[0].mul(ether(1))); + } + + let denomDifferential; + if (combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]) + .gt(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) + ) { + denomDifferential = combinedNextUnitArray[0] + .mul(combinedCurrentUnitArray[1]) + .sub(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])); + } else { + denomDifferential = combinedNextUnitArray[1] + .mul(combinedCurrentUnitArray[0]) + .sub(combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1])); + } + + const calcNumerator = assetTwoFullUnit + .mul(numDifferential) + .mul(boundValue) + .mul(assetPairPrice) + .div(100).round(0, 3); + + const calcDenominator = assetOneFullUnit.mul(denomDifferential).mul(ether(1)); + + return calcNumerator + .mul(ether(1)) + .div(calcDenominator).round(0, 3) + .mul(numDifferential) + .div(ether(1)).round(0, 3); } public calculateCurrentPrice( From 277b3cac5eb39d5b6713e60e7112b5dc929c1356 Mon Sep 17 00:00:00 2001 From: bweick Date: Mon, 6 Jan 2020 20:58:14 -0800 Subject: [PATCH 11/35] Refactored some logic in the auction bound calculations. Changed auction bound calcs from using derivative to extrapolate auction prices to directly computing auction prices. --- .../core/liquidators/impl/LinearAuction.sol | 13 +- .../TwoAssetPriceBoundedLinearAuction.sol | 170 +++++++++++----- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 19 +- .../twoAssetPriceBoundedLinearAuction.spec.ts | 121 +++++------- .../linearAuctionLiquidator.spec.ts | 42 +--- utils/constants.ts | 2 + utils/helpers/liquidatorHelper.ts | 184 ++++++++++-------- 7 files changed, 300 insertions(+), 251 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 1526bef05..94d4da2e0 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -94,9 +94,8 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - uint256 fairValue = calculateFairValue(_currentSet, _nextSet); - _linearAuction.startPrice = calculateStartPrice(_linearAuction, fairValue); - _linearAuction.endPrice = calculateEndPrice(_linearAuction, fairValue); + _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction); + _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -147,7 +146,7 @@ contract LinearAuction is Auction { /** * Returns the linear price based on the current timestamp. Returns the endPrice - * if time has exceeded the auciton period + * if time has exceeded the auction period * * @param _linearAuction Linear Auction State object * @return price uint representing the current price @@ -189,8 +188,7 @@ contract LinearAuction is Auction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view @@ -201,8 +199,7 @@ contract LinearAuction is Auction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index da14ba246..8ba66a607 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -38,10 +38,11 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { using CommonMath for uint256; struct AssetInfo { - uint256 assetPrice; + uint256 price; uint256 fullUnit; } + uint256 constant private CURVE_DENOMINATOR = 10 ** 18; uint256 constant private ONE_HUNDRED = 100; constructor( @@ -85,79 +86,160 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view returns(uint256) { - uint256 startDifference = calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart + // Get full Unit amount and price for each asset + AssetInfo memory assetOne = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); + + // Calculate current asset pair spot price as assetOne/assetTwo + uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + + // Check to see if asset pair price is increasing or decreasing as time passes + bool isTokenFlowIncreasing = isTokenFlowIncreasing( + _auction, + spotPrice, + assetOne.fullUnit, + assetTwo.fullUnit ); - return _fairValueScaled.sub(startDifference); + // If price implied by token flows is increasing then target price we are using for lower bound + // is below current spot price, if flows decreasing set target price above spotPrice + uint256 startPairPrice; + if (isTokenFlowIncreasing) { + startPairPrice = spotPrice.mul(ONE_HUNDRED.sub(rangeStart)).div(ONE_HUNDRED); + } else { + startPairPrice = spotPrice.mul(ONE_HUNDRED.add(rangeStart)).div(ONE_HUNDRED); + } + + // Convert start asset pair price to equivalent auction price + return convertAssetPairPriceToAuctionPrice( + _auction, + startPairPrice, + assetOne.fullUnit, + assetTwo.fullUnit + ); } /** * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view returns(uint256) { - uint256 endDifference = calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeEnd + // Get full Unit amount and price for each asset + AssetInfo memory assetOne = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); + + // Calculate current spot price as assetOne/assetTwo + uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + + // Check to see if asset pair price is increasing or decreasing as time passes + bool isTokenFlowIncreasing = isTokenFlowIncreasing( + _auction, + spotPrice, + assetOne.fullUnit, + assetTwo.fullUnit ); - return _fairValueScaled.add(endDifference); - } + // If price implied by token flows is increasing then target price we are using for upper bound + // is above current spot price, if flows decreasing set target price below spotPrice + uint256 endPairPrice; + if (isTokenFlowIncreasing) { + endPairPrice = spotPrice.mul(ONE_HUNDRED.add(rangeEnd)).div(ONE_HUNDRED); + } else { + endPairPrice = spotPrice.mul(ONE_HUNDRED.sub(rangeEnd)).div(ONE_HUNDRED); + } + // Convert end asset pair price to equivalent auction price + return convertAssetPairPriceToAuctionPrice( + _auction, + endPairPrice, + assetOne.fullUnit, + assetTwo.fullUnit + ); + } - function calculateAuctionBoundDifference( + function isTokenFlowIncreasing( Auction.Setup storage _auction, - uint256 _fairValue, - uint256 _boundValue + uint256 _spotPrice, + uint256 _assetOneFullUnit, + uint256 _assetTwoFullUnit ) internal view - returns (uint256) + returns (bool) { - AssetInfo memory baseAsset = getAssetInfo(_auction.combinedTokenArray[0]); - AssetInfo memory quoteAsset = getAssetInfo(_auction.combinedTokenArray[1]); - - uint256 numDifferential; - if (_auction.combinedNextSetUnits[0].scale() > _fairValue.mul(_auction.combinedCurrentSetUnits[0])) { - numDifferential = _auction.combinedNextSetUnits[0].scale().sub(_fairValue.mul(_auction.combinedCurrentSetUnits[0])); - } else { - numDifferential = _fairValue.mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].scale()); - } + // Calculate auction price at current asset pair spot price + uint256 auctionFairValue = convertAssetPairPriceToAuctionPrice( + _auction, + _spotPrice, + _assetOneFullUnit, + _assetTwoFullUnit + ); - uint256 denomDifferential; - if (_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]) > _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])) { - denomDifferential = _auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]).sub(_auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])); - } else { - denomDifferential = _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1])); - } + // Equation for assetOne net outflow is assetOneCurrentUnits*auctionPrice - assetOneNextUnits*auctionDenominator. + // Thus if assetOneNextUnits*auctionDenominator > assetOneCurrentUnits*auctionPrice then assetOne is + // an inflow. When assetOne is an inflow (negative outflow), it implies that assetTwo is an outflow, + // furthermore we are guaranteed that both flows have a positive derivative with respect to auction price (and + // auction price always increases). Since we define price as abs(assetTwoOutflow/assetOneOutflow), with assetOneFlow + // getting less negative and assetTwoFlow getting more positive it implies that the asset price is increasing as + // time passes in the auction. + return _auction.combinedNextSetUnits[0].mul(CURVE_DENOMINATOR) > + _auction.combinedCurrentSetUnits[0].mul(auctionFairValue); + } - uint256 calcNumerator = quoteAsset.fullUnit - .mul(numDifferential) - .mul(_boundValue) - .mul(baseAsset.assetPrice) - .div(quoteAsset.assetPrice) - .div(ONE_HUNDRED); + /** + * Convert an asset pair price to the equivalent auction price where a1 refers to assetOne and a2 refers to assetTwo + * and subscripts c, n, d mean currentSetUnit, nextSetUnit and fullUnit amount, respectively. aP and aD refer to auction + * price and auction denominator: + * + * assetPrice = abs(assetTwoOutflow/assetOneOutflow) + * + * assetPrice = ((a2_c/a2_d)*aP - (a2_n/a2_d)*aD) / ((a1_c/a1_d)*aP - (a1_n/a1_d)*aD) + * + * We know assetPrice so we isolate for aP: + * + * aP = aD((a2_n/a2_d)+assetPrice*(a1_n/a1_d)) / (a2_c/a2_d)+assetPrice*(a1_c/a1_d) + * + * This gives us the auction price that matches with the passed asset pair price. + */ + function convertAssetPairPriceToAuctionPrice( + Auction.Setup storage _auction, + uint256 _targetPrice, + uint256 assetOneFullUnit, + uint256 assetTwoFullUnit + ) + internal + view + returns (uint256) + { + // Calculate the numerator for the above equation. In order to ensure no rounding down errors we distribute the auction + // denominator. Additionally, since the price is passed as an 18 decimal number in order to maintain consistency we + // have to scale the first term up accordingly + uint256 calcNumerator = _auction.combinedNextSetUnits[1].mul(CURVE_DENOMINATOR).scale().div(assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedNextSetUnits[0]).mul(CURVE_DENOMINATOR).div(assetOneFullUnit) + ); - uint256 calcDenominator = baseAsset.fullUnit.mul(denomDifferential).scale(); + // Calculate the denominator for the above equation. As above we we have to scale the first term match the 18 decimal + // price. Furthermore since we are not guaranteed that targetPrice * a1_c > a1_d we have to scale the second term and + // thus also the first term in order to match (hence the two scale() in the first term) + uint256 calcDenominator = _auction.combinedCurrentSetUnits[1].scale().scale().div(assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedCurrentSetUnits[0]).scale().div(assetOneFullUnit) + ); - return calcNumerator.scale().div(calcDenominator).mul(numDifferential).deScale(); + // Here the scale required to account for the 18 decimal price cancels out since it was applied to both the numerator + // and denominator. However there was an extra scale applied to the denominator that we need to remove, in order to + // do so we'll just apply another scale to the numerator before dividing since 1/(1/10 ** 18) = 10 ** 18! + return calcNumerator.scale().div(calcDenominator); } function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { @@ -167,7 +249,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 decimals = ERC20Detailed(_asset).decimals(); return AssetInfo({ - assetPrice: assetPrice, + price: assetPrice, fullUnit: CommonMath.safePower(10, decimals) }); } diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index b77df428b..4f8ce46ac 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -54,23 +54,12 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); } - function calculateStartPriceMock(uint256 _fairValueScaled) external view returns(uint256) { - return super.calculateStartPrice(auctionInfo, _fairValueScaled); + function calculateStartPriceMock() external view returns(uint256) { + return super.calculateStartPrice(auctionInfo.auction); } - function calculateEndPriceMock(uint256 _fairValueScaled) external view returns(uint256) { - return super.calculateEndPrice(auctionInfo, _fairValueScaled); - } - - function calculateAuctionBoundDifferenceMock( - uint256 _fairValue, - uint256 _rangeStart - ) - external - view - returns (uint256) - { - return calculateAuctionBoundDifference(auctionInfo.auction, _fairValue, _rangeStart); + function calculateEndPriceMock() external view returns(uint256) { + return super.calculateEndPrice(auctionInfo.auction); } function parameterizeAuction( diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 7fc64be47..17face11b 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -31,7 +31,7 @@ import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; -const CoreMock = artifacts.require('CoreMock'); +const TwoAssetPriceBoundedLinearAuction = artifacts.require('TwoAssetPriceBoundedLinearAuction'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ @@ -68,7 +68,8 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { - ABIDecoder.addABI(CoreMock.abi); + ABIDecoder.addABI(TwoAssetPriceBoundedLinearAuction.abi); + transferProxy = await coreHelper.deployTransferProxyAsync(); vault = await coreHelper.deployVaultAsync(); coreMock = await coreHelper.deployCoreMockAsync(transferProxy, vault); @@ -106,7 +107,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { - ABIDecoder.removeABI(CoreMock.abi); + ABIDecoder.removeABI(TwoAssetPriceBoundedLinearAuction.abi); }); describe('#validateTwoAssetPriceBoundedAuction', async () => { @@ -203,9 +204,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe('#calculateStartPrice and calculateEndPrice', async () => { - let subjectFairValue: BigNumber; - + describe.only('#calculateStartPrice and calculateEndPrice', async () => { let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; let combinedNextSetUnits: BigNumber[]; @@ -238,29 +237,23 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { startPrice: new BigNumber(0), endPrice: new BigNumber(0), }; - - subjectFairValue = ether(5); }); async function startPriceSubject(): Promise { - return boundsCalculator.calculateStartPriceMock.callAsync( - subjectFairValue, - ); + return boundsCalculator.calculateStartPriceMock.callAsync(); } async function endPriceSubject(): Promise { - return boundsCalculator.calculateEndPriceMock.callAsync( - subjectFairValue, - ); + return boundsCalculator.calculateEndPriceMock.callAsync(); } it('calculates the correct start price value', async () => { const result = await startPriceSubject(); - const expectedResult = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedResult, ] = await liquidatorHelper.calculateAuctionBoundsAsync( linearAuction, - subjectFairValue, rangeStart, + rangeEnd, oracleWhiteList, ); @@ -270,9 +263,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { it('calculates the correct end price value', async () => { const result = await endPriceSubject(); - const expectedResult = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedResult] = await liquidatorHelper.calculateAuctionBoundsAsync( linearAuction, - subjectFairValue, + rangeStart, rangeEnd, oracleWhiteList, ); @@ -281,9 +274,8 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe('#calculateAuctionBoundDifference', async () => { - let subjectFairValue: BigNumber; - let subjectRangeStart: BigNumber; + describe.only('#calculateAuctionBounds', async () => { + let linearAuction: LinearAuction; let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; @@ -296,37 +288,40 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); beforeEach(async () => { - combinedTokenArray = [wrappedETH.address, usdc.address]; - combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; - await boundsCalculator.parameterizeAuction.sendTransactionAsync( combinedTokenArray, combinedCurrentSetUnits, combinedNextSetUnits ); - subjectFairValue = ether(5); - subjectRangeStart = new BigNumber(3); + linearAuction = { + auction: { + pricePrecision: new BigNumber(0), + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; }); async function subject(): Promise { - return boundsCalculator.calculateAuctionBoundDifferenceMock.callAsync( - subjectFairValue, - subjectRangeStart - ); + return boundsCalculator.calculateStartPriceMock.callAsync(); } - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -340,16 +335,13 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { combinedNextSetUnits = [new BigNumber(1152), new BigNumber(10 ** 12)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -364,16 +356,13 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { combinedNextSetUnits = [new BigNumber(9 * 10 ** 12), new BigNumber(128)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -385,19 +374,16 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { before(async () => { combinedTokenArray = [usdc.address, wrappedETH.address]; combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; - combinedNextSetUnits = [new BigNumber(1152), new BigNumber(9 * 10 ** 12)]; + combinedNextSetUnits = [new BigNumber(128), new BigNumber(9 * 10 ** 12)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -408,20 +394,17 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { describe('different allocation', async () => { before(async () => { combinedTokenArray = [wrappedETH.address, usdc.address]; - combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(192)]; + combinedCurrentSetUnits = [new BigNumber(10 ** 14), new BigNumber(12800)]; + combinedNextSetUnits = [new BigNumber(10 ** 14), new BigNumber(1267200)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 7ba0d52de..0b14d9e6e 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -216,7 +216,7 @@ contract('LinearAuctionLiquidator', accounts => { }); }); - describe('#startRebalance', async () => { + describe.only('#startRebalance', async () => { let subjectCaller: Address; let subjectCurrentSet: Address; let subjectNextSet: Address; @@ -285,17 +285,11 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedStartPrice, ] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, rangeStart, + rangeEnd, oracleWhiteList, ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); @@ -306,16 +300,10 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedEndPrice] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, + rangeStart, rangeEnd, oracleWhiteList, ); @@ -356,17 +344,11 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedStartPrice, ] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, rangeStart, + rangeEnd, oracleWhiteList, ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); @@ -377,16 +359,10 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedEndPrice] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, + rangeStart, rangeEnd, oracleWhiteList, ); diff --git a/utils/constants.ts b/utils/constants.ts index c572e316d..22035263c 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -2,6 +2,7 @@ import { BigNumber } from 'bignumber.js'; import { ether } from '../utils/units'; export const AUCTION_TIME_INCREMENT = new BigNumber(30); // Unix seconds +export const AUCTION_CURVE_DENOMINATOR = ether(1); export const DEFAULT_AUCTION_PRICE_NUMERATOR = new BigNumber(1374); export const DEFAULT_AUCTION_PRICE_DIVISOR = new BigNumber(1000); export const DEFAULT_GAS = 19000000; @@ -17,6 +18,7 @@ export const EMPTY_BYTESTRING: string = '0x00'; export const KYBER_RESERVE_CONFIGURED_RATE: BigNumber = new BigNumber('321556325999999997'); export const NULL_ADDRESS: string = '0x0000000000000000000000000000000000000000'; 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 SCALE_FACTOR = ether(1); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 46415af2f..231df8289 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -15,14 +15,15 @@ import { } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { - ZERO, + AUCTION_CURVE_DENOMINATOR, + ONE_HUNDRED, SCALE_FACTOR, + ZERO } from '../constants'; import { LinearAuction, TokenFlow } from '../auction'; -import { ether } from '@utils/units'; const AuctionMock = artifacts.require('AuctionMock'); const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); @@ -200,86 +201,141 @@ export class LiquidatorHelper { } public async calculateAuctionBoundsAsync( - combinedTokenArray: Address[], - combinedCurrentUnitArray: BigNumber[], - combinedNextUnitArray: BigNumber[], - fairValue: BigNumber, + linearAuction: LinearAuction, startBound: BigNumber, endBound: BigNumber, oracleWhiteList: OracleWhiteListContract ): Promise<[BigNumber, BigNumber]> { - const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync(combinedTokenArray); + const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync( + linearAuction.auction.combinedTokenArray + ); const assetOneFullUnit = new BigNumber(10 ** assetOneDecimals.toNumber()); const assetTwoFullUnit = new BigNumber(10 ** assetTwoDecimals.toNumber()); - const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync(combinedTokenArray, oracleWhiteList); + const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync( + linearAuction.auction.combinedTokenArray, + oracleWhiteList + ); - const startValue = this.calculateAuctionBound( - combinedCurrentUnitArray, - combinedNextUnitArray, + const startValue = this.calculateTwoAssetStartPrice( + linearAuction, assetOneFullUnit, assetTwoFullUnit, assetOnePrice.div(assetTwoPrice), - fairValue, startBound ); - const endValue = this.calculateAuctionBound( - combinedCurrentUnitArray, - combinedNextUnitArray, + const endValue = this.calculateTwoAssetEndPrice( + linearAuction, assetOneFullUnit, assetTwoFullUnit, assetOnePrice.div(assetTwoPrice), - fairValue, endBound ); return [startValue, endValue]; } - public calculateAuctionBound( - combinedCurrentUnitArray: BigNumber[], - combinedNextUnitArray: BigNumber[], + public calculateTwoAssetStartPrice( + linearAuction: LinearAuction, assetOneFullUnit: BigNumber, assetTwoFullUnit: BigNumber, assetPairPrice: BigNumber, - fairValue: BigNumber, - boundValue: BigNumber + startBound: BigNumber ): BigNumber { - let numDifferential; - if (combinedNextUnitArray[0].mul(ether(1)).gt(fairValue.mul(combinedCurrentUnitArray[0]))) { - numDifferential = combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0])); + const auctionFairValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + assetPairPrice + ); + + const tokenFlowIncreasing = this.isTokenFlowIncreasing( + linearAuction.auction.combinedCurrentSetUnits[0], + linearAuction.auction.combinedNextSetUnits[0], + auctionFairValue + ); + + let startPairPrice: BigNumber; + if (tokenFlowIncreasing) { + startPairPrice = assetPairPrice.mul(ONE_HUNDRED.sub(startBound)).div(ONE_HUNDRED); } else { - numDifferential = fairValue.mul(combinedCurrentUnitArray[0]).sub(combinedNextUnitArray[0].mul(ether(1))); + startPairPrice = assetPairPrice.mul(ONE_HUNDRED.add(startBound)).div(ONE_HUNDRED); } - let denomDifferential; - if (combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]) - .gt(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) - ) { - denomDifferential = combinedNextUnitArray[0] - .mul(combinedCurrentUnitArray[1]) - .sub(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])); + const startValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + startPairPrice + ); + return startValue; + } + + public calculateTwoAssetEndPrice( + linearAuction: LinearAuction, + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + assetPairPrice: BigNumber, + endBound: BigNumber + ): BigNumber { + const auctionFairValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + assetPairPrice + ); + + const tokenFlowIncreasing = this.isTokenFlowIncreasing( + linearAuction.auction.combinedCurrentSetUnits[0], + linearAuction.auction.combinedNextSetUnits[0], + auctionFairValue + ); + + let endPairPrice: BigNumber; + if (tokenFlowIncreasing) { + endPairPrice = assetPairPrice.mul(ONE_HUNDRED.add(endBound)).div(ONE_HUNDRED); } else { - denomDifferential = combinedNextUnitArray[1] - .mul(combinedCurrentUnitArray[0]) - .sub(combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1])); + endPairPrice = assetPairPrice.mul(ONE_HUNDRED.sub(endBound)).div(ONE_HUNDRED); } - const calcNumerator = assetTwoFullUnit - .mul(numDifferential) - .mul(boundValue) - .mul(assetPairPrice) - .div(100).round(0, 3); + const endValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + endPairPrice + ); + return endValue; + } - const calcDenominator = assetOneFullUnit.mul(denomDifferential).mul(ether(1)); + public calculateAuctionBound( + linearAuction: LinearAuction, + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + targetPrice: BigNumber + ): BigNumber { + + const combinedNextUnitArray = linearAuction.auction.combinedNextSetUnits; + const combinedCurrentUnitArray = linearAuction.auction.combinedCurrentSetUnits; + + const calcNumerator = combinedNextUnitArray[1].mul(AUCTION_CURVE_DENOMINATOR).div(assetTwoFullUnit).add( + targetPrice.mul(combinedNextUnitArray[0]).mul(AUCTION_CURVE_DENOMINATOR).div(assetOneFullUnit) + ); + + const calcDenominator = combinedCurrentUnitArray[1].div(assetTwoFullUnit).add( + targetPrice.mul(combinedCurrentUnitArray[0]).div(assetOneFullUnit) + ); + + return calcNumerator.div(calcDenominator).round(0, 3); + } - return calcNumerator - .mul(ether(1)) - .div(calcDenominator).round(0, 3) - .mul(numDifferential) - .div(ether(1)).round(0, 3); + public isTokenFlowIncreasing( + assetOneCurrentUnit: BigNumber, + assetOneNextUnit: BigNumber, + fairValue: BigNumber + ): boolean { + return assetOneNextUnit.mul(AUCTION_CURVE_DENOMINATOR).greaterThan(assetOneCurrentUnit.mul(fairValue)); } public calculateCurrentPrice( @@ -294,42 +350,6 @@ export class LiquidatorHelper { return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } - public async calculateTwoAssetStartPrice( - linearAuction: LinearAuction, - fairValue: BigNumber, - rangeStartPercentage: BigNumber, - oracleWhiteList: OracleWhiteListContract, - ): Promise { - const [startDifference] = await this.calculateAuctionBoundsAsync( - linearAuction.auction.combinedTokenArray, - linearAuction.auction.combinedCurrentSetUnits, - linearAuction.auction.combinedNextSetUnits, - fairValue, - rangeStartPercentage, - rangeStartPercentage, // Dummy value, unused - oracleWhiteList - ); - return fairValue.sub(startDifference); - } - - public async calculateTwoAssetEndPrice( - linearAuction: LinearAuction, - fairValue: BigNumber, - rangeEndPercentage: BigNumber, - oracleWhiteList: OracleWhiteListContract, - ): Promise { - const [, endDifference] = await this.calculateAuctionBoundsAsync( - linearAuction.auction.combinedTokenArray, - linearAuction.auction.combinedCurrentSetUnits, - linearAuction.auction.combinedNextSetUnits, - fairValue, - rangeEndPercentage, // Dummy value, unused - rangeEndPercentage, - oracleWhiteList - ); - return fairValue.add(endDifference); - } - public async calculateFairValueAsync( currentSetToken: SetTokenContract, nextSetToken: SetTokenContract, From 5da1f13a7bc81f44fe8185d60b873279db48dc8f Mon Sep 17 00:00:00 2001 From: bweick Date: Mon, 6 Jan 2020 21:44:39 -0800 Subject: [PATCH 12/35] Remove .only --- .../impl/twoAssetPriceBoundedLinearAuction.spec.ts | 4 ++-- .../core/liquidators/linearAuctionLiquidator.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 17face11b..a73dcf8d3 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -204,7 +204,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe.only('#calculateStartPrice and calculateEndPrice', async () => { + describe('#calculateStartPrice and calculateEndPrice', async () => { let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; let combinedNextSetUnits: BigNumber[]; @@ -274,7 +274,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe.only('#calculateAuctionBounds', async () => { + describe('#calculateAuctionBounds', async () => { let linearAuction: LinearAuction; let combinedTokenArray: Address[]; diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 0b14d9e6e..4213ac4bd 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -216,7 +216,7 @@ contract('LinearAuctionLiquidator', accounts => { }); }); - describe.only('#startRebalance', async () => { + describe('#startRebalance', async () => { let subjectCaller: Address; let subjectCurrentSet: Address; let subjectNextSet: Address; From e50ab1d091e0b59c69e9d2cb69d0167b6119cf8f Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 10:58:01 -0800 Subject: [PATCH 13/35] Refactored liquidator, pushed oracleWhitelist to bounds calculation files. --- contracts/core/liquidators/impl/Auction.sol | 82 ++----------------- .../core/liquidators/impl/LinearAuction.sol | 58 ++----------- .../TwoAssetPriceBoundedLinearAuction.sol | 38 ++++++--- .../core/liquidators/impl/AuctionMock.sol | 5 -- .../liquidators/impl/LinearAuctionMock.sol | 57 ++++++++++--- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 10 ++- utils/helpers/liquidatorHelper.ts | 3 +- 7 files changed, 95 insertions(+), 158 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index fb4158811..1ba12e0d3 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -24,7 +24,6 @@ import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfac import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { ICore } from "../../interfaces/ICore.sol"; -import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; import { SetMath } from "../../lib/SetMath.sol"; @@ -46,7 +45,6 @@ contract Auction { /* ============ Structs ============ */ struct Setup { - uint256 pricePrecision; uint256 minimumBid; uint256 startTime; uint256 startingCurrentSets; @@ -56,21 +54,6 @@ contract Auction { uint256[] combinedNextSetUnits; } - /* ============ Constants ============ */ - uint256 constant public MINIMUM_PRICE_PRECISION = 1000; - - /* ============ State Variables ============ */ - IOracleWhiteList public oracleWhiteList; - - /** - * Auction constructor - * - * @param _oracleWhiteList Oracle WhiteList instance - */ - constructor(IOracleWhiteList _oracleWhiteList) public { - oracleWhiteList = _oracleWhiteList; - } - /* ============ Auction Struct Methods ============ */ /* @@ -89,9 +72,7 @@ contract Auction { ) internal { - _auction.pricePrecision = calculatePricePrecision(_currentSet, _nextSet); - - uint256 minimumBid = calculateMinimumBid(_currentSet, _nextSet, _auction.pricePrecision); + uint256 minimumBid = calculateMinimumBid(_currentSet, _nextSet); // remainingCurrentSets must be greater than minimumBid or no bidding would be allowed require( @@ -169,7 +150,7 @@ contract Auction { returns (Rebalance.TokenFlow memory) { // Normalized quantity amount - uint256 unitsMultiplier = _quantity.div(_auction.minimumBid).mul(_auction.pricePrecision); + uint256 unitsMultiplier = _quantity.div(_auction.minimumBid); address[] memory memCombinedTokenArray = _auction.combinedTokenArray; @@ -194,46 +175,16 @@ contract Auction { return Rebalance.composeTokenFlow(memCombinedTokenArray, inflowUnitArray, outflowUnitArray); } - /** - * Calculates the price precision based on the USD values of the next and current Sets. - */ - function calculatePricePrecision( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - returns (uint256) - { - // Value the sets - uint256 currentSetUSDValue = calculateUSDValueOfSet(_currentSet); - uint256 nextSetUSDValue = calculateUSDValueOfSet(_nextSet); - - // If currentSetValue is 10x greater than nextSetValue calculate required bump in pricePrecision - if (currentSetUSDValue > nextSetUSDValue.mul(10)) { - // Round up valuation to nearest order of magnitude - uint256 orderOfMagnitude = CommonMath.ceilLog10(currentSetUSDValue.div(nextSetUSDValue)); - - // Apply order of magnitude to pricePrecision, since Log10 is rounded up subtract 1 order of - // magnitude - return MINIMUM_PRICE_PRECISION.mul(10 ** orderOfMagnitude).div(10); - } - - return MINIMUM_PRICE_PRECISION; - } - /** * Calculate the minimumBid allowed for the rebalance * * @param _currentSet The Set to rebalance from * @param _nextSet The Set to rebalance to - * @param _pricePrecision Price precision used in auction * @return Minimum bid amount */ function calculateMinimumBid( ISetToken _currentSet, - ISetToken _nextSet, - uint256 _pricePrecision + ISetToken _nextSet ) internal view @@ -241,8 +192,7 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit) - .mul(_pricePrecision); + return Math.max(currentSetNaturalUnit, nextNaturalUnit); } /** @@ -357,13 +307,11 @@ contract Auction { returns (uint256[] memory) { address[] memory combinedTokenArray = _auction.combinedTokenArray; - uint256 pricePrecisionMem = _auction.pricePrecision; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, - pricePrecisionMem, combinedTokenArray[i] ); } @@ -376,14 +324,12 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, uint256 _minimumBid, - uint256 _pricePrecision, address _component ) private @@ -401,8 +347,7 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid, - _pricePrecision + _minimumBid ); } @@ -416,30 +361,17 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid, - uint256 _pricePrecision + uint256 _minimumBid ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) - .div(_pricePrecision); - } - - /** - * Calculate USD value of passed Set - * - * @param _set Instance of SetToken - * @return USDValue USD Value of the Set Token - */ - function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { - return SetUSDValuation.calculateSetTokenDollarValue(_set, oracleWhiteList); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); } } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 94d4da2e0..c5567b835 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -20,7 +20,6 @@ pragma experimental "ABIEncoderV2"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { Auction } from "./Auction.sol"; -import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; @@ -44,29 +43,18 @@ contract LinearAuction is Auction { /* ============ State Variables ============ */ uint256 public auctionPeriod; // Length in seconds of auction - uint256 public rangeStart; // Percentage below FairValue to begin auction at - uint256 public rangeEnd; // Percentage above FairValue to end auction at /** * LinearAuction constructor * - * @param _oracleWhiteList Oracle WhiteList instance * @param _auctionPeriod Length of auction - * @param _rangeStart Percentage below FairValue to begin auction at - * @param _rangeEnd Percentage above FairValue to end auction at */ constructor( - IOracleWhiteList _oracleWhiteList, - uint256 _auctionPeriod, - uint256 _rangeStart, - uint256 _rangeEnd + uint256 _auctionPeriod ) public - Auction(_oracleWhiteList) { auctionPeriod = _auctionPeriod; - rangeStart = _rangeStart; - rangeEnd = _rangeEnd; } /* ============ Internal Functions ============ */ @@ -94,27 +82,13 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction); - _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction); + _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); + _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } /* ============ Internal View Functions ============ */ - function validateRebalanceComponents( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - { - address[] memory combinedTokenArray = Auction.getCombinedTokenArray(_currentSet, _nextSet); - require( - oracleWhiteList.areValidAddresses(combinedTokenArray), - "LinearAuction.validateRebalanceComponents: Passed token does not have matching oracle." - ); - } - /** * Returns the TokenFlow based on the current price */ @@ -165,30 +139,14 @@ contract LinearAuction is Auction { } } - /** - * Calculates the fair value based on the USD values of the next and current Sets. - * Returns a scaled value - */ - function calculateFairValue( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - returns (uint256) - { - uint256 currentSetUSDValue = Auction.calculateUSDValueOfSet(_currentSet); - uint256 nextSetUSDValue = Auction.calculateUSDValueOfSet(_nextSet); - - return nextSetUSDValue.scale().div(currentSetUSDValue); - } - /** * Abstract function that must be implemented. * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view @@ -199,7 +157,9 @@ contract LinearAuction is Auction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 8ba66a607..78c561cc9 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -45,6 +45,17 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 constant private CURVE_DENOMINATOR = 10 ** 18; uint256 constant private ONE_HUNDRED = 100; + IOracleWhiteList public oracleWhiteList; + uint256 public rangeStart; // Percentage below FairValue to begin auction at + uint256 public rangeEnd; // Percentage above FairValue to end auction at + + /** + * LinearAuction constructor + * + * @param _auctionPeriod Length of auction + * @param _rangeStart Percentage below FairValue to begin auction at + * @param _rangeEnd Percentage above FairValue to end auction at + */ constructor( IOracleWhiteList _oracleWhiteList, uint256 _auctionPeriod, @@ -52,13 +63,12 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 _rangeEnd ) public - LinearAuction( - _oracleWhiteList, - _auctionPeriod, - _rangeStart, - _rangeEnd - ) - {} + LinearAuction(_auctionPeriod) + { + oracleWhiteList = _oracleWhiteList; + rangeStart = _rangeStart; + rangeEnd = _rangeEnd; + } /** * Validates that the auction only includes two components and the components are valid. @@ -76,9 +86,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { "TwoAssetPriceBoundedLinearAuction: Only two components are allowed." ); - LinearAuction.validateRebalanceComponents( - _currentSet, - _nextSet + require( + oracleWhiteList.areValidAddresses(combinedTokenArray), + "TwoAssetPriceBoundedLinearAuction: Passed token does not have matching oracle." ); } @@ -86,7 +96,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view @@ -129,7 +141,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view diff --git a/contracts/mocks/core/liquidators/impl/AuctionMock.sol b/contracts/mocks/core/liquidators/impl/AuctionMock.sol index 705a8146f..81d75d93f 100644 --- a/contracts/mocks/core/liquidators/impl/AuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/AuctionMock.sol @@ -8,11 +8,6 @@ import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.s contract AuctionMock is Auction { Auction.Setup public auction; - constructor(IOracleWhiteList _oracleWhiteList) - public - Auction(_oracleWhiteList) - {} - function initializeAuction( ISetToken _currentSet, ISetToken _nextSet, diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index db67331b5..6b55a0741 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -10,6 +10,10 @@ import { SetUSDValuation } from "../../../../core/liquidators/impl/SetUSDValuati contract LinearAuctionMock is LinearAuction { LinearAuction.State public auction; + IOracleWhiteList public oracleWhiteList; + + uint256 public rangeStart; // Percentage below FairValue to begin auction at + uint256 public rangeEnd; // Percentage above FairValue to end auction at constructor( IOracleWhiteList _oracleWhiteList, @@ -19,35 +23,58 @@ contract LinearAuctionMock is LinearAuction { ) public LinearAuction( - _oracleWhiteList, - _auctionPeriod, - _rangeStart, - _rangeEnd + _auctionPeriod ) - {} + { + oracleWhiteList = _oracleWhiteList; + rangeStart = _rangeStart; + rangeEnd = _rangeEnd; + } function calculateStartPrice( State storage _linearAuction, - uint256 _fairValueScaled + ISetToken _currentSet, + ISetToken _nextSet ) internal view returns(uint256) { - uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); - return _fairValueScaled.sub(startRange); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + uint256 startRange = fairValue.mul(rangeStart).div(100); + return fairValue.sub(startRange); } function calculateEndPrice( State storage _linearAuction, - uint256 _fairValueScaled + ISetToken _currentSet, + ISetToken _nextSet ) internal view returns(uint256) { - uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); - return _fairValueScaled.add(endRange); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + uint256 endRange = fairValue.mul(rangeEnd).div(100); + return fairValue.add(endRange); + } + + /** + * Calculates the fair value based on the USD values of the next and current Sets. + * Returns a scaled value + */ + function calculateFairValue( + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256) + { + uint256 currentSetUSDValue = calculateUSDValueOfSet(_currentSet); + uint256 nextSetUSDValue = calculateUSDValueOfSet(_nextSet); + + return nextSetUSDValue.scale().div(currentSetUSDValue); } function initializeLinearAuction( @@ -82,8 +109,14 @@ contract LinearAuctionMock is LinearAuction { return super.getTokenFlow(auction, _quantity); } + /** + * Calculate USD value of passed Set + * + * @param _set Instance of SetToken + * @return USDValue USD Value of the Set Token + */ function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { - return super.calculateUSDValueOfSet(_set); + return SetUSDValuation.calculateSetTokenDollarValue(_set, oracleWhiteList); } } diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 4f8ce46ac..9a0d46926 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -54,12 +54,16 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); } - function calculateStartPriceMock() external view returns(uint256) { - return super.calculateStartPrice(auctionInfo.auction); + function calculateStartPriceMock() external view returns(uint256) { + ISetToken currentSet = ISetToken(address(0)); + ISetToken nextSet = ISetToken(address(0)); + return super.calculateStartPrice(auctionInfo.auction, currentSet, nextSet); } function calculateEndPriceMock() external view returns(uint256) { - return super.calculateEndPrice(auctionInfo.auction); + ISetToken currentSet = ISetToken(address(0)); + ISetToken nextSet = ISetToken(address(0)); + return super.calculateEndPrice(auctionInfo.auction, currentSet, nextSet); } function parameterizeAuction( diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 231df8289..ce0032e35 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -54,10 +54,9 @@ export class LiquidatorHelper { /* ============ Deployment ============ */ public async deployAuctionMockAsync( - oracleWhiteList: Address, from: Address = this._contractOwnerAddress ): Promise { - const auctionMock = await AuctionMock.new(oracleWhiteList, txnFrom(from)); + const auctionMock = await AuctionMock.new(txnFrom(from)); return new AuctionMockContract(getContractInstance(auctionMock), txnFrom(from)); } From c2e60382c0318297b227af8731894c5def07a6e9 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 11:58:21 -0800 Subject: [PATCH 14/35] Updated tests for refactor. --- .../core/liquidators/impl/auction.spec.ts | 1086 +++++++-------- .../liquidators/impl/linearAuction.spec.ts | 1172 ++++++++--------- .../twoAssetPriceBoundedLinearAuction.spec.ts | 44 +- .../linearAuctionLiquidator.spec.ts | 40 +- utils/auction.ts | 3 - utils/helpers/liquidatorHelper.ts | 4 +- 6 files changed, 1175 insertions(+), 1174 deletions(-) diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index 19a50bf31..6b5ccb9dc 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -1,549 +1,549 @@ -require('module-alias/register'); - -import * as ABIDecoder from 'abi-decoder'; -import * as _ from 'lodash'; -import * as chai from 'chai'; -import { BigNumber } from 'bignumber.js'; -import { Address } from 'set-protocol-utils'; - -import ChaiSetup from '@utils/chaiSetup'; -import { BigNumberSetup } from '@utils/bigNumberSetup'; -import { - CoreContract, - SetTokenContract, - SetTokenFactoryContract, - StandardTokenMockContract, - AuctionMockContract, - OracleWhiteListContract, - TransferProxyContract, - UpdatableOracleMockContract, - VaultContract, -} from '@utils/contracts'; -import { expectRevertError } from '@utils/tokenAssertions'; -import { Blockchain } from '@utils/blockchain'; -import { getWeb3 } from '@utils/web3Helper'; -import { - DEFAULT_GAS, -} from '@utils/constants'; -import { ether, gWei } from '@utils/units'; - -import { CoreHelper } from '@utils/helpers/coreHelper'; -import { ERC20Helper } from '@utils/helpers/erc20Helper'; -import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -BigNumberSetup.configure(); -ChaiSetup.configure(); -const web3 = getWeb3(); -const { expect } = chai; -const blockchain = new Blockchain(web3); -const Core = artifacts.require('Core'); -const AuctionMock = artifacts.require('AuctionMock'); - -contract('Auction', accounts => { - const [ - ownerAccount, - functionCaller, - ] = accounts; - - let core: CoreContract; - let transferProxy: TransferProxyContract; - let vault: VaultContract; - let setTokenFactory: SetTokenFactoryContract; - let auctionMock: AuctionMockContract; - let oracleWhiteList: OracleWhiteListContract; - - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); - - let component1: StandardTokenMockContract; - let component2: StandardTokenMockContract; - let component3: StandardTokenMockContract; - - let component1Price: BigNumber; - let component2Price: BigNumber; - let component3Price: BigNumber; - - let component1Oracle: UpdatableOracleMockContract; - let component2Oracle: UpdatableOracleMockContract; - let component3Oracle: UpdatableOracleMockContract; - - let set1: SetTokenContract; - let set2: SetTokenContract; - - let set1Components: Address[]; - let set2Components: Address[]; - - let set1Units: BigNumber[]; - let set2Units: BigNumber[]; - - let set1NaturalUnit: BigNumber; - let set2NaturalUnit: BigNumber; - - before(async () => { - ABIDecoder.addABI(Core.abi); - ABIDecoder.addABI(AuctionMock.abi); - - transferProxy = await coreHelper.deployTransferProxyAsync(); - vault = await coreHelper.deployVaultAsync(); - core = await coreHelper.deployCoreAsync(transferProxy, vault); - - setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - - await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - - component1 = await erc20Helper.deployTokenAsync(ownerAccount); - component2 = await erc20Helper.deployTokenAsync(ownerAccount); - component3 = await erc20Helper.deployTokenAsync(ownerAccount); - - component1Price = ether(1); - component2Price = ether(2); - component3Price = ether(1); - - component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); - component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); - component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - - oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( - [component1.address, component2.address, component3.address], - [component1Oracle.address, component2Oracle.address, component3Oracle.address], - ); - - set1Components = [component1.address, component2.address]; - set1Units = [gWei(1), gWei(1)]; - set1NaturalUnit = gWei(2); - set1 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set1Components, - set1Units, - set1NaturalUnit, - ); - - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; - set2NaturalUnit = gWei(1); - set2 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set2Components, - set2Units, - set2NaturalUnit, - ); - }); - - after(async () => { - ABIDecoder.removeABI(Core.abi); - ABIDecoder.removeABI(AuctionMock.abi); - }); - - beforeEach(async () => { - await blockchain.saveSnapshotAsync(); - }); - - afterEach(async () => { - await blockchain.revertAsync(); - }); - - describe('#constructor', async () => { - let subjectWhiteList: Address; - - beforeEach(async () => { - subjectWhiteList = oracleWhiteList.address; - }); +// require('module-alias/register'); + +// import * as ABIDecoder from 'abi-decoder'; +// import * as _ from 'lodash'; +// import * as chai from 'chai'; +// import { BigNumber } from 'bignumber.js'; +// import { Address } from 'set-protocol-utils'; + +// import ChaiSetup from '@utils/chaiSetup'; +// import { BigNumberSetup } from '@utils/bigNumberSetup'; +// import { +// CoreContract, +// SetTokenContract, +// SetTokenFactoryContract, +// StandardTokenMockContract, +// AuctionMockContract, +// OracleWhiteListContract, +// TransferProxyContract, +// UpdatableOracleMockContract, +// VaultContract, +// } from '@utils/contracts'; +// import { expectRevertError } from '@utils/tokenAssertions'; +// import { Blockchain } from '@utils/blockchain'; +// import { getWeb3 } from '@utils/web3Helper'; +// import { +// DEFAULT_GAS, +// } from '@utils/constants'; +// import { ether, gWei } from '@utils/units'; + +// import { CoreHelper } from '@utils/helpers/coreHelper'; +// import { ERC20Helper } from '@utils/helpers/erc20Helper'; +// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +// BigNumberSetup.configure(); +// ChaiSetup.configure(); +// const web3 = getWeb3(); +// const { expect } = chai; +// const blockchain = new Blockchain(web3); +// const Core = artifacts.require('Core'); +// const AuctionMock = artifacts.require('AuctionMock'); + +// contract('Auction', accounts => { +// const [ +// ownerAccount, +// functionCaller, +// ] = accounts; + +// let core: CoreContract; +// let transferProxy: TransferProxyContract; +// let vault: VaultContract; +// let setTokenFactory: SetTokenFactoryContract; +// let auctionMock: AuctionMockContract; +// let oracleWhiteList: OracleWhiteListContract; + +// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); +// const erc20Helper = new ERC20Helper(ownerAccount); +// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); +// const libraryMockHelper = new LibraryMockHelper(ownerAccount); + +// let component1: StandardTokenMockContract; +// let component2: StandardTokenMockContract; +// let component3: StandardTokenMockContract; + +// let component1Price: BigNumber; +// let component2Price: BigNumber; +// let component3Price: BigNumber; + +// let component1Oracle: UpdatableOracleMockContract; +// let component2Oracle: UpdatableOracleMockContract; +// let component3Oracle: UpdatableOracleMockContract; + +// let set1: SetTokenContract; +// let set2: SetTokenContract; + +// let set1Components: Address[]; +// let set2Components: Address[]; + +// let set1Units: BigNumber[]; +// let set2Units: BigNumber[]; + +// let set1NaturalUnit: BigNumber; +// let set2NaturalUnit: BigNumber; + +// before(async () => { +// ABIDecoder.addABI(Core.abi); +// ABIDecoder.addABI(AuctionMock.abi); + +// transferProxy = await coreHelper.deployTransferProxyAsync(); +// vault = await coreHelper.deployVaultAsync(); +// core = await coreHelper.deployCoreAsync(transferProxy, vault); + +// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + +// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + +// component1 = await erc20Helper.deployTokenAsync(ownerAccount); +// component2 = await erc20Helper.deployTokenAsync(ownerAccount); +// component3 = await erc20Helper.deployTokenAsync(ownerAccount); + +// component1Price = ether(1); +// component2Price = ether(2); +// component3Price = ether(1); + +// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); +// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); +// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + +// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( +// [component1.address, component2.address, component3.address], +// [component1Oracle.address, component2Oracle.address, component3Oracle.address], +// ); + +// set1Components = [component1.address, component2.address]; +// set1Units = [gWei(1), gWei(1)]; +// set1NaturalUnit = gWei(2); +// set1 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set1Components, +// set1Units, +// set1NaturalUnit, +// ); + +// set2Components = [component2.address, component3.address]; +// set2Units = [gWei(1), gWei(1)]; +// set2NaturalUnit = gWei(1); +// set2 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set2Components, +// set2Units, +// set2NaturalUnit, +// ); +// }); + +// after(async () => { +// ABIDecoder.removeABI(Core.abi); +// ABIDecoder.removeABI(AuctionMock.abi); +// }); + +// beforeEach(async () => { +// await blockchain.saveSnapshotAsync(); +// }); + +// afterEach(async () => { +// await blockchain.revertAsync(); +// }); + +// describe('#constructor', async () => { +// let subjectWhiteList: Address; + +// beforeEach(async () => { +// subjectWhiteList = oracleWhiteList.address; +// }); - async function subject(): Promise { - return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); - } +// async function subject(): Promise { +// return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); +// } - it('sets the correct oracleWhiteList', async () => { - auctionMock = await subject(); +// it('sets the correct oracleWhiteList', async () => { +// auctionMock = await subject(); - const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); - expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); - }); - }); +// const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); +// expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); +// }); +// }); - describe('#initializeAuction', async () => { - let subjectCaller: Address; - let subjectCurrentSet: Address; - let subjectNextSet: Address; - let subjectStartingCurrentSetQuantity: BigNumber; +// describe('#initializeAuction', async () => { +// let subjectCaller: Address; +// let subjectCurrentSet: Address; +// let subjectNextSet: Address; +// let subjectStartingCurrentSetQuantity: BigNumber; - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - subjectCaller = functionCaller; - subjectCurrentSet = set1.address; - subjectNextSet = set2.address; - subjectStartingCurrentSetQuantity = ether(10); - }); - - after(async () => { - }); - - async function subject(): Promise { - return auctionMock.initializeAuction.sendTransactionAsync( - subjectCurrentSet, - subjectNextSet, - subjectStartingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - set1, - set2, - oracleWhiteList - ); - - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct minimumBid', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const pricePrecision = auctionSetup.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); - expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); - }); - - it('sets the correct startTime', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const { timestamp } = await web3.eth.getBlock('latest'); - expect(auctionSetup.startTime).to.bignumber.equal(timestamp); - }); - - it('sets the correct startingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); - }); - - it('sets the correct remainingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); - }); - - it('sets the correct combinedTokenArray', async () => { - await subject(); - - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const expectedResult = _.union(set1Components, set2Components); - - expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); - }); - - it('sets the correct combinedCurrentSetUnits', async () => { - await subject(); - - const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); - - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( - set1, - combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision - ); - - expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); - }); - - it('sets the correct combinedNextSetUnits', async () => { - await subject(); - - const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( - set2, - combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision - ); - - expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); - }); - - describe('when currentSet is greater than 10x the nextSet', async () => { - beforeEach(async () => { - const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; - const setNaturalUnit = gWei(300); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - setComponents, - setUnits, - setNaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - }); - - describe('when currentSet is 1-2x greater than nextSet', async () => { - beforeEach(async () => { - const set3Components = [component1.address, component3.address]; - const set3Units = [gWei(1), gWei(1)]; - const set3NaturalUnit = gWei(2); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set3Components, - set3Units, - set3NaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - }); - - describe('when there is insufficient collateral to rebalance', async () => { - beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(10); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#reduceRemainingCurrentSets', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - let subjectReductionQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectReductionQuantity = ether(5); - }); - - async function subject(): Promise { - return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - subjectReductionQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('calculates the correct new remainingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); - expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); - }); - }); - - describe('#validateBidQuantity', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - let subjectQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectQuantity = ether(5); - }); - - async function subject(): Promise { - return auctionMock.validateBidQuantity.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('does not revert', async () => { - await subject(); - }); - - describe('when the quantity is not a multiple of the minimumBid', async () => { - beforeEach(async () => { - const auctionSetup: any = await auctionMock.auction.callAsync(); - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(auctionSetup.pricePrecision) - .div(2); - subjectQuantity = gWei(10).plus(halfMinimumBid); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - - describe('when the quantity is more than the remainingCurrentsets', async () => { - beforeEach(async () => { - subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#validateAuctionCompletion', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - let reductionQuantity: BigNumber; - let customReductionQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - reductionQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - async function subject(): Promise { - return auctionMock.validateAuctionCompletion.sendTransactionAsync( - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('should not revert', async () => { - await subject(); - }); - - describe('when the auction is not complete', async () => { - before(async () => { - customReductionQuantity = startingCurrentSetQuantity.div(2); - }); - - after(async () => { - customReductionQuantity = undefined; - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#isAuctionActive', async () => { - let subjectCaller: Address; - let startingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - }); - - async function subject(): Promise { - return auctionMock.isAuctionActive.callAsync( - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('should return false', async () => { - const result = await subject(); - expect(result).to.equal(false); - }); - - describe('when the auction has begun', async () => { - beforeEach(async () => { - startingCurrentSetQuantity = ether(10); - - await blockchain.increaseTimeAsync(new BigNumber(1000)); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return true', async () => { - const result = await subject(); - expect(result).to.equal(true); - }); - }); - }); -}); \ No newline at end of file +// subjectCaller = functionCaller; +// subjectCurrentSet = set1.address; +// subjectNextSet = set2.address; +// subjectStartingCurrentSetQuantity = ether(10); +// }); + +// after(async () => { +// }); + +// async function subject(): Promise { +// return auctionMock.initializeAuction.sendTransactionAsync( +// subjectCurrentSet, +// subjectNextSet, +// subjectStartingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// set1, +// set2, +// oracleWhiteList +// ); + +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct minimumBid', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const pricePrecision = auctionSetup.pricePrecision; +// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); +// expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); +// }); + +// it('sets the correct startTime', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// expect(auctionSetup.startTime).to.bignumber.equal(timestamp); +// }); + +// it('sets the correct startingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); +// }); + +// it('sets the correct remainingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); +// }); + +// it('sets the correct combinedTokenArray', async () => { +// await subject(); + +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const expectedResult = _.union(set1Components, set2Components); + +// expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); +// }); + +// it('sets the correct combinedCurrentSetUnits', async () => { +// await subject(); + +// const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); + +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( +// set1, +// combinedTokenArray, +// new BigNumber(auctionSetup.minimumBid), +// auctionSetup.pricePrecision +// ); + +// expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); +// }); + +// it('sets the correct combinedNextSetUnits', async () => { +// await subject(); + +// const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( +// set2, +// combinedTokenArray, +// new BigNumber(auctionSetup.minimumBid), +// auctionSetup.pricePrecision +// ); + +// expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); +// }); + +// describe('when currentSet is greater than 10x the nextSet', async () => { +// beforeEach(async () => { +// const setComponents = [component1.address, component2.address]; +// const setUnits = [gWei(1), gWei(1)]; +// const setNaturalUnit = gWei(300); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// setComponents, +// setUnits, +// setNaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); +// }); + +// describe('when currentSet is 1-2x greater than nextSet', async () => { +// beforeEach(async () => { +// const set3Components = [component1.address, component3.address]; +// const set3Units = [gWei(1), gWei(1)]; +// const set3NaturalUnit = gWei(2); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set3Components, +// set3Units, +// set3NaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); +// }); + +// describe('when there is insufficient collateral to rebalance', async () => { +// beforeEach(async () => { +// subjectStartingCurrentSetQuantity = gWei(10); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#reduceRemainingCurrentSets', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// let subjectReductionQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectReductionQuantity = ether(5); +// }); + +// async function subject(): Promise { +// return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// subjectReductionQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('calculates the correct new remainingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); +// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); +// }); +// }); + +// describe('#validateBidQuantity', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// let subjectQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectQuantity = ether(5); +// }); + +// async function subject(): Promise { +// return auctionMock.validateBidQuantity.sendTransactionAsync( +// subjectQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('does not revert', async () => { +// await subject(); +// }); + +// describe('when the quantity is not a multiple of the minimumBid', async () => { +// beforeEach(async () => { +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) +// .mul(auctionSetup.pricePrecision) +// .div(2); +// subjectQuantity = gWei(10).plus(halfMinimumBid); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); + +// describe('when the quantity is more than the remainingCurrentsets', async () => { +// beforeEach(async () => { +// subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#validateAuctionCompletion', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; +// let reductionQuantity: BigNumber; +// let customReductionQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// reductionQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// async function subject(): Promise { +// return auctionMock.validateAuctionCompletion.sendTransactionAsync( +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('should not revert', async () => { +// await subject(); +// }); + +// describe('when the auction is not complete', async () => { +// before(async () => { +// customReductionQuantity = startingCurrentSetQuantity.div(2); +// }); + +// after(async () => { +// customReductionQuantity = undefined; +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#isAuctionActive', async () => { +// let subjectCaller: Address; +// let startingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// }); + +// async function subject(): Promise { +// return auctionMock.isAuctionActive.callAsync( +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('should return false', async () => { +// const result = await subject(); +// expect(result).to.equal(false); +// }); + +// describe('when the auction has begun', async () => { +// beforeEach(async () => { +// startingCurrentSetQuantity = ether(10); + +// await blockchain.increaseTimeAsync(new BigNumber(1000)); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return true', async () => { +// const result = await subject(); +// expect(result).to.equal(true); +// }); +// }); +// }); +// }); \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index f7bfa03d8..03cfa8f85 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -1,586 +1,586 @@ -require('module-alias/register'); - -import * as ABIDecoder from 'abi-decoder'; -import * as _ from 'lodash'; -import * as chai from 'chai'; -import { BigNumber } from 'bignumber.js'; -import { Address } from 'set-protocol-utils'; - -import ChaiSetup from '@utils/chaiSetup'; -import { BigNumberSetup } from '@utils/bigNumberSetup'; -import { - CoreContract, - OracleWhiteListContract, - SetTokenContract, - SetTokenFactoryContract, - StandardTokenMockContract, - LinearAuctionMockContract, - TransferProxyContract, - UpdatableOracleMockContract, - VaultContract, -} from '@utils/contracts'; -import { Blockchain } from '@utils/blockchain'; -import { getWeb3 } from '@utils/web3Helper'; -import { - DEFAULT_GAS, - ONE_DAY_IN_SECONDS, -} from '@utils/constants'; -import { ether, gWei } from '@utils/units'; -import { getLinearAuction, TokenFlow } from '@utils/auction'; - -import { CoreHelper } from '@utils/helpers/coreHelper'; -import { ERC20Helper } from '@utils/helpers/erc20Helper'; -import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -BigNumberSetup.configure(); -ChaiSetup.configure(); -const web3 = getWeb3(); -const { expect } = chai; -const blockchain = new Blockchain(web3); -const Core = artifacts.require('Core'); -const LinearAuctionMock = artifacts.require('LinearAuctionMock'); - -contract('LinearAuction', accounts => { - const [ - ownerAccount, - functionCaller, - ] = accounts; - - let core: CoreContract; - let transferProxy: TransferProxyContract; - let vault: VaultContract; - let setTokenFactory: SetTokenFactoryContract; - let auctionMock: LinearAuctionMockContract; - - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - - let auctionPeriod: BigNumber; - let rangeStart: BigNumber; - let rangeEnd: BigNumber; - let oracleWhiteList: OracleWhiteListContract; - - let component1: StandardTokenMockContract; - let component2: StandardTokenMockContract; - let component3: StandardTokenMockContract; - - let component1Price: BigNumber; - let component2Price: BigNumber; - let component3Price: BigNumber; - - let set1: SetTokenContract; - let set2: SetTokenContract; - - let set1Components: Address[]; - let set2Components: Address[]; - - let set1Units: BigNumber[]; - let set2Units: BigNumber[]; - - let set1NaturalUnit: BigNumber; - let set2NaturalUnit: BigNumber; - - let component1Oracle: UpdatableOracleMockContract; - let component2Oracle: UpdatableOracleMockContract; - let component3Oracle: UpdatableOracleMockContract; - - before(async () => { - ABIDecoder.addABI(Core.abi); - ABIDecoder.addABI(LinearAuctionMock.abi); - - transferProxy = await coreHelper.deployTransferProxyAsync(); - vault = await coreHelper.deployVaultAsync(); - core = await coreHelper.deployCoreAsync(transferProxy, vault); - - setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - - component1 = await erc20Helper.deployTokenAsync(ownerAccount); - component2 = await erc20Helper.deployTokenAsync(ownerAccount); - component3 = await erc20Helper.deployTokenAsync(ownerAccount); - - set1Components = [component1.address, component2.address]; - set1Units = [gWei(1), gWei(1)]; - set1NaturalUnit = gWei(1); - set1 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set1Components, - set1Units, - set1NaturalUnit, - ); - - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; - set2NaturalUnit = gWei(2); - set2 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set2Components, - set2Units, - set2NaturalUnit, - ); - - component1Price = ether(1); - component2Price = ether(2); - component3Price = ether(1); - - component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); - component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); - component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - - oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( - [component1.address, component2.address, component3.address], - [component1Oracle.address, component2Oracle.address, component3Oracle.address], - ); - - auctionPeriod = ONE_DAY_IN_SECONDS; - rangeStart = new BigNumber(10); // 10% above fair value - rangeEnd = new BigNumber(10); // 10% below fair value - - auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( - oracleWhiteList.address, - auctionPeriod, - rangeStart, - rangeEnd, - ); - - }); - - after(async () => { - ABIDecoder.removeABI(Core.abi); - ABIDecoder.removeABI(LinearAuctionMock.abi); - }); - - beforeEach(async () => { - await blockchain.saveSnapshotAsync(); - }); - - afterEach(async () => { - await blockchain.revertAsync(); - }); - - describe('#constructor', async () => { - it('sets the correct auctionPeriod', async () => { - const result = await auctionMock.auctionPeriod.callAsync(); - expect(result).to.bignumber.equal(auctionPeriod); - }); - - it('sets the correct rangeStart', async () => { - const result = await auctionMock.rangeStart.callAsync(); - expect(result).to.bignumber.equal(rangeStart); - }); - - it('sets the correct rangeEnd', async () => { - const result = await auctionMock.rangeEnd.callAsync(); - expect(result).to.bignumber.equal(rangeEnd); - }); - }); - - describe('#initializeLinearAuction', async () => { - let subjectCaller: Address; - let subjectCurrentSet: Address; - let subjectNextSet: Address; - let subjectStartingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - subjectCaller = functionCaller; - subjectCurrentSet = set1.address; - subjectNextSet = set2.address; - subjectStartingCurrentSetQuantity = ether(10); - }); - - after(async () => { - }); - - async function subject(): Promise { - return auctionMock.initializeLinearAuction.sendTransactionAsync( - subjectCurrentSet, - subjectNextSet, - subjectStartingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('sets the correct minimumBid', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); - expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); - }); - - it('sets the correct endTime', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); - - const { timestamp } = await web3.eth.getBlock('latest'); - const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); - expect(auction.endTime).to.bignumber.equal(expectedEndTime); - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct startPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeStart = await auctionMock.rangeStart.callAsync(); - - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - const expectedStartPrice = fairValue.sub(negativeRange); - expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); - }); - - it('sets the correct endPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - const expectedEndPrice = fairValue.add(positiveRange); - expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); - }); - - describe('when currentSet is greater than 10x the nextSet', async () => { - beforeEach(async () => { - const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; - const setNaturalUnit = gWei(101); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - setComponents, - setUnits, - setNaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct startPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeStart = await auctionMock.rangeStart.callAsync(); - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - const expectedStartPrice = fairValue.sub(negativeRange); - - expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); - }); - - it('sets the correct endPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - const expectedEndPrice = fairValue.add(positiveRange); - - expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); - }); - }); - }); - - describe('[CONTEXT] Initialized auction', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeLinearAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - describe('#getPrice', async () => { - async function subject(): Promise { - return auctionMock.getPrice.callAsync(); - } - - it('returns the correct result', async () => { - const result = await subject(); - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(result).to.bignumber.equal(currentPrice); - }); - - describe('when the auction has elapsed half the period', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.div(2)); - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('returns the correct price', async () => { - const result = await subject(); - - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(result).to.bignumber.equal(currentPrice); - }); - }); - - describe('when the timestamp has exceeded the endTime', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(100)); - - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('returns the correct price / endPrice', async () => { - const result = await subject(); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(result).to.bignumber.equal(linearAuction.endPrice); - }); - }); - }); - - describe('#getPrice', async () => { - async function subject(): Promise { - return auctionMock.getPrice.callAsync(); - } - - it('returns the correct numerator', async () => { - const numerator = await subject(); - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(numerator).to.bignumber.equal(currentPrice); - }); - }); - - describe('#getTokenFlow', async () => { - let subjectQuantity: BigNumber; - - let tokenFlows: TokenFlow; - - beforeEach(async () => { - subjectQuantity = startingCurrentSetQuantity; - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const { timestamp } = await web3.eth.getBlock('latest'); - - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - - tokenFlows = liquidatorHelper.constructTokenFlow( - linearAuction, - linearAuction.auction.pricePrecision, - subjectQuantity, - currentPrice, - ); - }); - - async function subject(): Promise { - return auctionMock.getTokenFlow.callAsync(subjectQuantity); - } - - it('returns the token array', async () => { - const { addresses } = await subject(); - expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); - }); - - it('returns the correct inflow', async () => { - const { inflow } = await subject(); - expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); - }); - - it('returns the correct outflow', async () => { - const { outflow } = await subject(); - expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); - }); - - describe('when the auction has elapsed half the period', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.div(2)); - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectQuantity = startingCurrentSetQuantity.div(2); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const { timestamp } = await web3.eth.getBlock('latest'); - - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - - tokenFlows = liquidatorHelper.constructTokenFlow( - linearAuction, - linearAuction.auction.pricePrecision, - subjectQuantity, - currentPrice, - ); - }); - - it('returns the token array', async () => { - const { addresses } = await subject(); - expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); - }); - - it('returns the correct inflow', async () => { - const { inflow } = await subject(); - expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); - }); - - it('returns the correct outflow', async () => { - const { outflow } = await subject(); - expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); - }); - }); - }); - - describe('#hasAuctionFailed', async () => { - async function subject(): Promise { - return auctionMock.hasAuctionFailed.callAsync(); - } - - it('returns false', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - - describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return true', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - }); - - describe('when the auction has been completed', async () => { - beforeEach(async () => { - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return false', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - }); - - describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - - await blockchain.mineBlockAsync(); - }); - - it('should return true', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.true; - }); - }); - }); - }); -}); \ No newline at end of file +// require('module-alias/register'); + +// import * as ABIDecoder from 'abi-decoder'; +// import * as _ from 'lodash'; +// import * as chai from 'chai'; +// import { BigNumber } from 'bignumber.js'; +// import { Address } from 'set-protocol-utils'; + +// import ChaiSetup from '@utils/chaiSetup'; +// import { BigNumberSetup } from '@utils/bigNumberSetup'; +// import { +// CoreContract, +// OracleWhiteListContract, +// SetTokenContract, +// SetTokenFactoryContract, +// StandardTokenMockContract, +// LinearAuctionMockContract, +// TransferProxyContract, +// UpdatableOracleMockContract, +// VaultContract, +// } from '@utils/contracts'; +// import { Blockchain } from '@utils/blockchain'; +// import { getWeb3 } from '@utils/web3Helper'; +// import { +// DEFAULT_GAS, +// ONE_DAY_IN_SECONDS, +// } from '@utils/constants'; +// import { ether, gWei } from '@utils/units'; +// import { getLinearAuction, TokenFlow } from '@utils/auction'; + +// import { CoreHelper } from '@utils/helpers/coreHelper'; +// import { ERC20Helper } from '@utils/helpers/erc20Helper'; +// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +// BigNumberSetup.configure(); +// ChaiSetup.configure(); +// const web3 = getWeb3(); +// const { expect } = chai; +// const blockchain = new Blockchain(web3); +// const Core = artifacts.require('Core'); +// const LinearAuctionMock = artifacts.require('LinearAuctionMock'); + +// contract('LinearAuction', accounts => { +// const [ +// ownerAccount, +// functionCaller, +// ] = accounts; + +// let core: CoreContract; +// let transferProxy: TransferProxyContract; +// let vault: VaultContract; +// let setTokenFactory: SetTokenFactoryContract; +// let auctionMock: LinearAuctionMockContract; + +// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); +// const erc20Helper = new ERC20Helper(ownerAccount); +// const libraryMockHelper = new LibraryMockHelper(ownerAccount); +// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + +// let auctionPeriod: BigNumber; +// let rangeStart: BigNumber; +// let rangeEnd: BigNumber; +// let oracleWhiteList: OracleWhiteListContract; + +// let component1: StandardTokenMockContract; +// let component2: StandardTokenMockContract; +// let component3: StandardTokenMockContract; + +// let component1Price: BigNumber; +// let component2Price: BigNumber; +// let component3Price: BigNumber; + +// let set1: SetTokenContract; +// let set2: SetTokenContract; + +// let set1Components: Address[]; +// let set2Components: Address[]; + +// let set1Units: BigNumber[]; +// let set2Units: BigNumber[]; + +// let set1NaturalUnit: BigNumber; +// let set2NaturalUnit: BigNumber; + +// let component1Oracle: UpdatableOracleMockContract; +// let component2Oracle: UpdatableOracleMockContract; +// let component3Oracle: UpdatableOracleMockContract; + +// before(async () => { +// ABIDecoder.addABI(Core.abi); +// ABIDecoder.addABI(LinearAuctionMock.abi); + +// transferProxy = await coreHelper.deployTransferProxyAsync(); +// vault = await coreHelper.deployVaultAsync(); +// core = await coreHelper.deployCoreAsync(transferProxy, vault); + +// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); +// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + +// component1 = await erc20Helper.deployTokenAsync(ownerAccount); +// component2 = await erc20Helper.deployTokenAsync(ownerAccount); +// component3 = await erc20Helper.deployTokenAsync(ownerAccount); + +// set1Components = [component1.address, component2.address]; +// set1Units = [gWei(1), gWei(1)]; +// set1NaturalUnit = gWei(1); +// set1 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set1Components, +// set1Units, +// set1NaturalUnit, +// ); + +// set2Components = [component2.address, component3.address]; +// set2Units = [gWei(1), gWei(1)]; +// set2NaturalUnit = gWei(2); +// set2 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set2Components, +// set2Units, +// set2NaturalUnit, +// ); + +// component1Price = ether(1); +// component2Price = ether(2); +// component3Price = ether(1); + +// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); +// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); +// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + +// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( +// [component1.address, component2.address, component3.address], +// [component1Oracle.address, component2Oracle.address, component3Oracle.address], +// ); + +// auctionPeriod = ONE_DAY_IN_SECONDS; +// rangeStart = new BigNumber(10); // 10% above fair value +// rangeEnd = new BigNumber(10); // 10% below fair value + +// auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( +// oracleWhiteList.address, +// auctionPeriod, +// rangeStart, +// rangeEnd, +// ); + +// }); + +// after(async () => { +// ABIDecoder.removeABI(Core.abi); +// ABIDecoder.removeABI(LinearAuctionMock.abi); +// }); + +// beforeEach(async () => { +// await blockchain.saveSnapshotAsync(); +// }); + +// afterEach(async () => { +// await blockchain.revertAsync(); +// }); + +// describe('#constructor', async () => { +// it('sets the correct auctionPeriod', async () => { +// const result = await auctionMock.auctionPeriod.callAsync(); +// expect(result).to.bignumber.equal(auctionPeriod); +// }); + +// it('sets the correct rangeStart', async () => { +// const result = await auctionMock.rangeStart.callAsync(); +// expect(result).to.bignumber.equal(rangeStart); +// }); + +// it('sets the correct rangeEnd', async () => { +// const result = await auctionMock.rangeEnd.callAsync(); +// expect(result).to.bignumber.equal(rangeEnd); +// }); +// }); + +// describe('#initializeLinearAuction', async () => { +// let subjectCaller: Address; +// let subjectCurrentSet: Address; +// let subjectNextSet: Address; +// let subjectStartingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// subjectCaller = functionCaller; +// subjectCurrentSet = set1.address; +// subjectNextSet = set2.address; +// subjectStartingCurrentSetQuantity = ether(10); +// }); + +// after(async () => { +// }); + +// async function subject(): Promise { +// return auctionMock.initializeLinearAuction.sendTransactionAsync( +// subjectCurrentSet, +// subjectNextSet, +// subjectStartingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('sets the correct minimumBid', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const pricePrecision = auction.auction.pricePrecision; +// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) +// .mul(pricePrecision); +// expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); +// }); + +// it('sets the correct endTime', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); +// const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); +// expect(auction.endTime).to.bignumber.equal(expectedEndTime); +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); + +// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct startPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// set1, +// set2, +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeStart = await auctionMock.rangeStart.callAsync(); + +// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); +// const expectedStartPrice = fairValue.sub(negativeRange); +// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); +// }); + +// it('sets the correct endPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// set1, +// set2, +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeEnd = await auctionMock.rangeEnd.callAsync(); +// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); +// const expectedEndPrice = fairValue.add(positiveRange); +// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); +// }); + +// describe('when currentSet is greater than 10x the nextSet', async () => { +// beforeEach(async () => { +// const setComponents = [component1.address, component2.address]; +// const setUnits = [gWei(1), gWei(1)]; +// const setNaturalUnit = gWei(101); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// setComponents, +// setUnits, +// setNaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); + +// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct startPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeStart = await auctionMock.rangeStart.callAsync(); +// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); +// const expectedStartPrice = fairValue.sub(negativeRange); + +// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); +// }); + +// it('sets the correct endPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeEnd = await auctionMock.rangeEnd.callAsync(); +// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); +// const expectedEndPrice = fairValue.add(positiveRange); + +// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); +// }); +// }); +// }); + +// describe('[CONTEXT] Initialized auction', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeLinearAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// describe('#getPrice', async () => { +// async function subject(): Promise { +// return auctionMock.getPrice.callAsync(); +// } + +// it('returns the correct result', async () => { +// const result = await subject(); +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(result).to.bignumber.equal(currentPrice); +// }); + +// describe('when the auction has elapsed half the period', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('returns the correct price', async () => { +// const result = await subject(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(result).to.bignumber.equal(currentPrice); +// }); +// }); + +// describe('when the timestamp has exceeded the endTime', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(100)); + +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('returns the correct price / endPrice', async () => { +// const result = await subject(); + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// expect(result).to.bignumber.equal(linearAuction.endPrice); +// }); +// }); +// }); + +// describe('#getPrice', async () => { +// async function subject(): Promise { +// return auctionMock.getPrice.callAsync(); +// } + +// it('returns the correct numerator', async () => { +// const numerator = await subject(); +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(numerator).to.bignumber.equal(currentPrice); +// }); +// }); + +// describe('#getTokenFlow', async () => { +// let subjectQuantity: BigNumber; + +// let tokenFlows: TokenFlow; + +// beforeEach(async () => { +// subjectQuantity = startingCurrentSetQuantity; + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const { timestamp } = await web3.eth.getBlock('latest'); + +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); + +// tokenFlows = liquidatorHelper.constructTokenFlow( +// linearAuction, +// linearAuction.auction.pricePrecision, +// subjectQuantity, +// currentPrice, +// ); +// }); + +// async function subject(): Promise { +// return auctionMock.getTokenFlow.callAsync(subjectQuantity); +// } + +// it('returns the token array', async () => { +// const { addresses } = await subject(); +// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); +// }); + +// it('returns the correct inflow', async () => { +// const { inflow } = await subject(); +// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); +// }); + +// it('returns the correct outflow', async () => { +// const { outflow } = await subject(); +// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); +// }); + +// describe('when the auction has elapsed half the period', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectQuantity = startingCurrentSetQuantity.div(2); + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const { timestamp } = await web3.eth.getBlock('latest'); + +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); + +// tokenFlows = liquidatorHelper.constructTokenFlow( +// linearAuction, +// linearAuction.auction.pricePrecision, +// subjectQuantity, +// currentPrice, +// ); +// }); + +// it('returns the token array', async () => { +// const { addresses } = await subject(); +// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); +// }); + +// it('returns the correct inflow', async () => { +// const { inflow } = await subject(); +// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); +// }); + +// it('returns the correct outflow', async () => { +// const { outflow } = await subject(); +// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); +// }); +// }); +// }); + +// describe('#hasAuctionFailed', async () => { +// async function subject(): Promise { +// return auctionMock.hasAuctionFailed.callAsync(); +// } + +// it('returns false', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); + +// describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return true', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); +// }); + +// describe('when the auction has been completed', async () => { +// beforeEach(async () => { +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return false', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); +// }); + +// describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + +// await blockchain.mineBlockAsync(); +// }); + +// it('should return true', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.true; +// }); +// }); +// }); +// }); +// }); \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index a73dcf8d3..68ed00873 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -31,6 +31,7 @@ import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; +const Core = artifacts.require('Core'); const TwoAssetPriceBoundedLinearAuction = artifacts.require('TwoAssetPriceBoundedLinearAuction'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { @@ -68,6 +69,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { + ABIDecoder.addABI(Core.abi); ABIDecoder.addABI(TwoAssetPriceBoundedLinearAuction.abi); transferProxy = await coreHelper.deployTransferProxyAsync(); @@ -107,9 +109,32 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { + ABIDecoder.removeABI(Core.abi); ABIDecoder.removeABI(TwoAssetPriceBoundedLinearAuction.abi); }); + describe('#constructor', async () => { + it('sets the correct auctionPeriod', async () => { + const result = await boundsCalculator.auctionPeriod.callAsync(); + expect(result).to.bignumber.equal(auctionPeriod); + }); + + it('sets the correct rangeStart', async () => { + const result = await boundsCalculator.rangeStart.callAsync(); + expect(result).to.bignumber.equal(rangeStart); + }); + + it('sets the correct rangeEnd', async () => { + const result = await boundsCalculator.rangeEnd.callAsync(); + expect(result).to.bignumber.equal(rangeEnd); + }); + + it('sets the correct oracleWhiteList', async () => { + const result = await boundsCalculator.oracleWhiteList.callAsync(); + expect(result).to.equal(oracleWhiteList.address); + }); + }); + describe('#validateTwoAssetPriceBoundedAuction', async () => { let set1: SetTokenContract; let set2: SetTokenContract; @@ -202,6 +227,23 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { await expectRevertError(subject()); }); }); + + describe('when a passed token does not have matching oracle', async () => { + before(async () => { + const nonOracleComponent = await erc20Helper.deployTokenAsync(deployerAccount, 6); + + customComponents2 = [wrappedETH.address, nonOracleComponent.address]; + }); + + after(async () => { + customComponents1 = undefined; + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('#calculateStartPrice and calculateEndPrice', async () => { @@ -224,7 +266,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { - pricePrecision: new BigNumber(0), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), @@ -296,7 +337,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { - pricePrecision: new BigNumber(0), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 4213ac4bd..da27979ba 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,9 +249,8 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -266,20 +265,6 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.endTime).to.bignumber.equal(expectedEndTime); }); - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await liquidator.auctions.callAsync(subjectCaller); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - set1, - set2, - oracleWhiteList, - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - it('sets the correct startPrice', async () => { await subject(); @@ -326,19 +311,6 @@ contract('LinearAuctionLiquidator', accounts => { subjectNextSet = set3.address; }); - it('sets the correct pricePrecision', async () => { - await subject(); - const auction: any = await liquidator.auctions.callAsync(subjectCaller); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - it('sets the correct startPrice', async () => { await subject(); @@ -467,7 +439,6 @@ contract('LinearAuctionLiquidator', accounts => { tokenFlows = liquidatorHelper.constructTokenFlow( linearAuction, - linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, ); @@ -507,12 +478,8 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); - const pricePrecision = auction.auction.pricePrecision; - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision) - .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); @@ -571,7 +538,6 @@ contract('LinearAuctionLiquidator', accounts => { tokenFlows = liquidatorHelper.constructTokenFlow( linearAuction, - linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, ); diff --git a/utils/auction.ts b/utils/auction.ts index 319e9e0dd..4f3cc92f0 100644 --- a/utils/auction.ts +++ b/utils/auction.ts @@ -23,7 +23,6 @@ export interface TokenFlow { } export interface Auction { - pricePrecision: BigNumber; minimumBid: BigNumber; startTime: BigNumber; startingCurrentSets: BigNumber; @@ -35,7 +34,6 @@ export interface Auction { export function getLinearAuction(input: any): LinearAuction { const { - pricePrecision, minimumBid, startTime, startingCurrentSets, @@ -46,7 +44,6 @@ export function getLinearAuction(input: any): LinearAuction { return { auction: { - pricePrecision: new BigNumber(pricePrecision), minimumBid: new BigNumber(minimumBid), startTime: new BigNumber(startTime), startingCurrentSets: new BigNumber(startingCurrentSets), diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index ce0032e35..7fbd1b868 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -353,7 +353,6 @@ export class LiquidatorHelper { currentSetToken: SetTokenContract, nextSetToken: SetTokenContract, oracleWhiteList: OracleWhiteListContract, - pricePrecision: BigNumber, from: Address = this._contractOwnerAddress, ): Promise { const currentSetUSDValue = await this.calculateSetTokenValueAsync(currentSetToken, oracleWhiteList); @@ -437,7 +436,6 @@ export class LiquidatorHelper { public constructTokenFlow( linearAuction: LinearAuction, - pricePrecision: BigNumber, quantity: BigNumber, priceScaled: BigNumber, ): TokenFlow { @@ -452,7 +450,7 @@ export class LiquidatorHelper { minimumBid, } = linearAuction.auction; - const unitsMultiplier = quantity.div(minimumBid).round(0, 3).mul(pricePrecision); + const unitsMultiplier = quantity.div(minimumBid).round(0, 3); for (let i = 0; i < combinedCurrentSetUnits.length; i++) { const flow = combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)); From 743aaa4109a54fbd38b812050631d6d1102f20d9 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Tue, 7 Jan 2020 13:07:50 -0800 Subject: [PATCH 15/35] [Liquidator] Add auction and linearAuction tests (#562) * Fix * uncomment * Push * Fix test --- .../liquidators/impl/LinearAuctionMock.sol | 4 +- .../core/liquidators/impl/auction.spec.ts | 961 ++++++-------- .../liquidators/impl/linearAuction.spec.ts | 1136 ++++++++--------- .../twoAssetPriceBoundedLinearAuction.spec.ts | 4 +- .../linearAuctionLiquidator.spec.ts | 2 - utils/helpers/liquidatorHelper.ts | 24 +- 6 files changed, 974 insertions(+), 1157 deletions(-) diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 6b55a0741..a506d99c0 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -32,7 +32,7 @@ contract LinearAuctionMock is LinearAuction { } function calculateStartPrice( - State storage _linearAuction, + Auction.Setup storage _auction, ISetToken _currentSet, ISetToken _nextSet ) @@ -46,7 +46,7 @@ contract LinearAuctionMock is LinearAuction { } function calculateEndPrice( - State storage _linearAuction, + Auction.Setup storage _auction, ISetToken _currentSet, ISetToken _nextSet ) diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index 6b5ccb9dc..cadca2ccf 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -1,549 +1,424 @@ -// require('module-alias/register'); - -// import * as ABIDecoder from 'abi-decoder'; -// import * as _ from 'lodash'; -// import * as chai from 'chai'; -// import { BigNumber } from 'bignumber.js'; -// import { Address } from 'set-protocol-utils'; - -// import ChaiSetup from '@utils/chaiSetup'; -// import { BigNumberSetup } from '@utils/bigNumberSetup'; -// import { -// CoreContract, -// SetTokenContract, -// SetTokenFactoryContract, -// StandardTokenMockContract, -// AuctionMockContract, -// OracleWhiteListContract, -// TransferProxyContract, -// UpdatableOracleMockContract, -// VaultContract, -// } from '@utils/contracts'; -// import { expectRevertError } from '@utils/tokenAssertions'; -// import { Blockchain } from '@utils/blockchain'; -// import { getWeb3 } from '@utils/web3Helper'; -// import { -// DEFAULT_GAS, -// } from '@utils/constants'; -// import { ether, gWei } from '@utils/units'; - -// import { CoreHelper } from '@utils/helpers/coreHelper'; -// import { ERC20Helper } from '@utils/helpers/erc20Helper'; -// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -// BigNumberSetup.configure(); -// ChaiSetup.configure(); -// const web3 = getWeb3(); -// const { expect } = chai; -// const blockchain = new Blockchain(web3); -// const Core = artifacts.require('Core'); -// const AuctionMock = artifacts.require('AuctionMock'); - -// contract('Auction', accounts => { -// const [ -// ownerAccount, -// functionCaller, -// ] = accounts; - -// let core: CoreContract; -// let transferProxy: TransferProxyContract; -// let vault: VaultContract; -// let setTokenFactory: SetTokenFactoryContract; -// let auctionMock: AuctionMockContract; -// let oracleWhiteList: OracleWhiteListContract; - -// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); -// const erc20Helper = new ERC20Helper(ownerAccount); -// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); -// const libraryMockHelper = new LibraryMockHelper(ownerAccount); - -// let component1: StandardTokenMockContract; -// let component2: StandardTokenMockContract; -// let component3: StandardTokenMockContract; - -// let component1Price: BigNumber; -// let component2Price: BigNumber; -// let component3Price: BigNumber; - -// let component1Oracle: UpdatableOracleMockContract; -// let component2Oracle: UpdatableOracleMockContract; -// let component3Oracle: UpdatableOracleMockContract; - -// let set1: SetTokenContract; -// let set2: SetTokenContract; - -// let set1Components: Address[]; -// let set2Components: Address[]; - -// let set1Units: BigNumber[]; -// let set2Units: BigNumber[]; - -// let set1NaturalUnit: BigNumber; -// let set2NaturalUnit: BigNumber; - -// before(async () => { -// ABIDecoder.addABI(Core.abi); -// ABIDecoder.addABI(AuctionMock.abi); - -// transferProxy = await coreHelper.deployTransferProxyAsync(); -// vault = await coreHelper.deployVaultAsync(); -// core = await coreHelper.deployCoreAsync(transferProxy, vault); - -// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - -// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - -// component1 = await erc20Helper.deployTokenAsync(ownerAccount); -// component2 = await erc20Helper.deployTokenAsync(ownerAccount); -// component3 = await erc20Helper.deployTokenAsync(ownerAccount); - -// component1Price = ether(1); -// component2Price = ether(2); -// component3Price = ether(1); - -// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); -// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); -// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - -// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( -// [component1.address, component2.address, component3.address], -// [component1Oracle.address, component2Oracle.address, component3Oracle.address], -// ); - -// set1Components = [component1.address, component2.address]; -// set1Units = [gWei(1), gWei(1)]; -// set1NaturalUnit = gWei(2); -// set1 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set1Components, -// set1Units, -// set1NaturalUnit, -// ); - -// set2Components = [component2.address, component3.address]; -// set2Units = [gWei(1), gWei(1)]; -// set2NaturalUnit = gWei(1); -// set2 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set2Components, -// set2Units, -// set2NaturalUnit, -// ); -// }); - -// after(async () => { -// ABIDecoder.removeABI(Core.abi); -// ABIDecoder.removeABI(AuctionMock.abi); -// }); - -// beforeEach(async () => { -// await blockchain.saveSnapshotAsync(); -// }); - -// afterEach(async () => { -// await blockchain.revertAsync(); -// }); - -// describe('#constructor', async () => { -// let subjectWhiteList: Address; - -// beforeEach(async () => { -// subjectWhiteList = oracleWhiteList.address; -// }); +require('module-alias/register'); + +import * as ABIDecoder from 'abi-decoder'; +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + CoreContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, + AuctionMockContract, + TransferProxyContract, + VaultContract, +} from '@utils/contracts'; +import { expectRevertError } from '@utils/tokenAssertions'; +import { Blockchain } from '@utils/blockchain'; +import { getWeb3 } from '@utils/web3Helper'; +import { + DEFAULT_GAS, +} from '@utils/constants'; +import { ether, gWei } from '@utils/units'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const web3 = getWeb3(); +const { expect } = chai; +const blockchain = new Blockchain(web3); +const Core = artifacts.require('Core'); +const AuctionMock = artifacts.require('AuctionMock'); + +contract('Auction', accounts => { + const [ + ownerAccount, + functionCaller, + ] = accounts; + + let core: CoreContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let auctionMock: AuctionMockContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + + let component1: StandardTokenMockContract; + let component2: StandardTokenMockContract; + let component3: StandardTokenMockContract; + + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + before(async () => { + ABIDecoder.addABI(Core.abi); + ABIDecoder.addABI(AuctionMock.abi); + + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + core = await coreHelper.deployCoreAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + + await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + + component1 = await erc20Helper.deployTokenAsync(ownerAccount); + component2 = await erc20Helper.deployTokenAsync(ownerAccount); + component3 = await erc20Helper.deployTokenAsync(ownerAccount); + + set1Components = [component1.address, component2.address]; + set1Units = [gWei(1), gWei(1)]; + set1NaturalUnit = gWei(2); + set1 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = [component2.address, component3.address]; + set2Units = [gWei(1), gWei(1)]; + set2NaturalUnit = gWei(1); + set2 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + }); + + after(async () => { + ABIDecoder.removeABI(Core.abi); + ABIDecoder.removeABI(AuctionMock.abi); + }); + + beforeEach(async () => { + await blockchain.saveSnapshotAsync(); + }); + + afterEach(async () => { + await blockchain.revertAsync(); + }); + + describe('#initializeAuction', async () => { + let subjectCaller: Address; + let subjectCurrentSet: Address; + let subjectNextSet: Address; + let subjectStartingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + subjectStartingCurrentSetQuantity = ether(10); + }); + + after(async () => { + }); + + async function subject(): Promise { + return auctionMock.initializeAuction.sendTransactionAsync( + subjectCurrentSet, + subjectNextSet, + subjectStartingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } -// async function subject(): Promise { -// return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); -// } + it('sets the correct minimumBid', async () => { + await subject(); -// it('sets the correct oracleWhiteList', async () => { -// auctionMock = await subject(); + const auctionSetup: any = await auctionMock.auction.callAsync(); -// const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); -// expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); -// }); -// }); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); + }); -// describe('#initializeAuction', async () => { -// let subjectCaller: Address; -// let subjectCurrentSet: Address; -// let subjectNextSet: Address; -// let subjectStartingCurrentSetQuantity: BigNumber; + it('sets the correct startTime', async () => { + await subject(); -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const { timestamp } = await web3.eth.getBlock('latest'); + expect(auctionSetup.startTime).to.bignumber.equal(timestamp); + }); -// subjectCaller = functionCaller; -// subjectCurrentSet = set1.address; -// subjectNextSet = set2.address; -// subjectStartingCurrentSetQuantity = ether(10); -// }); - -// after(async () => { -// }); - -// async function subject(): Promise { -// return auctionMock.initializeAuction.sendTransactionAsync( -// subjectCurrentSet, -// subjectNextSet, -// subjectStartingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// set1, -// set2, -// oracleWhiteList -// ); - -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct minimumBid', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const pricePrecision = auctionSetup.pricePrecision; -// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); -// expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); -// }); - -// it('sets the correct startTime', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// expect(auctionSetup.startTime).to.bignumber.equal(timestamp); -// }); - -// it('sets the correct startingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); -// }); - -// it('sets the correct remainingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); -// }); - -// it('sets the correct combinedTokenArray', async () => { -// await subject(); - -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const expectedResult = _.union(set1Components, set2Components); - -// expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); -// }); - -// it('sets the correct combinedCurrentSetUnits', async () => { -// await subject(); - -// const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); - -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( -// set1, -// combinedTokenArray, -// new BigNumber(auctionSetup.minimumBid), -// auctionSetup.pricePrecision -// ); - -// expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); -// }); - -// it('sets the correct combinedNextSetUnits', async () => { -// await subject(); - -// const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( -// set2, -// combinedTokenArray, -// new BigNumber(auctionSetup.minimumBid), -// auctionSetup.pricePrecision -// ); - -// expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); -// }); - -// describe('when currentSet is greater than 10x the nextSet', async () => { -// beforeEach(async () => { -// const setComponents = [component1.address, component2.address]; -// const setUnits = [gWei(1), gWei(1)]; -// const setNaturalUnit = gWei(300); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// setComponents, -// setUnits, -// setNaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); -// }); - -// describe('when currentSet is 1-2x greater than nextSet', async () => { -// beforeEach(async () => { -// const set3Components = [component1.address, component3.address]; -// const set3Units = [gWei(1), gWei(1)]; -// const set3NaturalUnit = gWei(2); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set3Components, -// set3Units, -// set3NaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); -// }); - -// describe('when there is insufficient collateral to rebalance', async () => { -// beforeEach(async () => { -// subjectStartingCurrentSetQuantity = gWei(10); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#reduceRemainingCurrentSets', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// let subjectReductionQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectReductionQuantity = ether(5); -// }); - -// async function subject(): Promise { -// return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// subjectReductionQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('calculates the correct new remainingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); -// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); -// }); -// }); - -// describe('#validateBidQuantity', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// let subjectQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectQuantity = ether(5); -// }); - -// async function subject(): Promise { -// return auctionMock.validateBidQuantity.sendTransactionAsync( -// subjectQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('does not revert', async () => { -// await subject(); -// }); - -// describe('when the quantity is not a multiple of the minimumBid', async () => { -// beforeEach(async () => { -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) -// .mul(auctionSetup.pricePrecision) -// .div(2); -// subjectQuantity = gWei(10).plus(halfMinimumBid); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); - -// describe('when the quantity is more than the remainingCurrentsets', async () => { -// beforeEach(async () => { -// subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#validateAuctionCompletion', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; -// let reductionQuantity: BigNumber; -// let customReductionQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// reductionQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// async function subject(): Promise { -// return auctionMock.validateAuctionCompletion.sendTransactionAsync( -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('should not revert', async () => { -// await subject(); -// }); - -// describe('when the auction is not complete', async () => { -// before(async () => { -// customReductionQuantity = startingCurrentSetQuantity.div(2); -// }); - -// after(async () => { -// customReductionQuantity = undefined; -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#isAuctionActive', async () => { -// let subjectCaller: Address; -// let startingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// }); - -// async function subject(): Promise { -// return auctionMock.isAuctionActive.callAsync( -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('should return false', async () => { -// const result = await subject(); -// expect(result).to.equal(false); -// }); - -// describe('when the auction has begun', async () => { -// beforeEach(async () => { -// startingCurrentSetQuantity = ether(10); - -// await blockchain.increaseTimeAsync(new BigNumber(1000)); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return true', async () => { -// const result = await subject(); -// expect(result).to.equal(true); -// }); -// }); -// }); -// }); \ No newline at end of file + it('sets the correct startingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); + }); + + it('sets the correct remainingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); + }); + + it('sets the correct combinedTokenArray', async () => { + await subject(); + + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const expectedResult = _.union(set1Components, set2Components); + + expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); + }); + + it('sets the correct combinedCurrentSetUnits', async () => { + await subject(); + + const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); + + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( + set1, + combinedTokenArray, + new BigNumber(auctionSetup.minimumBid), + ); + + expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); + }); + + it('sets the correct combinedNextSetUnits', async () => { + await subject(); + + const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( + set2, + combinedTokenArray, + new BigNumber(auctionSetup.minimumBid), + ); + + expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); + }); + + describe('when there is insufficient collateral to rebalance', async () => { + beforeEach(async () => { + subjectStartingCurrentSetQuantity = gWei(0.5); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#reduceRemainingCurrentSets', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + let subjectReductionQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectReductionQuantity = ether(5); + }); + + async function subject(): Promise { + return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + subjectReductionQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('calculates the correct new remainingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); + expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); + }); + }); + + describe('#validateBidQuantity', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + let subjectQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectQuantity = ether(5); + }); + + async function subject(): Promise { + return auctionMock.validateBidQuantity.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('does not revert', async () => { + await subject(); + }); + + describe('when the quantity is not a multiple of the minimumBid', async () => { + beforeEach(async () => { + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + subjectQuantity = gWei(10).plus(halfMinimumBid); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('when the quantity is more than the remainingCurrentsets', async () => { + beforeEach(async () => { + subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#validateAuctionCompletion', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + let reductionQuantity: BigNumber; + let customReductionQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + reductionQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + async function subject(): Promise { + return auctionMock.validateAuctionCompletion.sendTransactionAsync( + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('should not revert', async () => { + await subject(); + }); + + describe('when the auction is not complete', async () => { + before(async () => { + customReductionQuantity = startingCurrentSetQuantity.div(2); + }); + + after(async () => { + customReductionQuantity = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#isAuctionActive', async () => { + let subjectCaller: Address; + let startingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + }); + + async function subject(): Promise { + return auctionMock.isAuctionActive.callAsync( + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('should return false', async () => { + const result = await subject(); + expect(result).to.equal(false); + }); + + describe('when the auction has begun', async () => { + beforeEach(async () => { + startingCurrentSetQuantity = ether(10); + + await blockchain.increaseTimeAsync(new BigNumber(1000)); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return true', async () => { + const result = await subject(); + expect(result).to.equal(true); + }); + }); + }); +}); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 03cfa8f85..075795d30 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -1,586 +1,550 @@ -// require('module-alias/register'); - -// import * as ABIDecoder from 'abi-decoder'; -// import * as _ from 'lodash'; -// import * as chai from 'chai'; -// import { BigNumber } from 'bignumber.js'; -// import { Address } from 'set-protocol-utils'; - -// import ChaiSetup from '@utils/chaiSetup'; -// import { BigNumberSetup } from '@utils/bigNumberSetup'; -// import { -// CoreContract, -// OracleWhiteListContract, -// SetTokenContract, -// SetTokenFactoryContract, -// StandardTokenMockContract, -// LinearAuctionMockContract, -// TransferProxyContract, -// UpdatableOracleMockContract, -// VaultContract, -// } from '@utils/contracts'; -// import { Blockchain } from '@utils/blockchain'; -// import { getWeb3 } from '@utils/web3Helper'; -// import { -// DEFAULT_GAS, -// ONE_DAY_IN_SECONDS, -// } from '@utils/constants'; -// import { ether, gWei } from '@utils/units'; -// import { getLinearAuction, TokenFlow } from '@utils/auction'; - -// import { CoreHelper } from '@utils/helpers/coreHelper'; -// import { ERC20Helper } from '@utils/helpers/erc20Helper'; -// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -// BigNumberSetup.configure(); -// ChaiSetup.configure(); -// const web3 = getWeb3(); -// const { expect } = chai; -// const blockchain = new Blockchain(web3); -// const Core = artifacts.require('Core'); -// const LinearAuctionMock = artifacts.require('LinearAuctionMock'); - -// contract('LinearAuction', accounts => { -// const [ -// ownerAccount, -// functionCaller, -// ] = accounts; - -// let core: CoreContract; -// let transferProxy: TransferProxyContract; -// let vault: VaultContract; -// let setTokenFactory: SetTokenFactoryContract; -// let auctionMock: LinearAuctionMockContract; - -// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); -// const erc20Helper = new ERC20Helper(ownerAccount); -// const libraryMockHelper = new LibraryMockHelper(ownerAccount); -// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - -// let auctionPeriod: BigNumber; -// let rangeStart: BigNumber; -// let rangeEnd: BigNumber; -// let oracleWhiteList: OracleWhiteListContract; - -// let component1: StandardTokenMockContract; -// let component2: StandardTokenMockContract; -// let component3: StandardTokenMockContract; - -// let component1Price: BigNumber; -// let component2Price: BigNumber; -// let component3Price: BigNumber; - -// let set1: SetTokenContract; -// let set2: SetTokenContract; - -// let set1Components: Address[]; -// let set2Components: Address[]; - -// let set1Units: BigNumber[]; -// let set2Units: BigNumber[]; - -// let set1NaturalUnit: BigNumber; -// let set2NaturalUnit: BigNumber; - -// let component1Oracle: UpdatableOracleMockContract; -// let component2Oracle: UpdatableOracleMockContract; -// let component3Oracle: UpdatableOracleMockContract; - -// before(async () => { -// ABIDecoder.addABI(Core.abi); -// ABIDecoder.addABI(LinearAuctionMock.abi); - -// transferProxy = await coreHelper.deployTransferProxyAsync(); -// vault = await coreHelper.deployVaultAsync(); -// core = await coreHelper.deployCoreAsync(transferProxy, vault); - -// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); -// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - -// component1 = await erc20Helper.deployTokenAsync(ownerAccount); -// component2 = await erc20Helper.deployTokenAsync(ownerAccount); -// component3 = await erc20Helper.deployTokenAsync(ownerAccount); - -// set1Components = [component1.address, component2.address]; -// set1Units = [gWei(1), gWei(1)]; -// set1NaturalUnit = gWei(1); -// set1 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set1Components, -// set1Units, -// set1NaturalUnit, -// ); - -// set2Components = [component2.address, component3.address]; -// set2Units = [gWei(1), gWei(1)]; -// set2NaturalUnit = gWei(2); -// set2 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set2Components, -// set2Units, -// set2NaturalUnit, -// ); - -// component1Price = ether(1); -// component2Price = ether(2); -// component3Price = ether(1); - -// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); -// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); -// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - -// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( -// [component1.address, component2.address, component3.address], -// [component1Oracle.address, component2Oracle.address, component3Oracle.address], -// ); - -// auctionPeriod = ONE_DAY_IN_SECONDS; -// rangeStart = new BigNumber(10); // 10% above fair value -// rangeEnd = new BigNumber(10); // 10% below fair value - -// auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( -// oracleWhiteList.address, -// auctionPeriod, -// rangeStart, -// rangeEnd, -// ); - -// }); - -// after(async () => { -// ABIDecoder.removeABI(Core.abi); -// ABIDecoder.removeABI(LinearAuctionMock.abi); -// }); - -// beforeEach(async () => { -// await blockchain.saveSnapshotAsync(); -// }); - -// afterEach(async () => { -// await blockchain.revertAsync(); -// }); - -// describe('#constructor', async () => { -// it('sets the correct auctionPeriod', async () => { -// const result = await auctionMock.auctionPeriod.callAsync(); -// expect(result).to.bignumber.equal(auctionPeriod); -// }); - -// it('sets the correct rangeStart', async () => { -// const result = await auctionMock.rangeStart.callAsync(); -// expect(result).to.bignumber.equal(rangeStart); -// }); - -// it('sets the correct rangeEnd', async () => { -// const result = await auctionMock.rangeEnd.callAsync(); -// expect(result).to.bignumber.equal(rangeEnd); -// }); -// }); - -// describe('#initializeLinearAuction', async () => { -// let subjectCaller: Address; -// let subjectCurrentSet: Address; -// let subjectNextSet: Address; -// let subjectStartingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// subjectCaller = functionCaller; -// subjectCurrentSet = set1.address; -// subjectNextSet = set2.address; -// subjectStartingCurrentSetQuantity = ether(10); -// }); - -// after(async () => { -// }); - -// async function subject(): Promise { -// return auctionMock.initializeLinearAuction.sendTransactionAsync( -// subjectCurrentSet, -// subjectNextSet, -// subjectStartingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('sets the correct minimumBid', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const pricePrecision = auction.auction.pricePrecision; -// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) -// .mul(pricePrecision); -// expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); -// }); - -// it('sets the correct endTime', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); -// const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); -// expect(auction.endTime).to.bignumber.equal(expectedEndTime); -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); - -// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct startPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// set1, -// set2, -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeStart = await auctionMock.rangeStart.callAsync(); - -// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); -// const expectedStartPrice = fairValue.sub(negativeRange); -// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); -// }); - -// it('sets the correct endPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// set1, -// set2, -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeEnd = await auctionMock.rangeEnd.callAsync(); -// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); -// const expectedEndPrice = fairValue.add(positiveRange); -// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); -// }); - -// describe('when currentSet is greater than 10x the nextSet', async () => { -// beforeEach(async () => { -// const setComponents = [component1.address, component2.address]; -// const setUnits = [gWei(1), gWei(1)]; -// const setNaturalUnit = gWei(101); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// setComponents, -// setUnits, -// setNaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); - -// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct startPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeStart = await auctionMock.rangeStart.callAsync(); -// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); -// const expectedStartPrice = fairValue.sub(negativeRange); - -// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); -// }); - -// it('sets the correct endPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeEnd = await auctionMock.rangeEnd.callAsync(); -// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); -// const expectedEndPrice = fairValue.add(positiveRange); - -// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); -// }); -// }); -// }); - -// describe('[CONTEXT] Initialized auction', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeLinearAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// describe('#getPrice', async () => { -// async function subject(): Promise { -// return auctionMock.getPrice.callAsync(); -// } - -// it('returns the correct result', async () => { -// const result = await subject(); -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(result).to.bignumber.equal(currentPrice); -// }); - -// describe('when the auction has elapsed half the period', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('returns the correct price', async () => { -// const result = await subject(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(result).to.bignumber.equal(currentPrice); -// }); -// }); - -// describe('when the timestamp has exceeded the endTime', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(100)); - -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('returns the correct price / endPrice', async () => { -// const result = await subject(); - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// expect(result).to.bignumber.equal(linearAuction.endPrice); -// }); -// }); -// }); - -// describe('#getPrice', async () => { -// async function subject(): Promise { -// return auctionMock.getPrice.callAsync(); -// } - -// it('returns the correct numerator', async () => { -// const numerator = await subject(); -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(numerator).to.bignumber.equal(currentPrice); -// }); -// }); - -// describe('#getTokenFlow', async () => { -// let subjectQuantity: BigNumber; - -// let tokenFlows: TokenFlow; - -// beforeEach(async () => { -// subjectQuantity = startingCurrentSetQuantity; - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const { timestamp } = await web3.eth.getBlock('latest'); - -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); - -// tokenFlows = liquidatorHelper.constructTokenFlow( -// linearAuction, -// linearAuction.auction.pricePrecision, -// subjectQuantity, -// currentPrice, -// ); -// }); - -// async function subject(): Promise { -// return auctionMock.getTokenFlow.callAsync(subjectQuantity); -// } - -// it('returns the token array', async () => { -// const { addresses } = await subject(); -// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); -// }); - -// it('returns the correct inflow', async () => { -// const { inflow } = await subject(); -// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); -// }); - -// it('returns the correct outflow', async () => { -// const { outflow } = await subject(); -// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); -// }); - -// describe('when the auction has elapsed half the period', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectQuantity = startingCurrentSetQuantity.div(2); - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const { timestamp } = await web3.eth.getBlock('latest'); - -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); - -// tokenFlows = liquidatorHelper.constructTokenFlow( -// linearAuction, -// linearAuction.auction.pricePrecision, -// subjectQuantity, -// currentPrice, -// ); -// }); - -// it('returns the token array', async () => { -// const { addresses } = await subject(); -// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); -// }); - -// it('returns the correct inflow', async () => { -// const { inflow } = await subject(); -// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); -// }); - -// it('returns the correct outflow', async () => { -// const { outflow } = await subject(); -// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); -// }); -// }); -// }); - -// describe('#hasAuctionFailed', async () => { -// async function subject(): Promise { -// return auctionMock.hasAuctionFailed.callAsync(); -// } - -// it('returns false', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); - -// describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return true', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); -// }); - -// describe('when the auction has been completed', async () => { -// beforeEach(async () => { -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return false', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); -// }); - -// describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - -// await blockchain.mineBlockAsync(); -// }); - -// it('should return true', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.true; -// }); -// }); -// }); -// }); -// }); \ No newline at end of file +require('module-alias/register'); + +import * as ABIDecoder from 'abi-decoder'; +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + CoreContract, + OracleWhiteListContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, + LinearAuctionMockContract, + TransferProxyContract, + UpdatableOracleMockContract, + VaultContract, +} from '@utils/contracts'; +import { Blockchain } from '@utils/blockchain'; +import { getWeb3 } from '@utils/web3Helper'; +import { + DEFAULT_GAS, + ONE_DAY_IN_SECONDS, +} from '@utils/constants'; +import { ether, gWei } from '@utils/units'; +import { getLinearAuction, TokenFlow } from '@utils/auction'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const web3 = getWeb3(); +const { expect } = chai; +const blockchain = new Blockchain(web3); +const Core = artifacts.require('Core'); +const LinearAuctionMock = artifacts.require('LinearAuctionMock'); + +contract('LinearAuction', accounts => { + const [ + ownerAccount, + functionCaller, + ] = accounts; + + let core: CoreContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let auctionMock: LinearAuctionMockContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const libraryMockHelper = new LibraryMockHelper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + + let auctionPeriod: BigNumber; + let rangeStart: BigNumber; + let rangeEnd: BigNumber; + let oracleWhiteList: OracleWhiteListContract; + + let component1: StandardTokenMockContract; + let component2: StandardTokenMockContract; + let component3: StandardTokenMockContract; + + let component1Price: BigNumber; + let component2Price: BigNumber; + let component3Price: BigNumber; + + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + let component1Oracle: UpdatableOracleMockContract; + let component2Oracle: UpdatableOracleMockContract; + let component3Oracle: UpdatableOracleMockContract; + + before(async () => { + ABIDecoder.addABI(Core.abi); + ABIDecoder.addABI(LinearAuctionMock.abi); + + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + core = await coreHelper.deployCoreAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + + component1 = await erc20Helper.deployTokenAsync(ownerAccount); + component2 = await erc20Helper.deployTokenAsync(ownerAccount); + component3 = await erc20Helper.deployTokenAsync(ownerAccount); + + set1Components = [component1.address, component2.address]; + set1Units = [gWei(1), gWei(1)]; + set1NaturalUnit = gWei(1); + set1 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = [component2.address, component3.address]; + set2Units = [gWei(1), gWei(1)]; + set2NaturalUnit = gWei(2); + set2 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + + component1Price = ether(1); + component2Price = ether(2); + component3Price = ether(1); + + component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); + component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); + component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + + oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( + [component1.address, component2.address, component3.address], + [component1Oracle.address, component2Oracle.address, component3Oracle.address], + ); + + auctionPeriod = ONE_DAY_IN_SECONDS; + rangeStart = new BigNumber(10); // 10% above fair value + rangeEnd = new BigNumber(10); // 10% below fair value + + auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( + oracleWhiteList.address, + auctionPeriod, + rangeStart, + rangeEnd, + ); + + }); + + after(async () => { + ABIDecoder.removeABI(Core.abi); + ABIDecoder.removeABI(LinearAuctionMock.abi); + }); + + beforeEach(async () => { + await blockchain.saveSnapshotAsync(); + }); + + afterEach(async () => { + await blockchain.revertAsync(); + }); + + describe('#constructor', async () => { + it('sets the correct auctionPeriod', async () => { + const result = await auctionMock.auctionPeriod.callAsync(); + expect(result).to.bignumber.equal(auctionPeriod); + }); + + it('sets the correct rangeStart', async () => { + const result = await auctionMock.rangeStart.callAsync(); + expect(result).to.bignumber.equal(rangeStart); + }); + + it('sets the correct rangeEnd', async () => { + const result = await auctionMock.rangeEnd.callAsync(); + expect(result).to.bignumber.equal(rangeEnd); + }); + }); + + describe('#initializeLinearAuction', async () => { + let subjectCaller: Address; + let subjectCurrentSet: Address; + let subjectNextSet: Address; + let subjectStartingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + subjectCaller = functionCaller; + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + subjectStartingCurrentSetQuantity = ether(10); + }); + + after(async () => { + }); + + async function subject(): Promise { + return auctionMock.initializeLinearAuction.sendTransactionAsync( + subjectCurrentSet, + subjectNextSet, + subjectStartingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('sets the correct minimumBid', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); + }); + + it('sets the correct endTime', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); + + const { timestamp } = await web3.eth.getBlock('latest'); + const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); + expect(auction.endTime).to.bignumber.equal(expectedEndTime); + }); + + it('sets the correct startPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + set1, + set2, + oracleWhiteList, + ); + const rangeStart = await auctionMock.rangeStart.callAsync(); + + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); + }); + + it('sets the correct endPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + set1, + set2, + oracleWhiteList, + ); + const rangeEnd = await auctionMock.rangeEnd.callAsync(); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); + }); + + describe('when currentSet is greater than 10x the nextSet', async () => { + beforeEach(async () => { + const setComponents = [component1.address, component2.address]; + const setUnits = [gWei(1), gWei(1)]; + const setNaturalUnit = gWei(101); + const set3 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + setComponents, + setUnits, + setNaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('sets the correct startPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + await coreHelper.getSetInstance(subjectCurrentSet), + await coreHelper.getSetInstance(subjectNextSet), + oracleWhiteList, + ); + const rangeStart = await auctionMock.rangeStart.callAsync(); + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); + + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); + }); + + it('sets the correct endPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + await coreHelper.getSetInstance(subjectCurrentSet), + await coreHelper.getSetInstance(subjectNextSet), + oracleWhiteList, + ); + const rangeEnd = await auctionMock.rangeEnd.callAsync(); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); + + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); + }); + }); + }); + + describe('[CONTEXT] Initialized auction', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeLinearAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + describe('#getPrice', async () => { + async function subject(): Promise { + return auctionMock.getPrice.callAsync(); + } + + it('returns the correct result', async () => { + const result = await subject(); + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(result).to.bignumber.equal(currentPrice); + }); + + describe('when the auction has elapsed half the period', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.div(2)); + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('returns the correct price', async () => { + const result = await subject(); + + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(result).to.bignumber.equal(currentPrice); + }); + }); + + describe('when the timestamp has exceeded the endTime', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(100)); + + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('returns the correct price / endPrice', async () => { + const result = await subject(); + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + expect(result).to.bignumber.equal(linearAuction.endPrice); + }); + }); + }); + + describe('#getPrice', async () => { + async function subject(): Promise { + return auctionMock.getPrice.callAsync(); + } + + it('returns the correct numerator', async () => { + const numerator = await subject(); + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(numerator).to.bignumber.equal(currentPrice); + }); + }); + + describe('#getTokenFlow', async () => { + let subjectQuantity: BigNumber; + + let tokenFlows: TokenFlow; + + beforeEach(async () => { + subjectQuantity = startingCurrentSetQuantity; + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const { timestamp } = await web3.eth.getBlock('latest'); + + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + + tokenFlows = liquidatorHelper.constructTokenFlow( + linearAuction, + subjectQuantity, + currentPrice, + ); + }); + + async function subject(): Promise { + return auctionMock.getTokenFlow.callAsync(subjectQuantity); + } + + it('returns the token array', async () => { + const { addresses } = await subject(); + expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); + }); + + it('returns the correct inflow', async () => { + const { inflow } = await subject(); + expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); + }); + + it('returns the correct outflow', async () => { + const { outflow } = await subject(); + expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); + }); + + describe('when the auction has elapsed half the period', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.div(2)); + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectQuantity = startingCurrentSetQuantity.div(2); + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const { timestamp } = await web3.eth.getBlock('latest'); + + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + + tokenFlows = liquidatorHelper.constructTokenFlow( + linearAuction, + subjectQuantity, + currentPrice, + ); + }); + + it('returns the token array', async () => { + const { addresses } = await subject(); + expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); + }); + + it('returns the correct inflow', async () => { + const { inflow } = await subject(); + expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); + }); + + it('returns the correct outflow', async () => { + const { outflow } = await subject(); + expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); + }); + }); + }); + + describe('#hasAuctionFailed', async () => { + async function subject(): Promise { + return auctionMock.hasAuctionFailed.callAsync(); + } + + it('returns false', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + + describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return true', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + }); + + describe('when the auction has been completed', async () => { + beforeEach(async () => { + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return false', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + }); + + describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + + await blockchain.mineBlockAsync(); + }); + + it('should return true', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.true; + }); + }); + }); + }); +}); diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 68ed00873..878c3da34 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -221,6 +221,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { after(async () => { customComponents1 = undefined; customComponents2 = undefined; + + customUnits1 = undefined; + customUnits2 = undefined; }); it('should revert', async () => { @@ -236,7 +239,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { - customComponents1 = undefined; customComponents2 = undefined; }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index da27979ba..f01f6a86d 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -250,7 +250,6 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); - expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -479,7 +478,6 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); - subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 7fbd1b868..78715cc81 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -154,43 +154,21 @@ export class LiquidatorHelper { return combinedUnits.map(unit => unit.mul(quantity).div(naturalUnit)); } - public async calculatePricePrecisionAsync( - currentSet: SetTokenContract, - nextSet: SetTokenContract, - oracleWhiteList: OracleWhiteListContract - ): Promise { - const currentSetValue = await this.calculateSetTokenValueAsync(currentSet, oracleWhiteList); - const nextSetValue = await this.calculateSetTokenValueAsync(nextSet, oracleWhiteList); - const minimumPricePrecision = new BigNumber(1000); - - if (currentSetValue.greaterThan(nextSetValue)) { - const orderOfMag = this._libraryMockHelper.ceilLog10(currentSetValue.div(nextSetValue)); - - return minimumPricePrecision.mul(10 ** (orderOfMag.toNumber() - 1)); - } - - return minimumPricePrecision; - } - public async constructCombinedUnitArrayAsync( setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, - priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); - // Calculate minimumBidAmount - const maxNaturalUnit = minimumBid.div(priceDivisor); - // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); From cc0edebbf184c0ee67623c5760d07be2e65bbe03 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 13:37:46 -0800 Subject: [PATCH 16/35] Add check to make sure auction bounds aren't the same. --- contracts/core/liquidators/impl/LinearAuction.sol | 6 ++++++ .../impl/twoAssetPriceBoundedLinearAuction.spec.ts | 8 ++++---- .../core/liquidators/linearAuctionLiquidator.spec.ts | 10 ++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index c5567b835..73595dc19 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -84,6 +84,12 @@ contract LinearAuction is Auction { _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); + + require( + _linearAuction.startPrice != _linearAuction.endPrice, + "LinearAuction.initializeLinearAuction: NextSet must have different composition from currentSet." + ); + _linearAuction.endTime = block.timestamp.add(auctionPeriod); } diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 878c3da34..c08a222aa 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -258,7 +258,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { beforeEach(async () => { combinedTokenArray = [wrappedETH.address, usdc.address]; combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; await boundsCalculator.parameterizeAuction.sendTransactionAsync( combinedTokenArray, @@ -272,9 +272,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), remainingCurrentSets: new BigNumber(0), - combinedTokenArray: [wrappedETH.address, usdc.address], - combinedCurrentSetUnits: [new BigNumber(10 ** 12), new BigNumber(128)], - combinedNextSetUnits: [new BigNumber(10 ** 12), new BigNumber(1152)], + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, }, endTime: new BigNumber(0), startPrice: new BigNumber(0), diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index f01f6a86d..9851068d8 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -384,6 +384,16 @@ contract('LinearAuctionLiquidator', accounts => { await expectRevertError(subject()); }); }); + + describe('when currentSet and nextSet have same composition', async () => { + beforeEach(async () => { + subjectNextSet = set1.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('[CONTEXT] Initialized auction', async () => { From 0723a4041d460a94e396c8b6dd7af4bf920314d2 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:14:24 -0800 Subject: [PATCH 17/35] Refactor --- contracts/core/lib/Rebalance.sol | 16 ----- .../liquidators/LinearAuctionLiquidator.sol | 4 +- contracts/core/liquidators/impl/Auction.sol | 36 ++++------ .../core/liquidators/impl/LinearAuction.sol | 41 +++++------ .../liquidators/impl/LinearAuctionMock.sol | 6 +- .../core/liquidators/impl/auction.spec.ts | 15 +--- .../liquidators/impl/linearAuction.spec.ts | 37 ++++------ .../linearAuctionLiquidator.spec.ts | 70 +++++++++---------- utils/auction.ts | 8 +-- utils/helpers/liquidatorHelper.ts | 26 +++---- 10 files changed, 98 insertions(+), 161 deletions(-) diff --git a/contracts/core/lib/Rebalance.sol b/contracts/core/lib/Rebalance.sol index 2d9d7d77b..30acc4d64 100644 --- a/contracts/core/lib/Rebalance.sol +++ b/contracts/core/lib/Rebalance.sol @@ -25,28 +25,12 @@ pragma solidity 0.5.7; */ library Rebalance { - struct Price { - uint256 numerator; - uint256 denominator; - } - struct TokenFlow { address[] addresses; uint256[] inflow; uint256[] outflow; } - function composePrice( - uint256 _numerator, - uint256 _denominator - ) - internal - pure - returns(Price memory) - { - return Price({ numerator: _numerator, denominator: _denominator }); - } - function composeTokenFlow( address[] memory _addresses, uint256[] memory _inflow, diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index 4e45b9190..db9b53b64 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -209,8 +209,8 @@ contract LinearAuctionLiquidator is LinearAuction, ILiquidator { return RebalancingLibrary.AuctionPriceParameters({ auctionStartTime: auction(_set).startTime, auctionTimeToPivot: auctionPeriod, - auctionStartPrice: linearAuction(_set).startNumerator, - auctionPivotPrice: linearAuction(_set).endNumerator + auctionStartPrice: linearAuction(_set).startPrice, + auctionPivotPrice: linearAuction(_set).endPrice }); } diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index adf52c839..9adb55512 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -42,6 +42,7 @@ import { SetUSDValuation } from "../impl/SetUSDValuation.sol"; */ contract Auction { using SafeMath for uint256; + using CommonMath for uint256; using AddressArrayUtils for address[]; /* ============ Structs ============ */ @@ -157,12 +158,12 @@ contract Auction { * * @param _auction Auction Setup object * @param _quantity Amount of currentSets bidder is seeking to rebalance - * @param _price Struct of auction price numerator and denominator + * @param _price Scaled value representing the auction numeartor */ function calculateTokenFlow( Setup storage _auction, uint256 _quantity, - Rebalance.Price memory _price + uint256 _price ) internal view @@ -241,8 +242,7 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit) - .mul(_pricePrecision); + return Math.max(currentSetNaturalUnit, nextNaturalUnit); } /** @@ -271,7 +271,7 @@ contract Auction { * @param _currentUnit Amount of token i in currentSet per minimum bid amount * @param _nextSetUnit Amount of token i in nextSet per minimum bid amount * @param _unitsMultiplier Bid amount normalized to number of minimum bid amounts - * @param _price Struct of auction price numerator and denominator + * @param _priceScaled Auction price as a scaled value * @return inflowUnit Amount of token i transferred into the system * @return outflowUnit Amount of token i transferred to the bidder */ @@ -279,7 +279,7 @@ contract Auction { uint256 _currentUnit, uint256 _nextSetUnit, uint256 _unitsMultiplier, - Rebalance.Price memory _price + uint256 _priceScaled ) internal pure @@ -316,19 +316,19 @@ contract Auction { uint256 outflowUnit; // Use if statement to check if token inflow or outflow - if (_nextSetUnit.mul(_price.denominator) > _currentUnit.mul(_price.numerator)) { + if (_nextSetUnit.scale() > _currentUnit.mul(_priceScaled)) { // Calculate inflow amount inflowUnit = _unitsMultiplier.mul( - _nextSetUnit.mul(_price.denominator).sub(_currentUnit.mul(_price.numerator)) - ).div(_price.numerator); + _nextSetUnit.scale().sub(_currentUnit.mul(_priceScaled)) + ).div(_priceScaled); // Set outflow amount to 0 for component i, since tokens need to be injected in rebalance outflowUnit = 0; } else { // Calculate outflow amount outflowUnit = _unitsMultiplier.mul( - _currentUnit.mul(_price.numerator).sub(_nextSetUnit.mul(_price.denominator)) - ).div(_price.numerator); + _currentUnit.mul(_priceScaled).sub(_nextSetUnit.scale()) + ).div(_priceScaled); // Set inflow amount to 0 for component i, since tokens need to be returned in rebalance inflowUnit = 0; @@ -358,12 +358,10 @@ contract Auction { { address[] memory combinedTokenArray = _auction.combinedTokenArray; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); - uint256 pricePrecisionMem = _auction.pricePrecision; for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, - pricePrecisionMem, combinedTokenArray[i] ); } @@ -376,14 +374,12 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, uint256 _minimumBid, - uint256 _pricePrecision, address _component ) private @@ -401,8 +397,7 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid, - _pricePrecision + _minimumBid ); } @@ -416,21 +411,18 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid, - uint256 _pricePrecision + uint256 _minimumBid ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) - .div(_pricePrecision); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); } /** diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 28d7cf80f..93faa55d4 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -38,8 +38,8 @@ contract LinearAuction is Auction { struct State { Auction.Setup auction; uint256 endTime; - uint256 startNumerator; - uint256 endNumerator; + uint256 startPrice; + uint256 endPrice; } /* ============ State Variables ============ */ @@ -94,9 +94,9 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - uint256 fairValue = calculateFairValue(_currentSet, _nextSet, _linearAuction.auction.pricePrecision); - _linearAuction.startNumerator = calculateStartNumerator(fairValue); - _linearAuction.endNumerator = calculateEndNumerator(fairValue); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + _linearAuction.startPrice = calculateStartPrice(fairValue); + _linearAuction.endPrice = calculateEndPrice(fairValue); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -134,13 +134,6 @@ contract LinearAuction is Auction { ); } - /** - * Returns the linear price based on the current timestamp - */ - function getPrice(State storage _linearAuction) internal view returns (Rebalance.Price memory) { - return Rebalance.composePrice(getNumerator(_linearAuction), _linearAuction.auction.pricePrecision); - } - /** * Auction failed is defined the timestamp breacnhing the auction end time and * the auction not being complete @@ -159,7 +152,7 @@ contract LinearAuction is Auction { * @param _linearAuction Linear Auction State object * @return price uint representing the current price */ - function getNumerator(State storage _linearAuction) internal view returns (uint256) { + function getPrice(State storage _linearAuction) internal view returns (uint256) { uint256 elapsed = block.timestamp.sub(_linearAuction.auction.startTime); // If current time has elapsed @@ -175,11 +168,11 @@ contract LinearAuction is Auction { /** * Calculates the fair value based on the USD values of the next and current Sets. + * Returns a scaled value */ function calculateFairValue( ISetToken _currentSet, - ISetToken _nextSet, - uint256 _pricePrecision + ISetToken _nextSet ) internal view @@ -188,22 +181,22 @@ contract LinearAuction is Auction { uint256 currentSetUSDValue = Auction.calculateUSDValueOfSet(_currentSet); uint256 nextSetUSDValue = Auction.calculateUSDValueOfSet(_nextSet); - return nextSetUSDValue.mul(_pricePrecision).div(currentSetUSDValue); + return nextSetUSDValue.scale().div(currentSetUSDValue); } /** - * Calculates the linear auction start price + * Calculates the linear auction start price with a scaled value */ - function calculateStartNumerator(uint256 _fairValue) internal view returns(uint256) { - uint256 startRange = _fairValue.mul(rangeStart).div(100); - return _fairValue.sub(startRange); + function calculateStartPrice(uint256 _fairValueScaled) internal view returns(uint256) { + uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); + return _fairValueScaled.sub(startRange); } /** - * Calculates the linear auction end price + * Calculates the linear auction end price with a scaled value */ - function calculateEndNumerator(uint256 _fairValue) internal view returns(uint256) { - uint256 endRange = _fairValue.mul(rangeEnd).div(100); - return _fairValue.add(endRange); + function calculateEndPrice(uint256 _fairValueScaled) internal view returns(uint256) { + uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); + return _fairValueScaled.add(endRange); } } \ No newline at end of file diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 7dfebb1e1..99254651e 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -48,7 +48,7 @@ contract LinearAuctionMock is LinearAuction { return super.hasAuctionFailed(auction); } - function getPrice() external view returns(Rebalance.Price memory) { + function getPrice() external view returns(uint256) { return super.getPrice(auction); } @@ -58,10 +58,6 @@ contract LinearAuctionMock is LinearAuction { return super.getTokenFlow(auction, _quantity); } - function getNumerator() external view returns(uint256) { - return super.getNumerator(auction); - } - function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { return super.calculateUSDValueOfSet(_set); } diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index f93709760..b19a025cf 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -212,9 +212,7 @@ contract('Auction', accounts => { const auctionSetup: any = await auctionMock.auction.callAsync(); - const pricePrecision = auctionSetup.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -262,7 +260,6 @@ contract('Auction', accounts => { set1, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision ); expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -279,7 +276,6 @@ contract('Auction', accounts => { set2, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision ); expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -311,7 +307,6 @@ contract('Auction', accounts => { await coreHelper.getSetInstance(subjectNextSet), oracleWhiteList ); - console.log(auctionSetup.pricePrecision); expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); }); @@ -342,14 +337,13 @@ contract('Auction', accounts => { await coreHelper.getSetInstance(subjectNextSet), oracleWhiteList ); - console.log(auctionSetup.pricePrecision); expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); }); describe('when there is insufficient collateral to rebalance', async () => { beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(10); + subjectStartingCurrentSetQuantity = gWei(1).div(10); }); it('should revert', async () => { @@ -433,10 +427,7 @@ contract('Auction', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auctionSetup: any = await auctionMock.auction.callAsync(); - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(auctionSetup.pricePrecision) - .div(2); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 92c46a158..3d92d93c6 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -23,7 +23,7 @@ import { Blockchain } from '@utils/blockchain'; import { getWeb3 } from '@utils/web3Helper'; import { DEFAULT_GAS, - ONE_DAY_IN_SECONDS + ONE_DAY_IN_SECONDS, } from '@utils/constants'; import { ether, gWei } from '@utils/units'; import { getLinearAuction, TokenFlow } from '@utils/auction'; @@ -210,9 +210,7 @@ contract('LinearAuction', accounts => { const auction: any = await auctionMock.auction.callAsync(); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -241,7 +239,7 @@ contract('LinearAuction', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -254,10 +252,10 @@ contract('LinearAuction', accounts => { ); const rangeStart = await auctionMock.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -270,7 +268,7 @@ contract('LinearAuction', accounts => { ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); describe('when currentSet is greater than 10x the nextSet', async () => { @@ -303,7 +301,7 @@ contract('LinearAuction', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -317,10 +315,10 @@ contract('LinearAuction', accounts => { const rangeStart = await auctionMock.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await auctionMock.auction.callAsync(); @@ -334,7 +332,7 @@ contract('LinearAuction', accounts => { const rangeEnd = await auctionMock.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); }); @@ -356,9 +354,9 @@ contract('LinearAuction', accounts => { ); }); - describe('#getNumerator', async () => { + describe('#getPrice', async () => { async function subject(): Promise { - return auctionMock.getNumerator.callAsync(); + return auctionMock.getPrice.callAsync(); } it('returns the correct result', async () => { @@ -423,7 +421,7 @@ contract('LinearAuction', accounts => { } it('returns the correct numerator', async () => { - const { numerator } = await subject(); + const numerator = await subject(); const { timestamp } = await web3.eth.getBlock('latest'); const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); const currentPrice = await liquidatorHelper.calculateCurrentPrice( @@ -433,13 +431,6 @@ contract('LinearAuction', accounts => { ); expect(numerator).to.bignumber.equal(currentPrice); }); - - it('returns the correct denominator', async () => { - const { denominator } = await subject(); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(denominator).to.bignumber.equal(linearAuction.auction.pricePrecision); - }); }); describe('#getTokenFlow', async () => { @@ -464,7 +455,6 @@ contract('LinearAuction', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); @@ -512,7 +502,6 @@ contract('LinearAuction', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 948a66e71..9ca2fa432 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,9 +249,7 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -280,7 +278,7 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -293,10 +291,10 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeStart = await liquidator.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -309,7 +307,7 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeEnd = await liquidator.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); describe('when the currentSet is > 10x nextSet', async () => { @@ -342,7 +340,7 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); }); - it('sets the correct startNumerator', async () => { + it('sets the correct startPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -355,10 +353,10 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeStart = await liquidator.rangeStart.callAsync(); const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); - expect(auction.startNumerator).to.bignumber.equal(expectedStartPrice); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); - it('sets the correct endNumerator', async () => { + it('sets the correct endPrice', async () => { await subject(); const auction: any = await liquidator.auctions.callAsync(subjectCaller); @@ -371,7 +369,7 @@ contract('LinearAuctionLiquidator', accounts => { ); const rangeEnd = await liquidator.rangeEnd.callAsync(); const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); - expect(auction.endNumerator).to.bignumber.equal(expectedEndPrice); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); @@ -426,6 +424,20 @@ contract('LinearAuctionLiquidator', accounts => { }); async function subject(): Promise { + return liquidatorProxy.placeBid.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + async function directCallSubject(): Promise { + return liquidator.placeBid.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + async function setTokenFlows(): Promise { const linearAuction = getLinearAuction(await liquidator.auctions.callAsync(liquidatorProxy.address)); const { timestamp } = await web3.eth.getBlock('latest'); @@ -440,19 +452,6 @@ contract('LinearAuctionLiquidator', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, - ); - - return liquidatorProxy.placeBid.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - async function directCallSubject(): Promise { - return liquidator.placeBid.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, ); } @@ -466,6 +465,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct combinedTokenArray', async () => { await subject(); + await setTokenFlows(); const combinedTokenArray = await liquidatorProxy.getCombinedTokenArray.callAsync(); expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(tokenFlows.addresses)); @@ -473,6 +473,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct inflow', async () => { await subject(); + await setTokenFlows(); const inflow = await liquidatorProxy.getInflow.callAsync(); expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); @@ -480,6 +481,7 @@ contract('LinearAuctionLiquidator', accounts => { it('returns the correct outflow', async () => { await subject(); + await setTokenFlows(); const outflow = await liquidatorProxy.getOutflow.callAsync(); expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); @@ -487,12 +489,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); - - const pricePrecision = auction.auction.pricePrecision; - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision) - .div(2); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); @@ -554,7 +551,6 @@ contract('LinearAuctionLiquidator', accounts => { linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, - linearAuction.auction.pricePrecision, ); }); @@ -616,8 +612,8 @@ contract('LinearAuctionLiquidator', accounts => { expect(JSON.stringify(auction.auction.combinedCurrentSetUnits)).to.equal(JSON.stringify([])); expect(JSON.stringify(auction.auction.combinedNextSetUnits)).to.equal(JSON.stringify([])); expect(auction.endTime).to.bignumber.equal(ZERO); - expect(auction.startNumerator).to.bignumber.equal(ZERO); - expect(auction.endNumerator).to.bignumber.equal(ZERO); + expect(auction.startPrice).to.bignumber.equal(ZERO); + expect(auction.endPrice).to.bignumber.equal(ZERO); }); describe('when there is a biddable quantity', async () => { @@ -666,8 +662,8 @@ contract('LinearAuctionLiquidator', accounts => { expect(JSON.stringify(auction.auction.combinedCurrentSetUnits)).to.equal(JSON.stringify([])); expect(JSON.stringify(auction.auction.combinedNextSetUnits)).to.equal(JSON.stringify([])); expect(auction.endTime).to.bignumber.equal(ZERO); - expect(auction.startNumerator).to.bignumber.equal(ZERO); - expect(auction.endNumerator).to.bignumber.equal(ZERO); + expect(auction.startPrice).to.bignumber.equal(ZERO); + expect(auction.endPrice).to.bignumber.equal(ZERO); }); describe('when the caller is not a valid Set', async () => { @@ -777,8 +773,8 @@ contract('LinearAuctionLiquidator', accounts => { const linearAuction = getLinearAuction(await liquidator.auctions.callAsync(subjectSet)); expect(auctionStartTime).to.bignumber.equal(linearAuction.auction.startTime); expect(auctionTimeToPivot).to.bignumber.equal(auctionPeriod); - expect(auctionStartPrice).to.bignumber.equal(linearAuction.startNumerator); - expect(auctionPivotPrice).to.bignumber.equal(linearAuction.endNumerator); + expect(auctionStartPrice).to.bignumber.equal(linearAuction.startPrice); + expect(auctionPivotPrice).to.bignumber.equal(linearAuction.endPrice); }); }); }); diff --git a/utils/auction.ts b/utils/auction.ts index c1ac29d0a..319e9e0dd 100644 --- a/utils/auction.ts +++ b/utils/auction.ts @@ -7,8 +7,8 @@ import { export interface LinearAuction { auction: Auction; endTime: BigNumber; - startNumerator: BigNumber; - endNumerator: BigNumber; + startPrice: BigNumber; + endPrice: BigNumber; } export interface Price { @@ -56,7 +56,7 @@ export function getLinearAuction(input: any): LinearAuction { combinedNextSetUnits: combinedNextSetUnits.map(v => new BigNumber(v)), }, endTime: new BigNumber(input.endTime), - startNumerator: new BigNumber(input.startNumerator), - endNumerator: new BigNumber(input.endNumerator), + startPrice: new BigNumber(input.startPrice), + endPrice: new BigNumber(input.endPrice), }; } \ No newline at end of file diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index af5ff22d4..af3df4080 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -15,6 +15,7 @@ import { import { getContractInstance, txnFrom } from '../web3Helper'; import { ZERO, + SCALE_FACTOR, } from '../constants'; import { LinearAuction, @@ -151,21 +152,17 @@ export class LiquidatorHelper { setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, - priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); - // Calculate minimumBidAmount - const maxNaturalUnit = minimumBid.div(priceDivisor); - // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); @@ -180,10 +177,10 @@ export class LiquidatorHelper { auctionPeriod: BigNumber ): BigNumber { const elapsed = timestamp.sub(linearAuction.auction.startTime); - const priceRange = new BigNumber(linearAuction.endNumerator).sub(linearAuction.startNumerator); + const priceRange = new BigNumber(linearAuction.endPrice).sub(linearAuction.startPrice); const elapsedPrice = elapsed.mul(priceRange).div(auctionPeriod).round(0, 3); - return new BigNumber(linearAuction.startNumerator).add(elapsedPrice); + return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } public calculateStartPrice( @@ -212,7 +209,7 @@ export class LiquidatorHelper { const currentSetUSDValue = await this.calculateSetTokenValueAsync(currentSetToken, oracleWhiteList); const nextSetUSDValue = await this.calculateSetTokenValueAsync(nextSetToken, oracleWhiteList); - return nextSetUSDValue.mul(pricePrecision).div(currentSetUSDValue).round(0, 3); + return nextSetUSDValue.mul(SCALE_FACTOR).div(currentSetUSDValue).round(0, 3); } public async calculateSetTokenValueAsync( @@ -292,8 +289,7 @@ export class LiquidatorHelper { linearAuction: LinearAuction, pricePrecision: BigNumber, quantity: BigNumber, - priceNumerator: BigNumber, - priceDenominator: BigNumber, + priceScaled: BigNumber, ): TokenFlow { const inflow: BigNumber[] = []; const outflow: BigNumber[] = []; @@ -309,17 +305,17 @@ export class LiquidatorHelper { const unitsMultiplier = quantity.div(minimumBid).round(0, 3).mul(pricePrecision); for (let i = 0; i < combinedCurrentSetUnits.length; i++) { - const flow = combinedNextSetUnits[i].mul(priceDenominator).sub(combinedCurrentSetUnits[i].mul(priceNumerator)); + const flow = combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)); if (flow.greaterThan(0)) { const inflowUnit = unitsMultiplier.mul( - combinedNextSetUnits[i].mul(priceDenominator).sub(combinedCurrentSetUnits[i].mul(priceNumerator)) - ).div(priceNumerator).round(0, 3); + combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)) + ).div(priceScaled).round(0, 3); inflow.push(inflowUnit); outflow.push(ZERO); } else { const outflowUnit = unitsMultiplier.mul( - combinedCurrentSetUnits[i].mul(priceNumerator).sub(combinedNextSetUnits[i].mul(priceDenominator)) - ).div(priceNumerator).round(0, 3); + combinedCurrentSetUnits[i].mul(priceScaled).sub(combinedNextSetUnits[i].mul(SCALE_FACTOR)) + ).div(priceScaled).round(0, 3); outflow.push(outflowUnit); inflow.push(ZERO); } From 3cb38e5d79713b99b6d65015929b9f6ad9ec1ad0 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:25:08 -0800 Subject: [PATCH 18/35] FIx --- contracts/core/liquidators/impl/LinearAuction.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 93faa55d4..056eb61fe 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -146,7 +146,7 @@ contract LinearAuction is Auction { } /** - * Returns the linear price based on the current timestamp. Returns the endNumerator + * Returns the linear price based on the current timestamp. Returns the endPrice * if time has exceeded the auciton period * * @param _linearAuction Linear Auction State object @@ -157,12 +157,12 @@ contract LinearAuction is Auction { // If current time has elapsed if (elapsed >= auctionPeriod) { - return _linearAuction.endNumerator; + return _linearAuction.endPrice; } else { - uint256 range = _linearAuction.endNumerator.sub(_linearAuction.startNumerator); + uint256 range = _linearAuction.endPrice.sub(_linearAuction.startPrice); uint256 elapsedPrice = elapsed.mul(range).div(auctionPeriod); - return _linearAuction.startNumerator.add(elapsedPrice); + return _linearAuction.startPrice.add(elapsedPrice); } } From 617cc99b279d6ab73bfdad46af4d88e96b633855 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 14:31:24 -0800 Subject: [PATCH 19/35] Fix compile --- test/contracts/core/liquidators/impl/linearAuction.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 3d92d93c6..f9ec08fd7 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -406,11 +406,11 @@ contract('LinearAuction', accounts => { ); }); - it('returns the correct price / endNumerator', async () => { + it('returns the correct price / endPrice', async () => { const result = await subject(); const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(result).to.bignumber.equal(linearAuction.endNumerator); + expect(result).to.bignumber.equal(linearAuction.endPrice); }); }); }); From a99b8dcb50064f8eb9d6456ae7bb19c252a978fa Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 15:55:14 -0800 Subject: [PATCH 20/35] Whats going on --- contracts/core/liquidators/impl/Auction.sol | 16 ++++++++++++---- package.json | 2 +- .../rebalancingLinearLiquidator.spec.ts | 2 +- .../core/liquidators/impl/auction.spec.ts | 12 +++++++++--- .../core/liquidators/impl/linearAuction.spec.ts | 4 +++- .../liquidators/linearAuctionLiquidator.spec.ts | 11 +++++++++-- utils/helpers/liquidatorHelper.ts | 6 +++++- 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 9adb55512..cf4a525d9 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -242,7 +242,8 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit); + return Math.max(currentSetNaturalUnit, nextNaturalUnit) + .mul(_pricePrecision); } /** @@ -357,11 +358,13 @@ contract Auction { returns (uint256[] memory) { address[] memory combinedTokenArray = _auction.combinedTokenArray; + uint256 pricePrecisionMem = _auction.pricePrecision; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, + pricePrecisionMem, combinedTokenArray[i] ); } @@ -374,11 +377,13 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount + * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, + uint256 _pricePrecision, uint256 _minimumBid, address _component ) @@ -397,7 +402,8 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid + _minimumBid, + _pricePrecision ); } @@ -411,18 +417,20 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount + * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid + uint256 _minimumBid, + uint256 _pricePrecision ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit).div(_pricePrecision); } /** diff --git a/package.json b/package.json index c43ee4187..85c5dd693 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-nocompile": "yarn transpile && yarn truffle-test-contracts", "test-continuous": "yarn deploy-development && truffle test", "transpile": "tsc", - "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", + "truffle-test-contracts": "truffle test `find transpiled/test/contracts/core/integration -name '*.spec.js'`", "prepublishOnly": "yarn dist", "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 04ada9ec9..1802c9967 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -428,7 +428,7 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); - describe('#settleRebalance', async () => { + describe.only('#settleRebalance', async () => { let subjectCaller: Address; let nextSetToken: SetTokenContract; diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index b19a025cf..19a50bf31 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -212,7 +212,8 @@ contract('Auction', accounts => { const auctionSetup: any = await auctionMock.auction.callAsync(); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auctionSetup.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -260,6 +261,7 @@ contract('Auction', accounts => { set1, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), + auctionSetup.pricePrecision ); expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -276,6 +278,7 @@ contract('Auction', accounts => { set2, combinedTokenArray, new BigNumber(auctionSetup.minimumBid), + auctionSetup.pricePrecision ); expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -343,7 +346,7 @@ contract('Auction', accounts => { describe('when there is insufficient collateral to rebalance', async () => { beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(1).div(10); + subjectStartingCurrentSetQuantity = gWei(10); }); it('should revert', async () => { @@ -427,7 +430,10 @@ contract('Auction', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + const auctionSetup: any = await auctionMock.auction.callAsync(); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(auctionSetup.pricePrecision) + .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index f9ec08fd7..0d8142beb 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -210,7 +210,9 @@ contract('LinearAuction', accounts => { const auction: any = await auctionMock.auction.callAsync(); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auction.auction.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 9ca2fa432..b7de7357f 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,7 +249,9 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const pricePrecision = auction.auction.pricePrecision; + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision); expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -489,7 +491,12 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); + + const pricePrecision = auction.auction.pricePrecision; + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) + .mul(pricePrecision) + .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index af3df4080..5380b3f26 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -152,17 +152,21 @@ export class LiquidatorHelper { setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, + priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); + // Calculate minimumBidAmount + const maxNaturalUnit = minimumBid.div(priceDivisor); + // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); From b789a406782274ad82419302fa1e1b3e7a0a1aae Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 16:02:47 -0800 Subject: [PATCH 21/35] Fix --- contracts/core/liquidators/impl/Auction.sol | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index cf4a525d9..344fb6ea9 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -383,8 +383,8 @@ contract Auction { */ function calculateCombinedUnit( ISetToken _setToken, - uint256 _pricePrecision, uint256 _minimumBid, + uint256 _pricePrecision, address _component ) private @@ -430,7 +430,8 @@ contract Auction { pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit).div(_pricePrecision); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) + .div(_pricePrecision); } /** diff --git a/package.json b/package.json index 85c5dd693..c43ee4187 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "test-nocompile": "yarn transpile && yarn truffle-test-contracts", "test-continuous": "yarn deploy-development && truffle test", "transpile": "tsc", - "truffle-test-contracts": "truffle test `find transpiled/test/contracts/core/integration -name '*.spec.js'`", + "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, From 8dd8306744c79355c606fcfe8a73908ab72ad53c Mon Sep 17 00:00:00 2001 From: felix2feng Date: Thu, 2 Jan 2020 16:24:57 -0800 Subject: [PATCH 22/35] remove.only --- .../core/integration/rebalancingLinearLiquidator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 1802c9967..04ada9ec9 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -428,7 +428,7 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); - describe.only('#settleRebalance', async () => { + describe('#settleRebalance', async () => { let subjectCaller: Address; let nextSetToken: SetTokenContract; From 976cb65da99413fb82e869491565c21ab7aab6f8 Mon Sep 17 00:00:00 2001 From: bweick Date: Sun, 5 Jan 2020 19:16:49 -0800 Subject: [PATCH 23/35] WIP. Added math to calculate bounds of auction. Rough integration in LinearAuction contract. --- contracts/core/liquidators/impl/Auction.sol | 1 - .../core/liquidators/impl/LinearAuction.sol | 45 +++- .../impl/TwoAssetAuctionBoundsCalculator.sol | 101 ++++++++ .../TwoAssetAuctionBoundsCalculatorMock.sol | 73 ++++++ .../twoAssetAuctionBoundsCalculator.spec.ts | 223 ++++++++++++++++++ utils/contracts.ts | 3 + utils/helpers/liquidatorHelper.ts | 80 +++++++ 7 files changed, 515 insertions(+), 11 deletions(-) create mode 100644 contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol create mode 100644 contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol create mode 100644 test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 344fb6ea9..fb4158811 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -17,7 +17,6 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; -import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 056eb61fe..5528da9d9 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -23,6 +23,7 @@ import { Auction } from "./Auction.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; +import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculator.sol"; /** @@ -31,7 +32,7 @@ import { Rebalance } from "../../lib/Rebalance.sol"; * * Library containing utility functions for computing auction Price for a linear price auction. */ -contract LinearAuction is Auction { +contract LinearAuction is TwoAssetAuctionBoundsCalculator { using SafeMath for uint256; /* ============ Structs ============ */ @@ -62,7 +63,7 @@ contract LinearAuction is Auction { uint256 _rangeEnd ) public - Auction(_oracleWhiteList) + TwoAssetAuctionBoundsCalculator(_oracleWhiteList) { auctionPeriod = _auctionPeriod; rangeStart = _rangeStart; @@ -95,8 +96,8 @@ contract LinearAuction is Auction { ); uint256 fairValue = calculateFairValue(_currentSet, _nextSet); - _linearAuction.startPrice = calculateStartPrice(fairValue); - _linearAuction.endPrice = calculateEndPrice(fairValue); + _linearAuction.startPrice = calculateStartPrice(_linearAuction, fairValue); + _linearAuction.endPrice = calculateEndPrice(_linearAuction, fairValue); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -187,16 +188,40 @@ contract LinearAuction is Auction { /** * Calculates the linear auction start price with a scaled value */ - function calculateStartPrice(uint256 _fairValueScaled) internal view returns(uint256) { - uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); - return _fairValueScaled.sub(startRange); + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.sub(startDifference); } /** * Calculates the linear auction end price with a scaled value */ - function calculateEndPrice(uint256 _fairValueScaled) internal view returns(uint256) { - uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); - return _fairValueScaled.add(endRange); + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.add(endDifference); } } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol b/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol new file mode 100644 index 000000000..81dd2780d --- /dev/null +++ b/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol @@ -0,0 +1,101 @@ +/* + Copyright 2019 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.5.7; +pragma experimental "ABIEncoderV2"; + +import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; + +import { Auction } from "./Auction.sol"; +import { CommonMath } from "../../../lib/CommonMath.sol"; +import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; + + +/** + * @title TwoAssetAuctionBoundsCalculator + * @author Set Protocol + * + */ +contract TwoAssetAuctionBoundsCalculator is Auction { + using SafeMath for uint256; + using CommonMath for uint256; + + struct AssetInfo { + uint256 assetPrice; + uint256 fullUnit; + } + + uint256 constant private ONE_HUNDRED = 100; + + constructor( + IOracleWhiteList _oracleWhiteList + ) + public + Auction(_oracleWhiteList) + {} + + function calculateAuctionBoundDifference( + Auction.Setup storage _auction, + uint256 _fairValue, + uint256 _boundValue + ) + internal + view + returns (uint256) + { + AssetInfo memory baseAsset = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory quoteAsset = getAssetInfo(_auction.combinedTokenArray[1]); + + uint256 numDifferential; + if (_auction.combinedNextSetUnits[0].scale() > _fairValue.mul(_auction.combinedCurrentSetUnits[0])) { + numDifferential = _auction.combinedNextSetUnits[0].scale().sub(_fairValue.mul(_auction.combinedCurrentSetUnits[0])); + } else { + numDifferential = _fairValue.mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].scale()); + } + + uint256 denomDifferential; + if (_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]) > _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])) { + denomDifferential = _auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]).sub(_auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])); + } else { + denomDifferential = _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1])); + } + + uint256 calcNumerator = quoteAsset.fullUnit + .mul(numDifferential) + .mul(_boundValue) + .mul(baseAsset.assetPrice) + .div(quoteAsset.assetPrice) + .div(ONE_HUNDRED); + + uint256 calcDenominator = baseAsset.fullUnit.mul(denomDifferential).scale(); + + return calcNumerator.scale().div(calcDenominator).mul(numDifferential).deScale(); + } + + function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { + address assetOracle = oracleWhiteList.getOracleAddressByToken(_asset); + uint256 assetPrice = IOracle(assetOracle).read(); + + uint256 decimals = ERC20Detailed(_asset).decimals(); + + return AssetInfo({ + assetPrice: assetPrice, + fullUnit: CommonMath.safePower(10, decimals) + }); + } +} \ No newline at end of file diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol new file mode 100644 index 000000000..972c516ef --- /dev/null +++ b/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol @@ -0,0 +1,73 @@ +/* + Copyright 2019 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity 0.5.7; +pragma experimental "ABIEncoderV2"; + +import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; + +import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; +import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; +import { TwoAssetAuctionBoundsCalculator } from "../../../../core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol"; + +/** + * @title TwoAssetAuctionBoundsCalculator + * @author Set Protocol + * + */ +contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator { + + Auction.Setup public auctionInfo; + + constructor( + IOracleWhiteList _oracleWhiteList + ) + public + TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + {} + + function calculateAuctionBoundDifferenceMock( + uint256 _fairValue, + uint256 _rangeStart + ) + external + view + returns (uint256) + { + return calculateAuctionBoundDifference(auctionInfo, _fairValue, _rangeStart); + } + + function parameterizeAuction( + address[] calldata _combinedTokenArray, + uint256[] calldata _combinedCurrentSetUnits, + uint256[] calldata _combinedNextSetUnits + ) + external + { + auctionInfo.combinedTokenArray = _combinedTokenArray; + auctionInfo.combinedCurrentSetUnits = _combinedCurrentSetUnits; + auctionInfo.combinedNextSetUnits = _combinedNextSetUnits; + } + + function getCombinedTokenArray() + external + view + returns(address[] memory) + { + return auctionInfo.combinedTokenArray; + } +} \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts b/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts new file mode 100644 index 000000000..9a135a15a --- /dev/null +++ b/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts @@ -0,0 +1,223 @@ +require('module-alias/register'); + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + StandardTokenMockContract, + OracleWhiteListContract, + TwoAssetAuctionBoundsCalculatorMockContract, + UpdatableOracleMockContract, +} from '@utils/contracts'; +import { ether } from '@utils/units'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const { expect } = chai; + +contract('TwoAssetAuctionBoundsCalculator', accounts => { + const [ + ownerAccount, + ] = accounts; + + let boundsCalculator: TwoAssetAuctionBoundsCalculatorMockContract; + let oracleWhiteList: OracleWhiteListContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + const libraryMockHelper = new LibraryMockHelper(ownerAccount); + + let wrappedETH: StandardTokenMockContract; + let wrappedBTC: StandardTokenMockContract; + let usdc: StandardTokenMockContract; + + let wrappedETHPrice: BigNumber; + let wrappedBTCPrice: BigNumber; + let usdcPrice: BigNumber; + + let wrappedETHOracle: UpdatableOracleMockContract; + let wrappedBTCOracle: UpdatableOracleMockContract; + let usdcOracle: UpdatableOracleMockContract; + + before(async () => { + wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); + wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); + usdc = await erc20Helper.deployTokenAsync(ownerAccount, 6); + + wrappedETHPrice = ether(128); + wrappedBTCPrice = ether(7500); + usdcPrice = ether(1); + + wrappedETHOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedETHPrice); + wrappedBTCOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedBTCPrice); + usdcOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(usdcPrice); + + oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( + [wrappedETH.address, wrappedBTC.address, usdc.address], + [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address], + ); + + boundsCalculator = await liquidatorHelper.deployTwoAssetAuctionBoundsCalculatorMock( + oracleWhiteList.address + ); + }); + + describe.only('#calculateStartNumerator', async () => { + let subjectFairValue: BigNumber; + let subjectRangeStart: BigNumber; + + let combinedTokenArray: Address[]; + let combinedCurrentSetUnits: BigNumber[]; + let combinedNextSetUnits: BigNumber[]; + + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + }); + + beforeEach(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + + await boundsCalculator.parameterizeAuction.sendTransactionAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits + ); + + subjectFairValue = ether(5); + subjectRangeStart = new BigNumber(3); + }); + + async function subject(): Promise { + return boundsCalculator.calculateAuctionBoundDifferenceMock.callAsync( + subjectFairValue, + subjectRangeStart + ); + } + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + + describe('when asset order is flipped', async () => { + before(async () => { + combinedTokenArray = [usdc.address, wrappedETH.address]; + combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; + combinedNextSetUnits = [new BigNumber(1152), new BigNumber(10 ** 12)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('when other asset is higher allocation', async () => { + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(9 * 10 ** 12), new BigNumber(128)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('when other asset is highest allocation and assets are flipped', async () => { + before(async () => { + combinedTokenArray = [usdc.address, wrappedETH.address]; + combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; + combinedNextSetUnits = [new BigNumber(1152), new BigNumber(9 * 10 ** 12)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + + describe('different allocation', async () => { + before(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(192)]; + }); + + it('sets the correct oracleWhiteList', async () => { + const actualStartBound = await subject(); + + const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + subjectFairValue, + subjectRangeStart, + new BigNumber(21), + oracleWhiteList + ); + + expect(actualStartBound).to.bignumber.equal(expectedStartBound); + }); + }); + }); +}); \ No newline at end of file diff --git a/utils/contracts.ts b/utils/contracts.ts index ee4e418f1..ae485610e 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -70,6 +70,9 @@ export { TimeLockUpgradeV2Contract } from '../types/generated/time_lock_upgrade_ export { TimeLockUpgradeV2MockContract } from '../types/generated/time_lock_upgrade_v2_mock'; export { TransferProxyContract } from '../types/generated/transfer_proxy'; export { TradingPoolViewerContract } from '../types/generated/trading_pool_viewer'; +export { + TwoAssetAuctionBoundsCalculatorMockContract +} from '../types/generated/two_asset_auction_bounds_calculator_mock'; export { UpdatableConstantAuctionPriceCurveContract } from '../types/generated/updatable_constant_auction_price_curve'; export { UpdatableOracleMockContract } from '../types/generated/updatable_oracle_mock'; export { VaultContract } from '../types/generated/vault'; diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 5380b3f26..4fc3ffb3a 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -11,6 +11,7 @@ import { LiquidatorProxyContract, OracleWhiteListContract, SetTokenContract, + TwoAssetAuctionBoundsCalculatorMockContract, } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { @@ -21,12 +22,14 @@ import { LinearAuction, TokenFlow } from '../auction'; +import { ether } from '@utils/units'; const AuctionMock = artifacts.require('AuctionMock'); const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); const LinearAuctionMock = artifacts.require('LinearAuctionMock'); const LiquidatorMock = artifacts.require('LiquidatorMock'); const LiquidatorProxy = artifacts.require('LiquidatorProxy'); +const TwoAssetAuctionBoundsCalculatorMock = artifacts.require('TwoAssetAuctionBoundsCalculatorMock'); import { ERC20Helper } from './erc20Helper'; import { LibraryMockHelper } from './libraryMockHelper'; @@ -110,6 +113,21 @@ export class LiquidatorHelper { ); } + public async deployTwoAssetAuctionBoundsCalculatorMock( + oracleWhiteList: Address, + from: Address = this._contractOwnerAddress + ): Promise { + const twoAssetAuctionBoundsCalculatorMock = await TwoAssetAuctionBoundsCalculatorMock.new( + oracleWhiteList, + txnFrom(from) + ); + + return new TwoAssetAuctionBoundsCalculatorMockContract( + getContractInstance(twoAssetAuctionBoundsCalculatorMock), + txnFrom(from) + ); + } + public async deployLiquidatorMockAsync( from: Address = this._contractOwnerAddress ): Promise { @@ -175,6 +193,68 @@ export class LiquidatorHelper { return combinedSetTokenUnits; } + public async calculateAuctionBoundsAsync( + combinedTokenArray: Address[], + combinedCurrentUnitArray: BigNumber[], + combinedNextUnitArray: BigNumber[], + fairValue: BigNumber, + startBound: BigNumber, + endBound: BigNumber, + oracleWhiteList: OracleWhiteListContract + ): Promise<[BigNumber, BigNumber]> { + const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync(combinedTokenArray); + + const assetOneFullUnit = new BigNumber(10 ** assetOneDecimals.toNumber()); + const assetTwoFullUnit = new BigNumber(10 ** assetTwoDecimals.toNumber()); + + const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync(combinedTokenArray, oracleWhiteList); + + const startValue = this.calculateAuctionBound( + combinedCurrentUnitArray, + combinedNextUnitArray, + assetOneFullUnit, + assetTwoFullUnit, + assetOnePrice.div(assetTwoPrice), + fairValue, + startBound + ); + + const endValue = this.calculateAuctionBound( + combinedCurrentUnitArray, + combinedNextUnitArray, + assetOneFullUnit, + assetTwoFullUnit, + assetOnePrice.div(assetTwoPrice), + fairValue, + endBound + ); + + return [startValue, endValue]; + } + + public calculateAuctionBound( + combinedCurrentUnitArray: BigNumber[], + combinedNextUnitArray: BigNumber[], + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + assetPairPrice: BigNumber, + fairValue: BigNumber, + boundValue: BigNumber + ): BigNumber { + const numerator = (combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0]))).pow(2) + .mul(assetTwoFullUnit) + .mul(boundValue) + .mul(assetPairPrice) + .div(100); + + const denominator = combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]).sub( + combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) + .mul(assetOneFullUnit) + .mul(10 ** 18); + + return numerator.div(denominator).abs(); + } + public calculateCurrentPrice( linearAuction: LinearAuction, timestamp: BigNumber, From 349e41a9fec1a17420c61de0c3d13c2a5f4b8249 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Sun, 5 Jan 2020 20:55:20 -0800 Subject: [PATCH 24/35] Changes (#560) --- .../liquidators/LinearAuctionLiquidator.sol | 5 +- .../core/liquidators/impl/LinearAuction.sol | 29 ++-------- ... => TwoAssetPriceBoundedLinearAuction.sol} | 58 +++++++++++++++++-- .../liquidators/impl/LinearAuctionMock.sol | 24 ++++++++ ...TwoAssetPriceBoundedLinearAuctionMock.sol} | 32 ++++++---- package.json | 2 +- ...twoAssetPriceBoundedLinearAuction.spec.ts} | 23 ++++++-- utils/contracts.ts | 4 +- utils/helpers/liquidatorHelper.ts | 20 ++++--- 9 files changed, 140 insertions(+), 57 deletions(-) rename contracts/core/liquidators/impl/{TwoAssetAuctionBoundsCalculator.sol => TwoAssetPriceBoundedLinearAuction.sol} (72%) rename contracts/mocks/core/liquidators/impl/{TwoAssetAuctionBoundsCalculatorMock.sol => TwoAssetPriceBoundedLinearAuctionMock.sol} (59%) rename test/contracts/core/liquidators/impl/{twoAssetAuctionBoundsCalculator.spec.ts => twoAssetPriceBoundedLinearAuction.spec.ts} (91%) diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index db9b53b64..3acb4c856 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -27,6 +27,7 @@ import { Auction } from "./impl/Auction.sol"; import { LinearAuction } from "./impl/LinearAuction.sol"; import { Rebalance } from "../lib/Rebalance.sol"; import { RebalancingLibrary } from "../lib/RebalancingLibrary.sol"; +import { TwoAssetPriceBoundedLinearAuction } from "./impl/TwoAssetPriceBoundedLinearAuction.sol"; /** @@ -36,7 +37,7 @@ import { RebalancingLibrary } from "../lib/RebalancingLibrary.sol"; * Contract that holds all the state and functionality required for setting up, returning prices, and tearing * down linear auction rebalances for RebalancingSetTokens. */ -contract LinearAuctionLiquidator is LinearAuction, ILiquidator { +contract LinearAuctionLiquidator is TwoAssetPriceBoundedLinearAuction, ILiquidator { using SafeMath for uint256; ICore public core; @@ -68,7 +69,7 @@ contract LinearAuctionLiquidator is LinearAuction, ILiquidator { string memory _name ) public - LinearAuction( + TwoAssetPriceBoundedLinearAuction( _oracleWhiteList, _auctionPeriod, _rangeStart, diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 5528da9d9..1526bef05 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -23,7 +23,6 @@ import { Auction } from "./Auction.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; -import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculator.sol"; /** @@ -32,7 +31,7 @@ import { TwoAssetAuctionBoundsCalculator } from "./TwoAssetAuctionBoundsCalculat * * Library containing utility functions for computing auction Price for a linear price auction. */ -contract LinearAuction is TwoAssetAuctionBoundsCalculator { +contract LinearAuction is Auction { using SafeMath for uint256; /* ============ Structs ============ */ @@ -63,7 +62,7 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { uint256 _rangeEnd ) public - TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + Auction(_oracleWhiteList) { auctionPeriod = _auctionPeriod; rangeStart = _rangeStart; @@ -186,6 +185,7 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { } /** + * Abstract function that must be implemented. * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( @@ -194,18 +194,10 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { ) internal view - returns(uint256) - { - uint256 startDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart - ); - - return _fairValueScaled.sub(startDifference); - } + returns(uint256); /** + * Abstract function that must be implemented. * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( @@ -214,14 +206,5 @@ contract LinearAuction is TwoAssetAuctionBoundsCalculator { ) internal view - returns(uint256) - { - uint256 endDifference = TwoAssetAuctionBoundsCalculator.calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart - ); - - return _fairValueScaled.add(endDifference); - } + returns(uint256); } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol similarity index 72% rename from contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol rename to contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 81dd2780d..6505d2d28 100644 --- a/contracts/core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -22,16 +22,17 @@ import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { Auction } from "./Auction.sol"; +import { LinearAuction } from "./LinearAuction.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; /** - * @title TwoAssetAuctionBoundsCalculator + * @title TwoAssetPriceBoundedLinearAuction * @author Set Protocol * */ -contract TwoAssetAuctionBoundsCalculator is Auction { +contract TwoAssetPriceBoundedLinearAuction is LinearAuction { using SafeMath for uint256; using CommonMath for uint256; @@ -43,12 +44,61 @@ contract TwoAssetAuctionBoundsCalculator is Auction { uint256 constant private ONE_HUNDRED = 100; constructor( - IOracleWhiteList _oracleWhiteList + IOracleWhiteList _oracleWhiteList, + uint256 _auctionPeriod, + uint256 _rangeStart, + uint256 _rangeEnd ) public - Auction(_oracleWhiteList) + LinearAuction( + _oracleWhiteList, + _auctionPeriod, + _rangeStart, + _rangeEnd + ) {} + /** + * Calculates the linear auction start price with a scaled value + */ + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startDifference = calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.sub(startDifference); + } + + /** + * Calculates the linear auction end price with a scaled value + */ + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endDifference = calculateAuctionBoundDifference( + _linearAuction.auction, + _fairValueScaled, + rangeStart + ); + + return _fairValueScaled.add(endDifference); + } + + function calculateAuctionBoundDifference( Auction.Setup storage _auction, uint256 _fairValue, diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 99254651e..db67331b5 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -26,6 +26,30 @@ contract LinearAuctionMock is LinearAuction { ) {} + function calculateStartPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); + return _fairValueScaled.sub(startRange); + } + + function calculateEndPrice( + State storage _linearAuction, + uint256 _fairValueScaled + ) + internal + view + returns(uint256) + { + uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); + return _fairValueScaled.add(endRange); + } + function initializeLinearAuction( ISetToken _currentSet, ISetToken _nextSet, diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol similarity index 59% rename from contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol rename to contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 972c516ef..4fd8935c9 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetAuctionBoundsCalculatorMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -20,24 +20,32 @@ pragma experimental "ABIEncoderV2"; import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; -import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; +import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; -import { TwoAssetAuctionBoundsCalculator } from "../../../../core/liquidators/impl/TwoAssetAuctionBoundsCalculator.sol"; +import { TwoAssetPriceBoundedLinearAuction } from "../../../../core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol"; /** - * @title TwoAssetAuctionBoundsCalculator + * @title TwoAssetPriceBoundedLinearAuction * @author Set Protocol * */ -contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator { +contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuction { - Auction.Setup public auctionInfo; + LinearAuction.State public auctionInfo; constructor( - IOracleWhiteList _oracleWhiteList + IOracleWhiteList _oracleWhiteList, + uint256 _auctionPeriod, + uint256 _rangeStart, + uint256 _rangeEnd ) public - TwoAssetAuctionBoundsCalculator(_oracleWhiteList) + TwoAssetPriceBoundedLinearAuction( + _oracleWhiteList, + _auctionPeriod, + _rangeStart, + _rangeEnd + ) {} function calculateAuctionBoundDifferenceMock( @@ -48,7 +56,7 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator view returns (uint256) { - return calculateAuctionBoundDifference(auctionInfo, _fairValue, _rangeStart); + return calculateAuctionBoundDifference(auctionInfo.auction, _fairValue, _rangeStart); } function parameterizeAuction( @@ -58,9 +66,9 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator ) external { - auctionInfo.combinedTokenArray = _combinedTokenArray; - auctionInfo.combinedCurrentSetUnits = _combinedCurrentSetUnits; - auctionInfo.combinedNextSetUnits = _combinedNextSetUnits; + auctionInfo.auction.combinedTokenArray = _combinedTokenArray; + auctionInfo.auction.combinedCurrentSetUnits = _combinedCurrentSetUnits; + auctionInfo.auction.combinedNextSetUnits = _combinedNextSetUnits; } function getCombinedTokenArray() @@ -68,6 +76,6 @@ contract TwoAssetAuctionBoundsCalculatorMock is TwoAssetAuctionBoundsCalculator view returns(address[] memory) { - return auctionInfo.combinedTokenArray; + return auctionInfo.auction.combinedTokenArray; } } \ No newline at end of file diff --git a/package.json b/package.json index c43ee4187..86f8e15b9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "transpile": "tsc", "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", - "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" + "flatten": "truffle-flattener contracts/core/tokens/RebalancingSetTokenV2Factory.sol" }, "repository": "git@github.com:SetProtocol/set-protocol-contracts.git", "author": "Felix Feng ", diff --git a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts similarity index 91% rename from test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts rename to test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 9a135a15a..c15426856 100644 --- a/test/contracts/core/liquidators/impl/twoAssetAuctionBoundsCalculator.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -10,7 +10,7 @@ import { BigNumberSetup } from '@utils/bigNumberSetup'; import { StandardTokenMockContract, OracleWhiteListContract, - TwoAssetAuctionBoundsCalculatorMockContract, + TwoAssetPriceBoundedLinearAuctionMockContract, UpdatableOracleMockContract, } from '@utils/contracts'; import { ether } from '@utils/units'; @@ -24,12 +24,12 @@ BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; -contract('TwoAssetAuctionBoundsCalculator', accounts => { +contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ ownerAccount, ] = accounts; - let boundsCalculator: TwoAssetAuctionBoundsCalculatorMockContract; + let boundsCalculator: TwoAssetPriceBoundedLinearAuctionMockContract; let oracleWhiteList: OracleWhiteListContract; const coreHelper = new CoreHelper(ownerAccount, ownerAccount); @@ -49,6 +49,10 @@ contract('TwoAssetAuctionBoundsCalculator', accounts => { let wrappedBTCOracle: UpdatableOracleMockContract; let usdcOracle: UpdatableOracleMockContract; + let auctionPeriod: BigNumber; + let rangeStart: BigNumber; + let rangeEnd: BigNumber; + before(async () => { wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); @@ -67,12 +71,19 @@ contract('TwoAssetAuctionBoundsCalculator', accounts => { [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address], ); - boundsCalculator = await liquidatorHelper.deployTwoAssetAuctionBoundsCalculatorMock( - oracleWhiteList.address + auctionPeriod = new BigNumber(14400); // 4 hours + rangeStart = new BigNumber(3); // 3% + rangeEnd = new BigNumber(21); // 21% + + boundsCalculator = await liquidatorHelper.deployTwoAssetPriceBoundedLinearAuctionMock( + oracleWhiteList.address, + auctionPeriod, + rangeStart, + rangeEnd, ); }); - describe.only('#calculateStartNumerator', async () => { + describe('#calculateAuctionBoundDifference', async () => { let subjectFairValue: BigNumber; let subjectRangeStart: BigNumber; diff --git a/utils/contracts.ts b/utils/contracts.ts index ae485610e..dd43296bb 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -71,8 +71,8 @@ export { TimeLockUpgradeV2MockContract } from '../types/generated/time_lock_upgr export { TransferProxyContract } from '../types/generated/transfer_proxy'; export { TradingPoolViewerContract } from '../types/generated/trading_pool_viewer'; export { - TwoAssetAuctionBoundsCalculatorMockContract -} from '../types/generated/two_asset_auction_bounds_calculator_mock'; + TwoAssetPriceBoundedLinearAuctionMockContract +} from '../types/generated/two_asset_price_bounded_linear_auction_mock'; export { UpdatableConstantAuctionPriceCurveContract } from '../types/generated/updatable_constant_auction_price_curve'; export { UpdatableOracleMockContract } from '../types/generated/updatable_oracle_mock'; export { VaultContract } from '../types/generated/vault'; diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 4fc3ffb3a..1dff2a668 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -11,7 +11,7 @@ import { LiquidatorProxyContract, OracleWhiteListContract, SetTokenContract, - TwoAssetAuctionBoundsCalculatorMockContract, + TwoAssetPriceBoundedLinearAuctionMockContract, } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { @@ -29,7 +29,7 @@ const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); const LinearAuctionMock = artifacts.require('LinearAuctionMock'); const LiquidatorMock = artifacts.require('LiquidatorMock'); const LiquidatorProxy = artifacts.require('LiquidatorProxy'); -const TwoAssetAuctionBoundsCalculatorMock = artifacts.require('TwoAssetAuctionBoundsCalculatorMock'); +const TwoAssetPriceBoundedLinearAuctionMock = artifacts.require('TwoAssetPriceBoundedLinearAuctionMock'); import { ERC20Helper } from './erc20Helper'; import { LibraryMockHelper } from './libraryMockHelper'; @@ -113,17 +113,23 @@ export class LiquidatorHelper { ); } - public async deployTwoAssetAuctionBoundsCalculatorMock( + public async deployTwoAssetPriceBoundedLinearAuctionMock( oracleWhiteList: Address, + auctionPeriod: BigNumber, + rangeStart: BigNumber, + rangeEnd: BigNumber, from: Address = this._contractOwnerAddress - ): Promise { - const twoAssetAuctionBoundsCalculatorMock = await TwoAssetAuctionBoundsCalculatorMock.new( + ): Promise { + const mockContract = await TwoAssetPriceBoundedLinearAuctionMock.new( oracleWhiteList, + auctionPeriod, + rangeStart, + rangeEnd, txnFrom(from) ); - return new TwoAssetAuctionBoundsCalculatorMockContract( - getContractInstance(twoAssetAuctionBoundsCalculatorMock), + return new TwoAssetPriceBoundedLinearAuctionMockContract( + getContractInstance(mockContract), txnFrom(from) ); } From 82dadd2de8ab2aae0a396130dd4fd7bfeded3da0 Mon Sep 17 00:00:00 2001 From: felix2feng Date: Sun, 5 Jan 2020 21:34:35 -0800 Subject: [PATCH 25/35] Push tests --- package.json | 2 +- .../liquidators/impl/linearAuction.spec.ts | 13 ++++-- .../linearAuctionLiquidator.spec.ts | 28 +++++++++++-- utils/helpers/liquidatorHelper.ts | 40 ++++++++++++++----- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 86f8e15b9..c43ee4187 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "transpile": "tsc", "truffle-test-contracts": "truffle test `find transpiled/test/contracts -name '*.spec.js'`", "prepublishOnly": "yarn dist", - "flatten": "truffle-flattener contracts/core/tokens/RebalancingSetTokenV2Factory.sol" + "flatten": "truffle-flattener contracts/core/[XXX]/[XXX].sol" }, "repository": "git@github.com:SetProtocol/set-protocol-contracts.git", "author": "Felix Feng ", diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 0d8142beb..f7bfa03d8 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -253,7 +253,9 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeStart = await auctionMock.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -269,7 +271,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); @@ -315,7 +318,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeStart = await auctionMock.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -332,7 +336,8 @@ contract('LinearAuction', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index b7de7357f..0ff045ec7 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -292,7 +292,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + getLinearAuction(auction), + fairValue, + rangeStart, + oracleWhiteList, + ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -308,7 +313,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + getLinearAuction(auction), + fairValue, + rangeEnd, + oracleWhiteList, + ); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); @@ -354,7 +364,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = liquidatorHelper.calculateStartPrice(fairValue, rangeStart); + const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + getLinearAuction(auction), + fairValue, + rangeStart, + oracleWhiteList, + ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); }); @@ -370,7 +385,12 @@ contract('LinearAuctionLiquidator', accounts => { auction.auction.pricePrecision, ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = liquidatorHelper.calculateEndPrice(fairValue, rangeEnd); + const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + getLinearAuction(auction), + fairValue, + rangeEnd, + oracleWhiteList, + ); expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 1dff2a668..8a446e666 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -273,20 +273,40 @@ export class LiquidatorHelper { return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } - public calculateStartPrice( + public async calculateTwoAssetStartPrice( + linearAuction: LinearAuction, fairValue: BigNumber, - rangeStart: BigNumber, - ): BigNumber { - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - return fairValue.sub(negativeRange); + rangeStartPercentage: BigNumber, + oracleWhiteList: OracleWhiteListContract, + ): Promise { + const [startDifference] = await this.calculateAuctionBoundsAsync( + linearAuction.auction.combinedTokenArray, + linearAuction.auction.combinedCurrentSetUnits, + linearAuction.auction.combinedNextSetUnits, + fairValue, + rangeStartPercentage, + rangeStartPercentage, // Dummy value, unused + oracleWhiteList + ); + return fairValue.sub(startDifference); } - public calculateEndPrice( + public async calculateTwoAssetEndPrice( + linearAuction: LinearAuction, fairValue: BigNumber, - rangeEnd: BigNumber, - ): BigNumber { - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - return fairValue.add(positiveRange); + rangeEndPercentage: BigNumber, + oracleWhiteList: OracleWhiteListContract, + ): Promise { + const [, endDifference] = await this.calculateAuctionBoundsAsync( + linearAuction.auction.combinedTokenArray, + linearAuction.auction.combinedCurrentSetUnits, + linearAuction.auction.combinedNextSetUnits, + fairValue, + rangeEndPercentage, // Dummy value, unused + rangeEndPercentage, + oracleWhiteList + ); + return fairValue.add(endDifference); } public async calculateFairValueAsync( From 422dfad03a478c1c6b3334c9ea6cd37664c25647 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Mon, 6 Jan 2020 01:44:56 -0800 Subject: [PATCH 26/35] [Liquidator] Add 2 component validation (#561) * Validations * Fix formula * Add validate tests --- .../liquidators/LinearAuctionLiquidator.sol | 2 +- .../TwoAssetPriceBoundedLinearAuction.sol | 27 ++- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 14 ++ .../rebalancingLinearLiquidator.spec.ts | 25 +- .../twoAssetPriceBoundedLinearAuction.spec.ts | 218 +++++++++++++++++- .../linearAuctionLiquidator.spec.ts | 30 ++- utils/helpers/liquidatorHelper.ts | 45 +++- 7 files changed, 329 insertions(+), 32 deletions(-) diff --git a/contracts/core/liquidators/LinearAuctionLiquidator.sol b/contracts/core/liquidators/LinearAuctionLiquidator.sol index 3acb4c856..de8560fcb 100644 --- a/contracts/core/liquidators/LinearAuctionLiquidator.sol +++ b/contracts/core/liquidators/LinearAuctionLiquidator.sol @@ -101,7 +101,7 @@ contract LinearAuctionLiquidator is TwoAssetPriceBoundedLinearAuction, ILiquidat { _liquidatorData; // Pass linting - LinearAuction.validateRebalanceComponents( + TwoAssetPriceBoundedLinearAuction.validateTwoAssetPriceBoundedAuction( _currentSet, _nextSet ); diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 6505d2d28..da14ba246 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -22,9 +22,10 @@ import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { Auction } from "./Auction.sol"; -import { LinearAuction } from "./LinearAuction.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; +import { ISetToken } from "../../interfaces/ISetToken.sol"; +import { LinearAuction } from "./LinearAuction.sol"; /** @@ -58,6 +59,28 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ) {} + /** + * Validates that the auction only includes two components and the components are valid. + */ + function validateTwoAssetPriceBoundedAuction( + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + { + address[] memory combinedTokenArray = Auction.getCombinedTokenArray(_currentSet, _nextSet); + require( + combinedTokenArray.length == 2, + "TwoAssetPriceBoundedLinearAuction: Only two components are allowed." + ); + + LinearAuction.validateRebalanceComponents( + _currentSet, + _nextSet + ); + } + /** * Calculates the linear auction start price with a scaled value */ @@ -92,7 +115,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 endDifference = calculateAuctionBoundDifference( _linearAuction.auction, _fairValueScaled, - rangeStart + rangeEnd ); return _fairValueScaled.add(endDifference); diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 4fd8935c9..b77df428b 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -21,6 +21,7 @@ import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20 import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; +import { ISetToken } from "../../../../core/interfaces/ISetToken.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; import { TwoAssetPriceBoundedLinearAuction } from "../../../../core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol"; @@ -48,6 +49,19 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct ) {} + // To test + function validateTwoAssetPriceBoundedAuctionMock(ISetToken _currentSet,ISetToken _nextSet) external view { + validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); + } + + function calculateStartPriceMock(uint256 _fairValueScaled) external view returns(uint256) { + return super.calculateStartPrice(auctionInfo, _fairValueScaled); + } + + function calculateEndPriceMock(uint256 _fairValueScaled) external view returns(uint256) { + return super.calculateEndPrice(auctionInfo, _fairValueScaled); + } + function calculateAuctionBoundDifferenceMock( uint256 _fairValue, uint256 _rangeStart diff --git a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts index 04ada9ec9..2c7d1d386 100644 --- a/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts +++ b/test/contracts/core/integration/rebalancingLinearLiquidator.spec.ts @@ -173,8 +173,8 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { set1NaturalUnit, ); - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; + set2Components = [component1.address, component2.address]; + set2Units = [gWei(1), gWei(2)]; set2NaturalUnit = customSet2NaturalUnit || gWei(2); set2 = await coreHelper.createSetTokenAsync( coreMock, @@ -355,6 +355,27 @@ contract('RebalancingSetV2 - LinearAuctionLiquidator', accounts => { }); }); + describe('when the union of currentSet and nextSet is not 2 components', async () => { + beforeEach(async () => { + const set3Components = [component1.address, component3.address]; + const set3Units = [gWei(1), gWei(1)]; + const set3NaturalUnit = customSet1NaturalUnit || gWei(1); + const set3 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set3Components, + set3Units, + set3NaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + describe('when the rebalance interval has not elapsed', async () => { beforeEach(async () => { subjectTimeFastForward = ONE_DAY_IN_SECONDS.sub(10); diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index c15426856..7fc64be47 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -2,40 +2,54 @@ require('module-alias/register'); import * as _ from 'lodash'; import * as chai from 'chai'; +import * as ABIDecoder from 'abi-decoder'; import { BigNumber } from 'bignumber.js'; import { Address } from 'set-protocol-utils'; import ChaiSetup from '@utils/chaiSetup'; import { BigNumberSetup } from '@utils/bigNumberSetup'; import { - StandardTokenMockContract, + CoreMockContract, OracleWhiteListContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, TwoAssetPriceBoundedLinearAuctionMockContract, + TransferProxyContract, UpdatableOracleMockContract, + VaultContract, } from '@utils/contracts'; -import { ether } from '@utils/units'; +import { ether, gWei } from '@utils/units'; +import { LinearAuction } from '@utils/auction'; import { CoreHelper } from '@utils/helpers/coreHelper'; import { ERC20Helper } from '@utils/helpers/erc20Helper'; import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; +import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; +const CoreMock = artifacts.require('CoreMock'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ - ownerAccount, + deployerAccount, ] = accounts; + let coreMock: CoreMockContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let boundsCalculator: TwoAssetPriceBoundedLinearAuctionMockContract; let oracleWhiteList: OracleWhiteListContract; - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); + const coreHelper = new CoreHelper(deployerAccount, deployerAccount); + const erc20Helper = new ERC20Helper(deployerAccount); + const liquidatorHelper = new LiquidatorHelper(deployerAccount, erc20Helper); + const libraryMockHelper = new LibraryMockHelper(deployerAccount); let wrappedETH: StandardTokenMockContract; let wrappedBTC: StandardTokenMockContract; @@ -54,9 +68,17 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { - wrappedETH = await erc20Helper.deployTokenAsync(ownerAccount, 18); - wrappedBTC = await erc20Helper.deployTokenAsync(ownerAccount, 8); - usdc = await erc20Helper.deployTokenAsync(ownerAccount, 6); + ABIDecoder.addABI(CoreMock.abi); + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + coreMock = await coreHelper.deployCoreMockAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(coreMock.address); + await coreHelper.setDefaultStateAndAuthorizationsAsync(coreMock, vault, transferProxy, setTokenFactory); + + wrappedETH = await erc20Helper.deployTokenAsync(deployerAccount, 18); + wrappedBTC = await erc20Helper.deployTokenAsync(deployerAccount, 8); + usdc = await erc20Helper.deployTokenAsync(deployerAccount, 6); wrappedETHPrice = ether(128); wrappedBTCPrice = ether(7500); @@ -83,6 +105,182 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { ); }); + after(async () => { + ABIDecoder.removeABI(CoreMock.abi); + }); + + describe('#validateTwoAssetPriceBoundedAuction', async () => { + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + let customComponents1: Address[]; + let customComponents2: Address[]; + + let customUnits1: BigNumber[]; + let customUnits2: BigNumber[]; + + let subjectCurrentSet: Address; + let subjectNextSet: Address; + + beforeEach(async () => { + set1Components = customComponents1 || [wrappedETH.address, wrappedBTC.address]; + set1Units = customUnits1 || [gWei(1), gWei(1)]; + set1NaturalUnit = new BigNumber(10 ** 12); + set1 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = customComponents2 || [wrappedETH.address, wrappedBTC.address]; + set2Units = customUnits2 || [gWei(1), gWei(2)]; + set2NaturalUnit = new BigNumber(10 ** 12); + set2 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + }); + + async function subject(): Promise { + return boundsCalculator.validateTwoAssetPriceBoundedAuctionMock.callAsync( + subjectCurrentSet, + subjectNextSet + ); + } + + it('does not revert', async () => { + await subject(); + }); + + describe('when the union is 3 components', async () => { + before(async () => { + customComponents2 = [wrappedETH.address, usdc.address]; + }); + + after(async () => { + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('when the union is 1 components', async () => { + before(async () => { + customComponents1 = [wrappedETH.address]; + customComponents2 = [wrappedETH.address]; + + customUnits1 = [gWei(1)]; + customUnits2 = [gWei(2)]; + }); + + after(async () => { + customComponents1 = undefined; + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#calculateStartPrice and calculateEndPrice', async () => { + let subjectFairValue: BigNumber; + + let combinedTokenArray: Address[]; + let combinedCurrentSetUnits: BigNumber[]; + let combinedNextSetUnits: BigNumber[]; + + let linearAuction: LinearAuction; + + beforeEach(async () => { + combinedTokenArray = [wrappedETH.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + + await boundsCalculator.parameterizeAuction.sendTransactionAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits + ); + + linearAuction = { + auction: { + pricePrecision: new BigNumber(0), + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray: [wrappedETH.address, usdc.address], + combinedCurrentSetUnits: [new BigNumber(10 ** 12), new BigNumber(128)], + combinedNextSetUnits: [new BigNumber(10 ** 12), new BigNumber(1152)], + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; + + subjectFairValue = ether(5); + }); + + async function startPriceSubject(): Promise { + return boundsCalculator.calculateStartPriceMock.callAsync( + subjectFairValue, + ); + } + + async function endPriceSubject(): Promise { + return boundsCalculator.calculateEndPriceMock.callAsync( + subjectFairValue, + ); + } + + it('calculates the correct start price value', async () => { + const result = await startPriceSubject(); + + const expectedResult = await liquidatorHelper.calculateTwoAssetStartPrice( + linearAuction, + subjectFairValue, + rangeStart, + oracleWhiteList, + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + + it('calculates the correct end price value', async () => { + const result = await endPriceSubject(); + + const expectedResult = await liquidatorHelper.calculateTwoAssetEndPrice( + linearAuction, + subjectFairValue, + rangeEnd, + oracleWhiteList, + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + }); + describe('#calculateAuctionBoundDifference', async () => { let subjectFairValue: BigNumber; let subjectRangeStart: BigNumber; diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 0ff045ec7..b1cc66ac2 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -120,8 +120,8 @@ contract('LinearAuctionLiquidator', accounts => { set1NaturalUnit, ); - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; + set2Components = [component1.address, component2.address]; + set2Units = [gWei(1), gWei(0.5)]; set2NaturalUnit = gWei(2); set2 = await coreHelper.createSetTokenAsync( core, @@ -325,7 +325,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the currentSet is > 10x nextSet', async () => { beforeEach(async () => { const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; + const setUnits = [gWei(1), gWei(2)]; const setNaturalUnit = gWei(100); const set3 = await coreHelper.createSetTokenAsync( core, @@ -340,7 +340,6 @@ contract('LinearAuctionLiquidator', accounts => { it('sets the correct pricePrecision', async () => { await subject(); - const auction: any = await liquidator.auctions.callAsync(subjectCaller); const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( @@ -408,7 +407,7 @@ contract('LinearAuctionLiquidator', accounts => { describe('when a token does not have a supported oracle', async () => { beforeEach(async () => { await oracleWhiteList.removeTokenOraclePair.sendTransactionAsync( - component3.address, + component1.address, { from: ownerAccount, gas: DEFAULT_GAS }, ); }); @@ -417,6 +416,27 @@ contract('LinearAuctionLiquidator', accounts => { await expectRevertError(subject()); }); }); + + describe('when the union of the current and next Set is not 2 components', async () => { + beforeEach(async () => { + const set3Components = [component1.address, component3.address]; + const set3Units = [gWei(1), gWei(2)]; + const set3NaturalUnit = gWei(2); + const set3 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set3Components, + set3Units, + set3NaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('[CONTEXT] Initialized auction', async () => { diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 8a446e666..46415af2f 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -247,18 +247,39 @@ export class LiquidatorHelper { fairValue: BigNumber, boundValue: BigNumber ): BigNumber { - const numerator = (combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0]))).pow(2) - .mul(assetTwoFullUnit) - .mul(boundValue) - .mul(assetPairPrice) - .div(100); - - const denominator = combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]).sub( - combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) - .mul(assetOneFullUnit) - .mul(10 ** 18); - - return numerator.div(denominator).abs(); + let numDifferential; + if (combinedNextUnitArray[0].mul(ether(1)).gt(fairValue.mul(combinedCurrentUnitArray[0]))) { + numDifferential = combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0])); + } else { + numDifferential = fairValue.mul(combinedCurrentUnitArray[0]).sub(combinedNextUnitArray[0].mul(ether(1))); + } + + let denomDifferential; + if (combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]) + .gt(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) + ) { + denomDifferential = combinedNextUnitArray[0] + .mul(combinedCurrentUnitArray[1]) + .sub(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])); + } else { + denomDifferential = combinedNextUnitArray[1] + .mul(combinedCurrentUnitArray[0]) + .sub(combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1])); + } + + const calcNumerator = assetTwoFullUnit + .mul(numDifferential) + .mul(boundValue) + .mul(assetPairPrice) + .div(100).round(0, 3); + + const calcDenominator = assetOneFullUnit.mul(denomDifferential).mul(ether(1)); + + return calcNumerator + .mul(ether(1)) + .div(calcDenominator).round(0, 3) + .mul(numDifferential) + .div(ether(1)).round(0, 3); } public calculateCurrentPrice( From 5cb751a54d54a064f9bca98c90b5039297cd57af Mon Sep 17 00:00:00 2001 From: bweick Date: Mon, 6 Jan 2020 20:58:14 -0800 Subject: [PATCH 27/35] Refactored some logic in the auction bound calculations. Changed auction bound calcs from using derivative to extrapolate auction prices to directly computing auction prices. --- .../core/liquidators/impl/LinearAuction.sol | 13 +- .../TwoAssetPriceBoundedLinearAuction.sol | 170 +++++++++++----- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 19 +- .../twoAssetPriceBoundedLinearAuction.spec.ts | 121 +++++------- .../linearAuctionLiquidator.spec.ts | 42 +--- utils/constants.ts | 2 + utils/helpers/liquidatorHelper.ts | 184 ++++++++++-------- 7 files changed, 300 insertions(+), 251 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 1526bef05..94d4da2e0 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -94,9 +94,8 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - uint256 fairValue = calculateFairValue(_currentSet, _nextSet); - _linearAuction.startPrice = calculateStartPrice(_linearAuction, fairValue); - _linearAuction.endPrice = calculateEndPrice(_linearAuction, fairValue); + _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction); + _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -147,7 +146,7 @@ contract LinearAuction is Auction { /** * Returns the linear price based on the current timestamp. Returns the endPrice - * if time has exceeded the auciton period + * if time has exceeded the auction period * * @param _linearAuction Linear Auction State object * @return price uint representing the current price @@ -189,8 +188,7 @@ contract LinearAuction is Auction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view @@ -201,8 +199,7 @@ contract LinearAuction is Auction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index da14ba246..8ba66a607 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -38,10 +38,11 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { using CommonMath for uint256; struct AssetInfo { - uint256 assetPrice; + uint256 price; uint256 fullUnit; } + uint256 constant private CURVE_DENOMINATOR = 10 ** 18; uint256 constant private ONE_HUNDRED = 100; constructor( @@ -85,79 +86,160 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view returns(uint256) { - uint256 startDifference = calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeStart + // Get full Unit amount and price for each asset + AssetInfo memory assetOne = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); + + // Calculate current asset pair spot price as assetOne/assetTwo + uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + + // Check to see if asset pair price is increasing or decreasing as time passes + bool isTokenFlowIncreasing = isTokenFlowIncreasing( + _auction, + spotPrice, + assetOne.fullUnit, + assetTwo.fullUnit ); - return _fairValueScaled.sub(startDifference); + // If price implied by token flows is increasing then target price we are using for lower bound + // is below current spot price, if flows decreasing set target price above spotPrice + uint256 startPairPrice; + if (isTokenFlowIncreasing) { + startPairPrice = spotPrice.mul(ONE_HUNDRED.sub(rangeStart)).div(ONE_HUNDRED); + } else { + startPairPrice = spotPrice.mul(ONE_HUNDRED.add(rangeStart)).div(ONE_HUNDRED); + } + + // Convert start asset pair price to equivalent auction price + return convertAssetPairPriceToAuctionPrice( + _auction, + startPairPrice, + assetOne.fullUnit, + assetTwo.fullUnit + ); } /** * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - State storage _linearAuction, - uint256 _fairValueScaled + Auction.Setup storage _auction ) internal view returns(uint256) { - uint256 endDifference = calculateAuctionBoundDifference( - _linearAuction.auction, - _fairValueScaled, - rangeEnd + // Get full Unit amount and price for each asset + AssetInfo memory assetOne = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); + + // Calculate current spot price as assetOne/assetTwo + uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + + // Check to see if asset pair price is increasing or decreasing as time passes + bool isTokenFlowIncreasing = isTokenFlowIncreasing( + _auction, + spotPrice, + assetOne.fullUnit, + assetTwo.fullUnit ); - return _fairValueScaled.add(endDifference); - } + // If price implied by token flows is increasing then target price we are using for upper bound + // is above current spot price, if flows decreasing set target price below spotPrice + uint256 endPairPrice; + if (isTokenFlowIncreasing) { + endPairPrice = spotPrice.mul(ONE_HUNDRED.add(rangeEnd)).div(ONE_HUNDRED); + } else { + endPairPrice = spotPrice.mul(ONE_HUNDRED.sub(rangeEnd)).div(ONE_HUNDRED); + } + // Convert end asset pair price to equivalent auction price + return convertAssetPairPriceToAuctionPrice( + _auction, + endPairPrice, + assetOne.fullUnit, + assetTwo.fullUnit + ); + } - function calculateAuctionBoundDifference( + function isTokenFlowIncreasing( Auction.Setup storage _auction, - uint256 _fairValue, - uint256 _boundValue + uint256 _spotPrice, + uint256 _assetOneFullUnit, + uint256 _assetTwoFullUnit ) internal view - returns (uint256) + returns (bool) { - AssetInfo memory baseAsset = getAssetInfo(_auction.combinedTokenArray[0]); - AssetInfo memory quoteAsset = getAssetInfo(_auction.combinedTokenArray[1]); - - uint256 numDifferential; - if (_auction.combinedNextSetUnits[0].scale() > _fairValue.mul(_auction.combinedCurrentSetUnits[0])) { - numDifferential = _auction.combinedNextSetUnits[0].scale().sub(_fairValue.mul(_auction.combinedCurrentSetUnits[0])); - } else { - numDifferential = _fairValue.mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].scale()); - } + // Calculate auction price at current asset pair spot price + uint256 auctionFairValue = convertAssetPairPriceToAuctionPrice( + _auction, + _spotPrice, + _assetOneFullUnit, + _assetTwoFullUnit + ); - uint256 denomDifferential; - if (_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]) > _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])) { - denomDifferential = _auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1]).sub(_auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0])); - } else { - denomDifferential = _auction.combinedNextSetUnits[1].mul(_auction.combinedCurrentSetUnits[0]).sub(_auction.combinedNextSetUnits[0].mul(_auction.combinedCurrentSetUnits[1])); - } + // Equation for assetOne net outflow is assetOneCurrentUnits*auctionPrice - assetOneNextUnits*auctionDenominator. + // Thus if assetOneNextUnits*auctionDenominator > assetOneCurrentUnits*auctionPrice then assetOne is + // an inflow. When assetOne is an inflow (negative outflow), it implies that assetTwo is an outflow, + // furthermore we are guaranteed that both flows have a positive derivative with respect to auction price (and + // auction price always increases). Since we define price as abs(assetTwoOutflow/assetOneOutflow), with assetOneFlow + // getting less negative and assetTwoFlow getting more positive it implies that the asset price is increasing as + // time passes in the auction. + return _auction.combinedNextSetUnits[0].mul(CURVE_DENOMINATOR) > + _auction.combinedCurrentSetUnits[0].mul(auctionFairValue); + } - uint256 calcNumerator = quoteAsset.fullUnit - .mul(numDifferential) - .mul(_boundValue) - .mul(baseAsset.assetPrice) - .div(quoteAsset.assetPrice) - .div(ONE_HUNDRED); + /** + * Convert an asset pair price to the equivalent auction price where a1 refers to assetOne and a2 refers to assetTwo + * and subscripts c, n, d mean currentSetUnit, nextSetUnit and fullUnit amount, respectively. aP and aD refer to auction + * price and auction denominator: + * + * assetPrice = abs(assetTwoOutflow/assetOneOutflow) + * + * assetPrice = ((a2_c/a2_d)*aP - (a2_n/a2_d)*aD) / ((a1_c/a1_d)*aP - (a1_n/a1_d)*aD) + * + * We know assetPrice so we isolate for aP: + * + * aP = aD((a2_n/a2_d)+assetPrice*(a1_n/a1_d)) / (a2_c/a2_d)+assetPrice*(a1_c/a1_d) + * + * This gives us the auction price that matches with the passed asset pair price. + */ + function convertAssetPairPriceToAuctionPrice( + Auction.Setup storage _auction, + uint256 _targetPrice, + uint256 assetOneFullUnit, + uint256 assetTwoFullUnit + ) + internal + view + returns (uint256) + { + // Calculate the numerator for the above equation. In order to ensure no rounding down errors we distribute the auction + // denominator. Additionally, since the price is passed as an 18 decimal number in order to maintain consistency we + // have to scale the first term up accordingly + uint256 calcNumerator = _auction.combinedNextSetUnits[1].mul(CURVE_DENOMINATOR).scale().div(assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedNextSetUnits[0]).mul(CURVE_DENOMINATOR).div(assetOneFullUnit) + ); - uint256 calcDenominator = baseAsset.fullUnit.mul(denomDifferential).scale(); + // Calculate the denominator for the above equation. As above we we have to scale the first term match the 18 decimal + // price. Furthermore since we are not guaranteed that targetPrice * a1_c > a1_d we have to scale the second term and + // thus also the first term in order to match (hence the two scale() in the first term) + uint256 calcDenominator = _auction.combinedCurrentSetUnits[1].scale().scale().div(assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedCurrentSetUnits[0]).scale().div(assetOneFullUnit) + ); - return calcNumerator.scale().div(calcDenominator).mul(numDifferential).deScale(); + // Here the scale required to account for the 18 decimal price cancels out since it was applied to both the numerator + // and denominator. However there was an extra scale applied to the denominator that we need to remove, in order to + // do so we'll just apply another scale to the numerator before dividing since 1/(1/10 ** 18) = 10 ** 18! + return calcNumerator.scale().div(calcDenominator); } function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { @@ -167,7 +249,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 decimals = ERC20Detailed(_asset).decimals(); return AssetInfo({ - assetPrice: assetPrice, + price: assetPrice, fullUnit: CommonMath.safePower(10, decimals) }); } diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index b77df428b..4f8ce46ac 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -54,23 +54,12 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); } - function calculateStartPriceMock(uint256 _fairValueScaled) external view returns(uint256) { - return super.calculateStartPrice(auctionInfo, _fairValueScaled); + function calculateStartPriceMock() external view returns(uint256) { + return super.calculateStartPrice(auctionInfo.auction); } - function calculateEndPriceMock(uint256 _fairValueScaled) external view returns(uint256) { - return super.calculateEndPrice(auctionInfo, _fairValueScaled); - } - - function calculateAuctionBoundDifferenceMock( - uint256 _fairValue, - uint256 _rangeStart - ) - external - view - returns (uint256) - { - return calculateAuctionBoundDifference(auctionInfo.auction, _fairValue, _rangeStart); + function calculateEndPriceMock() external view returns(uint256) { + return super.calculateEndPrice(auctionInfo.auction); } function parameterizeAuction( diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 7fc64be47..17face11b 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -31,7 +31,7 @@ import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; -const CoreMock = artifacts.require('CoreMock'); +const TwoAssetPriceBoundedLinearAuction = artifacts.require('TwoAssetPriceBoundedLinearAuction'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { const [ @@ -68,7 +68,8 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { - ABIDecoder.addABI(CoreMock.abi); + ABIDecoder.addABI(TwoAssetPriceBoundedLinearAuction.abi); + transferProxy = await coreHelper.deployTransferProxyAsync(); vault = await coreHelper.deployVaultAsync(); coreMock = await coreHelper.deployCoreMockAsync(transferProxy, vault); @@ -106,7 +107,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { - ABIDecoder.removeABI(CoreMock.abi); + ABIDecoder.removeABI(TwoAssetPriceBoundedLinearAuction.abi); }); describe('#validateTwoAssetPriceBoundedAuction', async () => { @@ -203,9 +204,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe('#calculateStartPrice and calculateEndPrice', async () => { - let subjectFairValue: BigNumber; - + describe.only('#calculateStartPrice and calculateEndPrice', async () => { let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; let combinedNextSetUnits: BigNumber[]; @@ -238,29 +237,23 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { startPrice: new BigNumber(0), endPrice: new BigNumber(0), }; - - subjectFairValue = ether(5); }); async function startPriceSubject(): Promise { - return boundsCalculator.calculateStartPriceMock.callAsync( - subjectFairValue, - ); + return boundsCalculator.calculateStartPriceMock.callAsync(); } async function endPriceSubject(): Promise { - return boundsCalculator.calculateEndPriceMock.callAsync( - subjectFairValue, - ); + return boundsCalculator.calculateEndPriceMock.callAsync(); } it('calculates the correct start price value', async () => { const result = await startPriceSubject(); - const expectedResult = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedResult, ] = await liquidatorHelper.calculateAuctionBoundsAsync( linearAuction, - subjectFairValue, rangeStart, + rangeEnd, oracleWhiteList, ); @@ -270,9 +263,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { it('calculates the correct end price value', async () => { const result = await endPriceSubject(); - const expectedResult = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedResult] = await liquidatorHelper.calculateAuctionBoundsAsync( linearAuction, - subjectFairValue, + rangeStart, rangeEnd, oracleWhiteList, ); @@ -281,9 +274,8 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe('#calculateAuctionBoundDifference', async () => { - let subjectFairValue: BigNumber; - let subjectRangeStart: BigNumber; + describe.only('#calculateAuctionBounds', async () => { + let linearAuction: LinearAuction; let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; @@ -296,37 +288,40 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); beforeEach(async () => { - combinedTokenArray = [wrappedETH.address, usdc.address]; - combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; - await boundsCalculator.parameterizeAuction.sendTransactionAsync( combinedTokenArray, combinedCurrentSetUnits, combinedNextSetUnits ); - subjectFairValue = ether(5); - subjectRangeStart = new BigNumber(3); + linearAuction = { + auction: { + pricePrecision: new BigNumber(0), + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; }); async function subject(): Promise { - return boundsCalculator.calculateAuctionBoundDifferenceMock.callAsync( - subjectFairValue, - subjectRangeStart - ); + return boundsCalculator.calculateStartPriceMock.callAsync(); } - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -340,16 +335,13 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { combinedNextSetUnits = [new BigNumber(1152), new BigNumber(10 ** 12)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -364,16 +356,13 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { combinedNextSetUnits = [new BigNumber(9 * 10 ** 12), new BigNumber(128)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -385,19 +374,16 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { before(async () => { combinedTokenArray = [usdc.address, wrappedETH.address]; combinedCurrentSetUnits = [new BigNumber(128), new BigNumber(10 ** 12)]; - combinedNextSetUnits = [new BigNumber(1152), new BigNumber(9 * 10 ** 12)]; + combinedNextSetUnits = [new BigNumber(128), new BigNumber(9 * 10 ** 12)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); @@ -408,20 +394,17 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { describe('different allocation', async () => { before(async () => { combinedTokenArray = [wrappedETH.address, usdc.address]; - combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(192)]; + combinedCurrentSetUnits = [new BigNumber(10 ** 14), new BigNumber(12800)]; + combinedNextSetUnits = [new BigNumber(10 ** 14), new BigNumber(1267200)]; }); - it('sets the correct oracleWhiteList', async () => { + it('gets the correct start bound', async () => { const actualStartBound = await subject(); const [expectedStartBound, ] = await liquidatorHelper.calculateAuctionBoundsAsync( - combinedTokenArray, - combinedCurrentSetUnits, - combinedNextSetUnits, - subjectFairValue, - subjectRangeStart, - new BigNumber(21), + linearAuction, + rangeStart, + rangeEnd, oracleWhiteList ); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index b1cc66ac2..f9ee85eb4 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -216,7 +216,7 @@ contract('LinearAuctionLiquidator', accounts => { }); }); - describe('#startRebalance', async () => { + describe.only('#startRebalance', async () => { let subjectCaller: Address; let subjectCurrentSet: Address; let subjectNextSet: Address; @@ -285,17 +285,11 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedStartPrice, ] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, rangeStart, + rangeEnd, oracleWhiteList, ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); @@ -306,16 +300,10 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedEndPrice] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, + rangeStart, rangeEnd, oracleWhiteList, ); @@ -356,17 +344,11 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeStart = await liquidator.rangeStart.callAsync(); - const expectedStartPrice = await liquidatorHelper.calculateTwoAssetStartPrice( + const [expectedStartPrice, ] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, rangeStart, + rangeEnd, oracleWhiteList, ); expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); @@ -377,16 +359,10 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); const rangeEnd = await liquidator.rangeEnd.callAsync(); - const expectedEndPrice = await liquidatorHelper.calculateTwoAssetEndPrice( + const [, expectedEndPrice] = await liquidatorHelper.calculateAuctionBoundsAsync( getLinearAuction(auction), - fairValue, + rangeStart, rangeEnd, oracleWhiteList, ); diff --git a/utils/constants.ts b/utils/constants.ts index c572e316d..22035263c 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -2,6 +2,7 @@ import { BigNumber } from 'bignumber.js'; import { ether } from '../utils/units'; export const AUCTION_TIME_INCREMENT = new BigNumber(30); // Unix seconds +export const AUCTION_CURVE_DENOMINATOR = ether(1); export const DEFAULT_AUCTION_PRICE_NUMERATOR = new BigNumber(1374); export const DEFAULT_AUCTION_PRICE_DIVISOR = new BigNumber(1000); export const DEFAULT_GAS = 19000000; @@ -17,6 +18,7 @@ export const EMPTY_BYTESTRING: string = '0x00'; export const KYBER_RESERVE_CONFIGURED_RATE: BigNumber = new BigNumber('321556325999999997'); export const NULL_ADDRESS: string = '0x0000000000000000000000000000000000000000'; 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 SCALE_FACTOR = ether(1); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 46415af2f..231df8289 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -15,14 +15,15 @@ import { } from '../contracts'; import { getContractInstance, txnFrom } from '../web3Helper'; import { - ZERO, + AUCTION_CURVE_DENOMINATOR, + ONE_HUNDRED, SCALE_FACTOR, + ZERO } from '../constants'; import { LinearAuction, TokenFlow } from '../auction'; -import { ether } from '@utils/units'; const AuctionMock = artifacts.require('AuctionMock'); const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); @@ -200,86 +201,141 @@ export class LiquidatorHelper { } public async calculateAuctionBoundsAsync( - combinedTokenArray: Address[], - combinedCurrentUnitArray: BigNumber[], - combinedNextUnitArray: BigNumber[], - fairValue: BigNumber, + linearAuction: LinearAuction, startBound: BigNumber, endBound: BigNumber, oracleWhiteList: OracleWhiteListContract ): Promise<[BigNumber, BigNumber]> { - const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync(combinedTokenArray); + const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync( + linearAuction.auction.combinedTokenArray + ); const assetOneFullUnit = new BigNumber(10 ** assetOneDecimals.toNumber()); const assetTwoFullUnit = new BigNumber(10 ** assetTwoDecimals.toNumber()); - const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync(combinedTokenArray, oracleWhiteList); + const [assetOnePrice, assetTwoPrice] = await this.getComponentPricesAsync( + linearAuction.auction.combinedTokenArray, + oracleWhiteList + ); - const startValue = this.calculateAuctionBound( - combinedCurrentUnitArray, - combinedNextUnitArray, + const startValue = this.calculateTwoAssetStartPrice( + linearAuction, assetOneFullUnit, assetTwoFullUnit, assetOnePrice.div(assetTwoPrice), - fairValue, startBound ); - const endValue = this.calculateAuctionBound( - combinedCurrentUnitArray, - combinedNextUnitArray, + const endValue = this.calculateTwoAssetEndPrice( + linearAuction, assetOneFullUnit, assetTwoFullUnit, assetOnePrice.div(assetTwoPrice), - fairValue, endBound ); return [startValue, endValue]; } - public calculateAuctionBound( - combinedCurrentUnitArray: BigNumber[], - combinedNextUnitArray: BigNumber[], + public calculateTwoAssetStartPrice( + linearAuction: LinearAuction, assetOneFullUnit: BigNumber, assetTwoFullUnit: BigNumber, assetPairPrice: BigNumber, - fairValue: BigNumber, - boundValue: BigNumber + startBound: BigNumber ): BigNumber { - let numDifferential; - if (combinedNextUnitArray[0].mul(ether(1)).gt(fairValue.mul(combinedCurrentUnitArray[0]))) { - numDifferential = combinedNextUnitArray[0].mul(ether(1)).sub(fairValue.mul(combinedCurrentUnitArray[0])); + const auctionFairValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + assetPairPrice + ); + + const tokenFlowIncreasing = this.isTokenFlowIncreasing( + linearAuction.auction.combinedCurrentSetUnits[0], + linearAuction.auction.combinedNextSetUnits[0], + auctionFairValue + ); + + let startPairPrice: BigNumber; + if (tokenFlowIncreasing) { + startPairPrice = assetPairPrice.mul(ONE_HUNDRED.sub(startBound)).div(ONE_HUNDRED); } else { - numDifferential = fairValue.mul(combinedCurrentUnitArray[0]).sub(combinedNextUnitArray[0].mul(ether(1))); + startPairPrice = assetPairPrice.mul(ONE_HUNDRED.add(startBound)).div(ONE_HUNDRED); } - let denomDifferential; - if (combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1]) - .gt(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])) - ) { - denomDifferential = combinedNextUnitArray[0] - .mul(combinedCurrentUnitArray[1]) - .sub(combinedNextUnitArray[1].mul(combinedCurrentUnitArray[0])); + const startValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + startPairPrice + ); + return startValue; + } + + public calculateTwoAssetEndPrice( + linearAuction: LinearAuction, + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + assetPairPrice: BigNumber, + endBound: BigNumber + ): BigNumber { + const auctionFairValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + assetPairPrice + ); + + const tokenFlowIncreasing = this.isTokenFlowIncreasing( + linearAuction.auction.combinedCurrentSetUnits[0], + linearAuction.auction.combinedNextSetUnits[0], + auctionFairValue + ); + + let endPairPrice: BigNumber; + if (tokenFlowIncreasing) { + endPairPrice = assetPairPrice.mul(ONE_HUNDRED.add(endBound)).div(ONE_HUNDRED); } else { - denomDifferential = combinedNextUnitArray[1] - .mul(combinedCurrentUnitArray[0]) - .sub(combinedNextUnitArray[0].mul(combinedCurrentUnitArray[1])); + endPairPrice = assetPairPrice.mul(ONE_HUNDRED.sub(endBound)).div(ONE_HUNDRED); } - const calcNumerator = assetTwoFullUnit - .mul(numDifferential) - .mul(boundValue) - .mul(assetPairPrice) - .div(100).round(0, 3); + const endValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + endPairPrice + ); + return endValue; + } - const calcDenominator = assetOneFullUnit.mul(denomDifferential).mul(ether(1)); + public calculateAuctionBound( + linearAuction: LinearAuction, + assetOneFullUnit: BigNumber, + assetTwoFullUnit: BigNumber, + targetPrice: BigNumber + ): BigNumber { + + const combinedNextUnitArray = linearAuction.auction.combinedNextSetUnits; + const combinedCurrentUnitArray = linearAuction.auction.combinedCurrentSetUnits; + + const calcNumerator = combinedNextUnitArray[1].mul(AUCTION_CURVE_DENOMINATOR).div(assetTwoFullUnit).add( + targetPrice.mul(combinedNextUnitArray[0]).mul(AUCTION_CURVE_DENOMINATOR).div(assetOneFullUnit) + ); + + const calcDenominator = combinedCurrentUnitArray[1].div(assetTwoFullUnit).add( + targetPrice.mul(combinedCurrentUnitArray[0]).div(assetOneFullUnit) + ); + + return calcNumerator.div(calcDenominator).round(0, 3); + } - return calcNumerator - .mul(ether(1)) - .div(calcDenominator).round(0, 3) - .mul(numDifferential) - .div(ether(1)).round(0, 3); + public isTokenFlowIncreasing( + assetOneCurrentUnit: BigNumber, + assetOneNextUnit: BigNumber, + fairValue: BigNumber + ): boolean { + return assetOneNextUnit.mul(AUCTION_CURVE_DENOMINATOR).greaterThan(assetOneCurrentUnit.mul(fairValue)); } public calculateCurrentPrice( @@ -294,42 +350,6 @@ export class LiquidatorHelper { return new BigNumber(linearAuction.startPrice).add(elapsedPrice); } - public async calculateTwoAssetStartPrice( - linearAuction: LinearAuction, - fairValue: BigNumber, - rangeStartPercentage: BigNumber, - oracleWhiteList: OracleWhiteListContract, - ): Promise { - const [startDifference] = await this.calculateAuctionBoundsAsync( - linearAuction.auction.combinedTokenArray, - linearAuction.auction.combinedCurrentSetUnits, - linearAuction.auction.combinedNextSetUnits, - fairValue, - rangeStartPercentage, - rangeStartPercentage, // Dummy value, unused - oracleWhiteList - ); - return fairValue.sub(startDifference); - } - - public async calculateTwoAssetEndPrice( - linearAuction: LinearAuction, - fairValue: BigNumber, - rangeEndPercentage: BigNumber, - oracleWhiteList: OracleWhiteListContract, - ): Promise { - const [, endDifference] = await this.calculateAuctionBoundsAsync( - linearAuction.auction.combinedTokenArray, - linearAuction.auction.combinedCurrentSetUnits, - linearAuction.auction.combinedNextSetUnits, - fairValue, - rangeEndPercentage, // Dummy value, unused - rangeEndPercentage, - oracleWhiteList - ); - return fairValue.add(endDifference); - } - public async calculateFairValueAsync( currentSetToken: SetTokenContract, nextSetToken: SetTokenContract, From 6a45c2a53a52b55d8ed5df5b7ba0e1e2b7140846 Mon Sep 17 00:00:00 2001 From: bweick Date: Mon, 6 Jan 2020 21:44:39 -0800 Subject: [PATCH 28/35] Remove .only --- .../impl/twoAssetPriceBoundedLinearAuction.spec.ts | 4 ++-- .../core/liquidators/linearAuctionLiquidator.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 17face11b..a73dcf8d3 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -204,7 +204,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe.only('#calculateStartPrice and calculateEndPrice', async () => { + describe('#calculateStartPrice and calculateEndPrice', async () => { let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; let combinedNextSetUnits: BigNumber[]; @@ -274,7 +274,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); - describe.only('#calculateAuctionBounds', async () => { + describe('#calculateAuctionBounds', async () => { let linearAuction: LinearAuction; let combinedTokenArray: Address[]; diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index f9ee85eb4..8f0aaf0a5 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -216,7 +216,7 @@ contract('LinearAuctionLiquidator', accounts => { }); }); - describe.only('#startRebalance', async () => { + describe('#startRebalance', async () => { let subjectCaller: Address; let subjectCurrentSet: Address; let subjectNextSet: Address; From 4f3ab7051cad2ac164f80d87f0b362cd42e947b3 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 10:58:01 -0800 Subject: [PATCH 29/35] Refactored liquidator, pushed oracleWhitelist to bounds calculation files. --- contracts/core/liquidators/impl/Auction.sol | 82 ++----------------- .../core/liquidators/impl/LinearAuction.sol | 58 ++----------- .../TwoAssetPriceBoundedLinearAuction.sol | 38 ++++++--- .../core/liquidators/impl/AuctionMock.sol | 5 -- .../liquidators/impl/LinearAuctionMock.sol | 57 ++++++++++--- .../TwoAssetPriceBoundedLinearAuctionMock.sol | 10 ++- utils/helpers/liquidatorHelper.ts | 3 +- 7 files changed, 95 insertions(+), 158 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index fb4158811..1ba12e0d3 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -24,7 +24,6 @@ import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfac import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol"; import { CommonMath } from "../../../lib/CommonMath.sol"; import { ICore } from "../../interfaces/ICore.sol"; -import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; import { SetMath } from "../../lib/SetMath.sol"; @@ -46,7 +45,6 @@ contract Auction { /* ============ Structs ============ */ struct Setup { - uint256 pricePrecision; uint256 minimumBid; uint256 startTime; uint256 startingCurrentSets; @@ -56,21 +54,6 @@ contract Auction { uint256[] combinedNextSetUnits; } - /* ============ Constants ============ */ - uint256 constant public MINIMUM_PRICE_PRECISION = 1000; - - /* ============ State Variables ============ */ - IOracleWhiteList public oracleWhiteList; - - /** - * Auction constructor - * - * @param _oracleWhiteList Oracle WhiteList instance - */ - constructor(IOracleWhiteList _oracleWhiteList) public { - oracleWhiteList = _oracleWhiteList; - } - /* ============ Auction Struct Methods ============ */ /* @@ -89,9 +72,7 @@ contract Auction { ) internal { - _auction.pricePrecision = calculatePricePrecision(_currentSet, _nextSet); - - uint256 minimumBid = calculateMinimumBid(_currentSet, _nextSet, _auction.pricePrecision); + uint256 minimumBid = calculateMinimumBid(_currentSet, _nextSet); // remainingCurrentSets must be greater than minimumBid or no bidding would be allowed require( @@ -169,7 +150,7 @@ contract Auction { returns (Rebalance.TokenFlow memory) { // Normalized quantity amount - uint256 unitsMultiplier = _quantity.div(_auction.minimumBid).mul(_auction.pricePrecision); + uint256 unitsMultiplier = _quantity.div(_auction.minimumBid); address[] memory memCombinedTokenArray = _auction.combinedTokenArray; @@ -194,46 +175,16 @@ contract Auction { return Rebalance.composeTokenFlow(memCombinedTokenArray, inflowUnitArray, outflowUnitArray); } - /** - * Calculates the price precision based on the USD values of the next and current Sets. - */ - function calculatePricePrecision( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - returns (uint256) - { - // Value the sets - uint256 currentSetUSDValue = calculateUSDValueOfSet(_currentSet); - uint256 nextSetUSDValue = calculateUSDValueOfSet(_nextSet); - - // If currentSetValue is 10x greater than nextSetValue calculate required bump in pricePrecision - if (currentSetUSDValue > nextSetUSDValue.mul(10)) { - // Round up valuation to nearest order of magnitude - uint256 orderOfMagnitude = CommonMath.ceilLog10(currentSetUSDValue.div(nextSetUSDValue)); - - // Apply order of magnitude to pricePrecision, since Log10 is rounded up subtract 1 order of - // magnitude - return MINIMUM_PRICE_PRECISION.mul(10 ** orderOfMagnitude).div(10); - } - - return MINIMUM_PRICE_PRECISION; - } - /** * Calculate the minimumBid allowed for the rebalance * * @param _currentSet The Set to rebalance from * @param _nextSet The Set to rebalance to - * @param _pricePrecision Price precision used in auction * @return Minimum bid amount */ function calculateMinimumBid( ISetToken _currentSet, - ISetToken _nextSet, - uint256 _pricePrecision + ISetToken _nextSet ) internal view @@ -241,8 +192,7 @@ contract Auction { { uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit) - .mul(_pricePrecision); + return Math.max(currentSetNaturalUnit, nextNaturalUnit); } /** @@ -357,13 +307,11 @@ contract Auction { returns (uint256[] memory) { address[] memory combinedTokenArray = _auction.combinedTokenArray; - uint256 pricePrecisionMem = _auction.pricePrecision; uint256[] memory combinedUnits = new uint256[](combinedTokenArray.length); for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, _auction.minimumBid, - pricePrecisionMem, combinedTokenArray[i] ); } @@ -376,14 +324,12 @@ contract Auction { * * @param _setToken Information on the SetToken * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, uint256 _minimumBid, - uint256 _pricePrecision, address _component ) private @@ -401,8 +347,7 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid, - _pricePrecision + _minimumBid ); } @@ -416,30 +361,17 @@ contract Auction { * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token * @param _minimumBid Minimum bid amount - * @param _pricePrecision Price precision used in auction * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid, - uint256 _pricePrecision + uint256 _minimumBid ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit) - .div(_pricePrecision); - } - - /** - * Calculate USD value of passed Set - * - * @param _set Instance of SetToken - * @return USDValue USD Value of the Set Token - */ - function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { - return SetUSDValuation.calculateSetTokenDollarValue(_set, oracleWhiteList); + return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); } } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 94d4da2e0..c5567b835 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -20,7 +20,6 @@ pragma experimental "ABIEncoderV2"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { Auction } from "./Auction.sol"; -import { IOracleWhiteList } from "../../interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; @@ -44,29 +43,18 @@ contract LinearAuction is Auction { /* ============ State Variables ============ */ uint256 public auctionPeriod; // Length in seconds of auction - uint256 public rangeStart; // Percentage below FairValue to begin auction at - uint256 public rangeEnd; // Percentage above FairValue to end auction at /** * LinearAuction constructor * - * @param _oracleWhiteList Oracle WhiteList instance * @param _auctionPeriod Length of auction - * @param _rangeStart Percentage below FairValue to begin auction at - * @param _rangeEnd Percentage above FairValue to end auction at */ constructor( - IOracleWhiteList _oracleWhiteList, - uint256 _auctionPeriod, - uint256 _rangeStart, - uint256 _rangeEnd + uint256 _auctionPeriod ) public - Auction(_oracleWhiteList) { auctionPeriod = _auctionPeriod; - rangeStart = _rangeStart; - rangeEnd = _rangeEnd; } /* ============ Internal Functions ============ */ @@ -94,27 +82,13 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction); - _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction); + _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); + _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); _linearAuction.endTime = block.timestamp.add(auctionPeriod); } /* ============ Internal View Functions ============ */ - function validateRebalanceComponents( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - { - address[] memory combinedTokenArray = Auction.getCombinedTokenArray(_currentSet, _nextSet); - require( - oracleWhiteList.areValidAddresses(combinedTokenArray), - "LinearAuction.validateRebalanceComponents: Passed token does not have matching oracle." - ); - } - /** * Returns the TokenFlow based on the current price */ @@ -165,30 +139,14 @@ contract LinearAuction is Auction { } } - /** - * Calculates the fair value based on the USD values of the next and current Sets. - * Returns a scaled value - */ - function calculateFairValue( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - returns (uint256) - { - uint256 currentSetUSDValue = Auction.calculateUSDValueOfSet(_currentSet); - uint256 nextSetUSDValue = Auction.calculateUSDValueOfSet(_nextSet); - - return nextSetUSDValue.scale().div(currentSetUSDValue); - } - /** * Abstract function that must be implemented. * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view @@ -199,7 +157,9 @@ contract LinearAuction is Auction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 8ba66a607..78c561cc9 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -45,6 +45,17 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 constant private CURVE_DENOMINATOR = 10 ** 18; uint256 constant private ONE_HUNDRED = 100; + IOracleWhiteList public oracleWhiteList; + uint256 public rangeStart; // Percentage below FairValue to begin auction at + uint256 public rangeEnd; // Percentage above FairValue to end auction at + + /** + * LinearAuction constructor + * + * @param _auctionPeriod Length of auction + * @param _rangeStart Percentage below FairValue to begin auction at + * @param _rangeEnd Percentage above FairValue to end auction at + */ constructor( IOracleWhiteList _oracleWhiteList, uint256 _auctionPeriod, @@ -52,13 +63,12 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 _rangeEnd ) public - LinearAuction( - _oracleWhiteList, - _auctionPeriod, - _rangeStart, - _rangeEnd - ) - {} + LinearAuction(_auctionPeriod) + { + oracleWhiteList = _oracleWhiteList; + rangeStart = _rangeStart; + rangeEnd = _rangeEnd; + } /** * Validates that the auction only includes two components and the components are valid. @@ -76,9 +86,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { "TwoAssetPriceBoundedLinearAuction: Only two components are allowed." ); - LinearAuction.validateRebalanceComponents( - _currentSet, - _nextSet + require( + oracleWhiteList.areValidAddresses(combinedTokenArray), + "TwoAssetPriceBoundedLinearAuction: Passed token does not have matching oracle." ); } @@ -86,7 +96,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction start price with a scaled value */ function calculateStartPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view @@ -129,7 +141,9 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * Calculates the linear auction end price with a scaled value */ function calculateEndPrice( - Auction.Setup storage _auction + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet ) internal view diff --git a/contracts/mocks/core/liquidators/impl/AuctionMock.sol b/contracts/mocks/core/liquidators/impl/AuctionMock.sol index 705a8146f..81d75d93f 100644 --- a/contracts/mocks/core/liquidators/impl/AuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/AuctionMock.sol @@ -8,11 +8,6 @@ import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.s contract AuctionMock is Auction { Auction.Setup public auction; - constructor(IOracleWhiteList _oracleWhiteList) - public - Auction(_oracleWhiteList) - {} - function initializeAuction( ISetToken _currentSet, ISetToken _nextSet, diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index db67331b5..6b55a0741 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -10,6 +10,10 @@ import { SetUSDValuation } from "../../../../core/liquidators/impl/SetUSDValuati contract LinearAuctionMock is LinearAuction { LinearAuction.State public auction; + IOracleWhiteList public oracleWhiteList; + + uint256 public rangeStart; // Percentage below FairValue to begin auction at + uint256 public rangeEnd; // Percentage above FairValue to end auction at constructor( IOracleWhiteList _oracleWhiteList, @@ -19,35 +23,58 @@ contract LinearAuctionMock is LinearAuction { ) public LinearAuction( - _oracleWhiteList, - _auctionPeriod, - _rangeStart, - _rangeEnd + _auctionPeriod ) - {} + { + oracleWhiteList = _oracleWhiteList; + rangeStart = _rangeStart; + rangeEnd = _rangeEnd; + } function calculateStartPrice( State storage _linearAuction, - uint256 _fairValueScaled + ISetToken _currentSet, + ISetToken _nextSet ) internal view returns(uint256) { - uint256 startRange = _fairValueScaled.mul(rangeStart).div(100); - return _fairValueScaled.sub(startRange); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + uint256 startRange = fairValue.mul(rangeStart).div(100); + return fairValue.sub(startRange); } function calculateEndPrice( State storage _linearAuction, - uint256 _fairValueScaled + ISetToken _currentSet, + ISetToken _nextSet ) internal view returns(uint256) { - uint256 endRange = _fairValueScaled.mul(rangeEnd).div(100); - return _fairValueScaled.add(endRange); + uint256 fairValue = calculateFairValue(_currentSet, _nextSet); + uint256 endRange = fairValue.mul(rangeEnd).div(100); + return fairValue.add(endRange); + } + + /** + * Calculates the fair value based on the USD values of the next and current Sets. + * Returns a scaled value + */ + function calculateFairValue( + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256) + { + uint256 currentSetUSDValue = calculateUSDValueOfSet(_currentSet); + uint256 nextSetUSDValue = calculateUSDValueOfSet(_nextSet); + + return nextSetUSDValue.scale().div(currentSetUSDValue); } function initializeLinearAuction( @@ -82,8 +109,14 @@ contract LinearAuctionMock is LinearAuction { return super.getTokenFlow(auction, _quantity); } + /** + * Calculate USD value of passed Set + * + * @param _set Instance of SetToken + * @return USDValue USD Value of the Set Token + */ function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { - return super.calculateUSDValueOfSet(_set); + return SetUSDValuation.calculateSetTokenDollarValue(_set, oracleWhiteList); } } diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 4f8ce46ac..9a0d46926 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -54,12 +54,16 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); } - function calculateStartPriceMock() external view returns(uint256) { - return super.calculateStartPrice(auctionInfo.auction); + function calculateStartPriceMock() external view returns(uint256) { + ISetToken currentSet = ISetToken(address(0)); + ISetToken nextSet = ISetToken(address(0)); + return super.calculateStartPrice(auctionInfo.auction, currentSet, nextSet); } function calculateEndPriceMock() external view returns(uint256) { - return super.calculateEndPrice(auctionInfo.auction); + ISetToken currentSet = ISetToken(address(0)); + ISetToken nextSet = ISetToken(address(0)); + return super.calculateEndPrice(auctionInfo.auction, currentSet, nextSet); } function parameterizeAuction( diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 231df8289..ce0032e35 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -54,10 +54,9 @@ export class LiquidatorHelper { /* ============ Deployment ============ */ public async deployAuctionMockAsync( - oracleWhiteList: Address, from: Address = this._contractOwnerAddress ): Promise { - const auctionMock = await AuctionMock.new(oracleWhiteList, txnFrom(from)); + const auctionMock = await AuctionMock.new(txnFrom(from)); return new AuctionMockContract(getContractInstance(auctionMock), txnFrom(from)); } From 072bbc561ff1d06e3791488dc3c97b7973432232 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 11:58:21 -0800 Subject: [PATCH 30/35] Updated tests for refactor. --- .../core/liquidators/impl/auction.spec.ts | 1086 +++++++-------- .../liquidators/impl/linearAuction.spec.ts | 1172 ++++++++--------- .../twoAssetPriceBoundedLinearAuction.spec.ts | 44 +- .../linearAuctionLiquidator.spec.ts | 40 +- utils/auction.ts | 3 - utils/helpers/liquidatorHelper.ts | 4 +- 6 files changed, 1175 insertions(+), 1174 deletions(-) diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index 19a50bf31..6b5ccb9dc 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -1,549 +1,549 @@ -require('module-alias/register'); - -import * as ABIDecoder from 'abi-decoder'; -import * as _ from 'lodash'; -import * as chai from 'chai'; -import { BigNumber } from 'bignumber.js'; -import { Address } from 'set-protocol-utils'; - -import ChaiSetup from '@utils/chaiSetup'; -import { BigNumberSetup } from '@utils/bigNumberSetup'; -import { - CoreContract, - SetTokenContract, - SetTokenFactoryContract, - StandardTokenMockContract, - AuctionMockContract, - OracleWhiteListContract, - TransferProxyContract, - UpdatableOracleMockContract, - VaultContract, -} from '@utils/contracts'; -import { expectRevertError } from '@utils/tokenAssertions'; -import { Blockchain } from '@utils/blockchain'; -import { getWeb3 } from '@utils/web3Helper'; -import { - DEFAULT_GAS, -} from '@utils/constants'; -import { ether, gWei } from '@utils/units'; - -import { CoreHelper } from '@utils/helpers/coreHelper'; -import { ERC20Helper } from '@utils/helpers/erc20Helper'; -import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -BigNumberSetup.configure(); -ChaiSetup.configure(); -const web3 = getWeb3(); -const { expect } = chai; -const blockchain = new Blockchain(web3); -const Core = artifacts.require('Core'); -const AuctionMock = artifacts.require('AuctionMock'); - -contract('Auction', accounts => { - const [ - ownerAccount, - functionCaller, - ] = accounts; - - let core: CoreContract; - let transferProxy: TransferProxyContract; - let vault: VaultContract; - let setTokenFactory: SetTokenFactoryContract; - let auctionMock: AuctionMockContract; - let oracleWhiteList: OracleWhiteListContract; - - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); - - let component1: StandardTokenMockContract; - let component2: StandardTokenMockContract; - let component3: StandardTokenMockContract; - - let component1Price: BigNumber; - let component2Price: BigNumber; - let component3Price: BigNumber; - - let component1Oracle: UpdatableOracleMockContract; - let component2Oracle: UpdatableOracleMockContract; - let component3Oracle: UpdatableOracleMockContract; - - let set1: SetTokenContract; - let set2: SetTokenContract; - - let set1Components: Address[]; - let set2Components: Address[]; - - let set1Units: BigNumber[]; - let set2Units: BigNumber[]; - - let set1NaturalUnit: BigNumber; - let set2NaturalUnit: BigNumber; - - before(async () => { - ABIDecoder.addABI(Core.abi); - ABIDecoder.addABI(AuctionMock.abi); - - transferProxy = await coreHelper.deployTransferProxyAsync(); - vault = await coreHelper.deployVaultAsync(); - core = await coreHelper.deployCoreAsync(transferProxy, vault); - - setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - - await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - - component1 = await erc20Helper.deployTokenAsync(ownerAccount); - component2 = await erc20Helper.deployTokenAsync(ownerAccount); - component3 = await erc20Helper.deployTokenAsync(ownerAccount); - - component1Price = ether(1); - component2Price = ether(2); - component3Price = ether(1); - - component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); - component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); - component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - - oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( - [component1.address, component2.address, component3.address], - [component1Oracle.address, component2Oracle.address, component3Oracle.address], - ); - - set1Components = [component1.address, component2.address]; - set1Units = [gWei(1), gWei(1)]; - set1NaturalUnit = gWei(2); - set1 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set1Components, - set1Units, - set1NaturalUnit, - ); - - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; - set2NaturalUnit = gWei(1); - set2 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set2Components, - set2Units, - set2NaturalUnit, - ); - }); - - after(async () => { - ABIDecoder.removeABI(Core.abi); - ABIDecoder.removeABI(AuctionMock.abi); - }); - - beforeEach(async () => { - await blockchain.saveSnapshotAsync(); - }); - - afterEach(async () => { - await blockchain.revertAsync(); - }); - - describe('#constructor', async () => { - let subjectWhiteList: Address; - - beforeEach(async () => { - subjectWhiteList = oracleWhiteList.address; - }); +// require('module-alias/register'); + +// import * as ABIDecoder from 'abi-decoder'; +// import * as _ from 'lodash'; +// import * as chai from 'chai'; +// import { BigNumber } from 'bignumber.js'; +// import { Address } from 'set-protocol-utils'; + +// import ChaiSetup from '@utils/chaiSetup'; +// import { BigNumberSetup } from '@utils/bigNumberSetup'; +// import { +// CoreContract, +// SetTokenContract, +// SetTokenFactoryContract, +// StandardTokenMockContract, +// AuctionMockContract, +// OracleWhiteListContract, +// TransferProxyContract, +// UpdatableOracleMockContract, +// VaultContract, +// } from '@utils/contracts'; +// import { expectRevertError } from '@utils/tokenAssertions'; +// import { Blockchain } from '@utils/blockchain'; +// import { getWeb3 } from '@utils/web3Helper'; +// import { +// DEFAULT_GAS, +// } from '@utils/constants'; +// import { ether, gWei } from '@utils/units'; + +// import { CoreHelper } from '@utils/helpers/coreHelper'; +// import { ERC20Helper } from '@utils/helpers/erc20Helper'; +// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +// BigNumberSetup.configure(); +// ChaiSetup.configure(); +// const web3 = getWeb3(); +// const { expect } = chai; +// const blockchain = new Blockchain(web3); +// const Core = artifacts.require('Core'); +// const AuctionMock = artifacts.require('AuctionMock'); + +// contract('Auction', accounts => { +// const [ +// ownerAccount, +// functionCaller, +// ] = accounts; + +// let core: CoreContract; +// let transferProxy: TransferProxyContract; +// let vault: VaultContract; +// let setTokenFactory: SetTokenFactoryContract; +// let auctionMock: AuctionMockContract; +// let oracleWhiteList: OracleWhiteListContract; + +// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); +// const erc20Helper = new ERC20Helper(ownerAccount); +// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); +// const libraryMockHelper = new LibraryMockHelper(ownerAccount); + +// let component1: StandardTokenMockContract; +// let component2: StandardTokenMockContract; +// let component3: StandardTokenMockContract; + +// let component1Price: BigNumber; +// let component2Price: BigNumber; +// let component3Price: BigNumber; + +// let component1Oracle: UpdatableOracleMockContract; +// let component2Oracle: UpdatableOracleMockContract; +// let component3Oracle: UpdatableOracleMockContract; + +// let set1: SetTokenContract; +// let set2: SetTokenContract; + +// let set1Components: Address[]; +// let set2Components: Address[]; + +// let set1Units: BigNumber[]; +// let set2Units: BigNumber[]; + +// let set1NaturalUnit: BigNumber; +// let set2NaturalUnit: BigNumber; + +// before(async () => { +// ABIDecoder.addABI(Core.abi); +// ABIDecoder.addABI(AuctionMock.abi); + +// transferProxy = await coreHelper.deployTransferProxyAsync(); +// vault = await coreHelper.deployVaultAsync(); +// core = await coreHelper.deployCoreAsync(transferProxy, vault); + +// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + +// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + +// component1 = await erc20Helper.deployTokenAsync(ownerAccount); +// component2 = await erc20Helper.deployTokenAsync(ownerAccount); +// component3 = await erc20Helper.deployTokenAsync(ownerAccount); + +// component1Price = ether(1); +// component2Price = ether(2); +// component3Price = ether(1); + +// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); +// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); +// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + +// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( +// [component1.address, component2.address, component3.address], +// [component1Oracle.address, component2Oracle.address, component3Oracle.address], +// ); + +// set1Components = [component1.address, component2.address]; +// set1Units = [gWei(1), gWei(1)]; +// set1NaturalUnit = gWei(2); +// set1 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set1Components, +// set1Units, +// set1NaturalUnit, +// ); + +// set2Components = [component2.address, component3.address]; +// set2Units = [gWei(1), gWei(1)]; +// set2NaturalUnit = gWei(1); +// set2 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set2Components, +// set2Units, +// set2NaturalUnit, +// ); +// }); + +// after(async () => { +// ABIDecoder.removeABI(Core.abi); +// ABIDecoder.removeABI(AuctionMock.abi); +// }); + +// beforeEach(async () => { +// await blockchain.saveSnapshotAsync(); +// }); + +// afterEach(async () => { +// await blockchain.revertAsync(); +// }); + +// describe('#constructor', async () => { +// let subjectWhiteList: Address; + +// beforeEach(async () => { +// subjectWhiteList = oracleWhiteList.address; +// }); - async function subject(): Promise { - return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); - } +// async function subject(): Promise { +// return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); +// } - it('sets the correct oracleWhiteList', async () => { - auctionMock = await subject(); +// it('sets the correct oracleWhiteList', async () => { +// auctionMock = await subject(); - const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); - expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); - }); - }); +// const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); +// expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); +// }); +// }); - describe('#initializeAuction', async () => { - let subjectCaller: Address; - let subjectCurrentSet: Address; - let subjectNextSet: Address; - let subjectStartingCurrentSetQuantity: BigNumber; +// describe('#initializeAuction', async () => { +// let subjectCaller: Address; +// let subjectCurrentSet: Address; +// let subjectNextSet: Address; +// let subjectStartingCurrentSetQuantity: BigNumber; - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - subjectCaller = functionCaller; - subjectCurrentSet = set1.address; - subjectNextSet = set2.address; - subjectStartingCurrentSetQuantity = ether(10); - }); - - after(async () => { - }); - - async function subject(): Promise { - return auctionMock.initializeAuction.sendTransactionAsync( - subjectCurrentSet, - subjectNextSet, - subjectStartingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - set1, - set2, - oracleWhiteList - ); - - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct minimumBid', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const pricePrecision = auctionSetup.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); - expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); - }); - - it('sets the correct startTime', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const { timestamp } = await web3.eth.getBlock('latest'); - expect(auctionSetup.startTime).to.bignumber.equal(timestamp); - }); - - it('sets the correct startingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); - }); - - it('sets the correct remainingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); - }); - - it('sets the correct combinedTokenArray', async () => { - await subject(); - - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const expectedResult = _.union(set1Components, set2Components); - - expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); - }); - - it('sets the correct combinedCurrentSetUnits', async () => { - await subject(); - - const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); - - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( - set1, - combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision - ); - - expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); - }); - - it('sets the correct combinedNextSetUnits', async () => { - await subject(); - - const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); - const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( - set2, - combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), - auctionSetup.pricePrecision - ); - - expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); - }); - - describe('when currentSet is greater than 10x the nextSet', async () => { - beforeEach(async () => { - const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; - const setNaturalUnit = gWei(300); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - setComponents, - setUnits, - setNaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - }); - - describe('when currentSet is 1-2x greater than nextSet', async () => { - beforeEach(async () => { - const set3Components = [component1.address, component3.address]; - const set3Units = [gWei(1), gWei(1)]; - const set3NaturalUnit = gWei(2); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set3Components, - set3Units, - set3NaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - }); - - describe('when there is insufficient collateral to rebalance', async () => { - beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(10); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#reduceRemainingCurrentSets', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - let subjectReductionQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectReductionQuantity = ether(5); - }); - - async function subject(): Promise { - return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - subjectReductionQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('calculates the correct new remainingCurrentSets', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); - expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); - }); - }); - - describe('#validateBidQuantity', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - let subjectQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectQuantity = ether(5); - }); - - async function subject(): Promise { - return auctionMock.validateBidQuantity.sendTransactionAsync( - subjectQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('does not revert', async () => { - await subject(); - }); - - describe('when the quantity is not a multiple of the minimumBid', async () => { - beforeEach(async () => { - const auctionSetup: any = await auctionMock.auction.callAsync(); - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(auctionSetup.pricePrecision) - .div(2); - subjectQuantity = gWei(10).plus(halfMinimumBid); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - - describe('when the quantity is more than the remainingCurrentsets', async () => { - beforeEach(async () => { - subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#validateAuctionCompletion', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - let reductionQuantity: BigNumber; - let customReductionQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - reductionQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - async function subject(): Promise { - return auctionMock.validateAuctionCompletion.sendTransactionAsync( - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('should not revert', async () => { - await subject(); - }); - - describe('when the auction is not complete', async () => { - before(async () => { - customReductionQuantity = startingCurrentSetQuantity.div(2); - }); - - after(async () => { - customReductionQuantity = undefined; - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); - }); - - describe('#isAuctionActive', async () => { - let subjectCaller: Address; - let startingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - - subjectCaller = functionCaller; - }); - - async function subject(): Promise { - return auctionMock.isAuctionActive.callAsync( - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('should return false', async () => { - const result = await subject(); - expect(result).to.equal(false); - }); - - describe('when the auction has begun', async () => { - beforeEach(async () => { - startingCurrentSetQuantity = ether(10); - - await blockchain.increaseTimeAsync(new BigNumber(1000)); - - await auctionMock.initializeAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return true', async () => { - const result = await subject(); - expect(result).to.equal(true); - }); - }); - }); -}); \ No newline at end of file +// subjectCaller = functionCaller; +// subjectCurrentSet = set1.address; +// subjectNextSet = set2.address; +// subjectStartingCurrentSetQuantity = ether(10); +// }); + +// after(async () => { +// }); + +// async function subject(): Promise { +// return auctionMock.initializeAuction.sendTransactionAsync( +// subjectCurrentSet, +// subjectNextSet, +// subjectStartingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// set1, +// set2, +// oracleWhiteList +// ); + +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct minimumBid', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const pricePrecision = auctionSetup.pricePrecision; +// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); +// expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); +// }); + +// it('sets the correct startTime', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// expect(auctionSetup.startTime).to.bignumber.equal(timestamp); +// }); + +// it('sets the correct startingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); +// }); + +// it('sets the correct remainingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); +// }); + +// it('sets the correct combinedTokenArray', async () => { +// await subject(); + +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const expectedResult = _.union(set1Components, set2Components); + +// expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); +// }); + +// it('sets the correct combinedCurrentSetUnits', async () => { +// await subject(); + +// const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); + +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( +// set1, +// combinedTokenArray, +// new BigNumber(auctionSetup.minimumBid), +// auctionSetup.pricePrecision +// ); + +// expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); +// }); + +// it('sets the correct combinedNextSetUnits', async () => { +// await subject(); + +// const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); +// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( +// set2, +// combinedTokenArray, +// new BigNumber(auctionSetup.minimumBid), +// auctionSetup.pricePrecision +// ); + +// expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); +// }); + +// describe('when currentSet is greater than 10x the nextSet', async () => { +// beforeEach(async () => { +// const setComponents = [component1.address, component2.address]; +// const setUnits = [gWei(1), gWei(1)]; +// const setNaturalUnit = gWei(300); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// setComponents, +// setUnits, +// setNaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); +// }); + +// describe('when currentSet is 1-2x greater than nextSet', async () => { +// beforeEach(async () => { +// const set3Components = [component1.address, component3.address]; +// const set3Units = [gWei(1), gWei(1)]; +// const set3NaturalUnit = gWei(2); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set3Components, +// set3Units, +// set3NaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); +// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); +// }); + +// describe('when there is insufficient collateral to rebalance', async () => { +// beforeEach(async () => { +// subjectStartingCurrentSetQuantity = gWei(10); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#reduceRemainingCurrentSets', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// let subjectReductionQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectReductionQuantity = ether(5); +// }); + +// async function subject(): Promise { +// return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// subjectReductionQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('calculates the correct new remainingCurrentSets', async () => { +// await subject(); + +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); +// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); +// }); +// }); + +// describe('#validateBidQuantity', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// let subjectQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectQuantity = ether(5); +// }); + +// async function subject(): Promise { +// return auctionMock.validateBidQuantity.sendTransactionAsync( +// subjectQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('does not revert', async () => { +// await subject(); +// }); + +// describe('when the quantity is not a multiple of the minimumBid', async () => { +// beforeEach(async () => { +// const auctionSetup: any = await auctionMock.auction.callAsync(); +// const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) +// .mul(auctionSetup.pricePrecision) +// .div(2); +// subjectQuantity = gWei(10).plus(halfMinimumBid); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); + +// describe('when the quantity is more than the remainingCurrentsets', async () => { +// beforeEach(async () => { +// subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#validateAuctionCompletion', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; +// let reductionQuantity: BigNumber; +// let customReductionQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// reductionQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// async function subject(): Promise { +// return auctionMock.validateAuctionCompletion.sendTransactionAsync( +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('should not revert', async () => { +// await subject(); +// }); + +// describe('when the auction is not complete', async () => { +// before(async () => { +// customReductionQuantity = startingCurrentSetQuantity.div(2); +// }); + +// after(async () => { +// customReductionQuantity = undefined; +// }); + +// it('should revert', async () => { +// await expectRevertError(subject()); +// }); +// }); +// }); + +// describe('#isAuctionActive', async () => { +// let subjectCaller: Address; +// let startingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + +// subjectCaller = functionCaller; +// }); + +// async function subject(): Promise { +// return auctionMock.isAuctionActive.callAsync( +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('should return false', async () => { +// const result = await subject(); +// expect(result).to.equal(false); +// }); + +// describe('when the auction has begun', async () => { +// beforeEach(async () => { +// startingCurrentSetQuantity = ether(10); + +// await blockchain.increaseTimeAsync(new BigNumber(1000)); + +// await auctionMock.initializeAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return true', async () => { +// const result = await subject(); +// expect(result).to.equal(true); +// }); +// }); +// }); +// }); \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index f7bfa03d8..03cfa8f85 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -1,586 +1,586 @@ -require('module-alias/register'); - -import * as ABIDecoder from 'abi-decoder'; -import * as _ from 'lodash'; -import * as chai from 'chai'; -import { BigNumber } from 'bignumber.js'; -import { Address } from 'set-protocol-utils'; - -import ChaiSetup from '@utils/chaiSetup'; -import { BigNumberSetup } from '@utils/bigNumberSetup'; -import { - CoreContract, - OracleWhiteListContract, - SetTokenContract, - SetTokenFactoryContract, - StandardTokenMockContract, - LinearAuctionMockContract, - TransferProxyContract, - UpdatableOracleMockContract, - VaultContract, -} from '@utils/contracts'; -import { Blockchain } from '@utils/blockchain'; -import { getWeb3 } from '@utils/web3Helper'; -import { - DEFAULT_GAS, - ONE_DAY_IN_SECONDS, -} from '@utils/constants'; -import { ether, gWei } from '@utils/units'; -import { getLinearAuction, TokenFlow } from '@utils/auction'; - -import { CoreHelper } from '@utils/helpers/coreHelper'; -import { ERC20Helper } from '@utils/helpers/erc20Helper'; -import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -BigNumberSetup.configure(); -ChaiSetup.configure(); -const web3 = getWeb3(); -const { expect } = chai; -const blockchain = new Blockchain(web3); -const Core = artifacts.require('Core'); -const LinearAuctionMock = artifacts.require('LinearAuctionMock'); - -contract('LinearAuction', accounts => { - const [ - ownerAccount, - functionCaller, - ] = accounts; - - let core: CoreContract; - let transferProxy: TransferProxyContract; - let vault: VaultContract; - let setTokenFactory: SetTokenFactoryContract; - let auctionMock: LinearAuctionMockContract; - - const coreHelper = new CoreHelper(ownerAccount, ownerAccount); - const erc20Helper = new ERC20Helper(ownerAccount); - const libraryMockHelper = new LibraryMockHelper(ownerAccount); - const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - - let auctionPeriod: BigNumber; - let rangeStart: BigNumber; - let rangeEnd: BigNumber; - let oracleWhiteList: OracleWhiteListContract; - - let component1: StandardTokenMockContract; - let component2: StandardTokenMockContract; - let component3: StandardTokenMockContract; - - let component1Price: BigNumber; - let component2Price: BigNumber; - let component3Price: BigNumber; - - let set1: SetTokenContract; - let set2: SetTokenContract; - - let set1Components: Address[]; - let set2Components: Address[]; - - let set1Units: BigNumber[]; - let set2Units: BigNumber[]; - - let set1NaturalUnit: BigNumber; - let set2NaturalUnit: BigNumber; - - let component1Oracle: UpdatableOracleMockContract; - let component2Oracle: UpdatableOracleMockContract; - let component3Oracle: UpdatableOracleMockContract; - - before(async () => { - ABIDecoder.addABI(Core.abi); - ABIDecoder.addABI(LinearAuctionMock.abi); - - transferProxy = await coreHelper.deployTransferProxyAsync(); - vault = await coreHelper.deployVaultAsync(); - core = await coreHelper.deployCoreAsync(transferProxy, vault); - - setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - - component1 = await erc20Helper.deployTokenAsync(ownerAccount); - component2 = await erc20Helper.deployTokenAsync(ownerAccount); - component3 = await erc20Helper.deployTokenAsync(ownerAccount); - - set1Components = [component1.address, component2.address]; - set1Units = [gWei(1), gWei(1)]; - set1NaturalUnit = gWei(1); - set1 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set1Components, - set1Units, - set1NaturalUnit, - ); - - set2Components = [component2.address, component3.address]; - set2Units = [gWei(1), gWei(1)]; - set2NaturalUnit = gWei(2); - set2 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - set2Components, - set2Units, - set2NaturalUnit, - ); - - component1Price = ether(1); - component2Price = ether(2); - component3Price = ether(1); - - component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); - component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); - component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - - oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( - [component1.address, component2.address, component3.address], - [component1Oracle.address, component2Oracle.address, component3Oracle.address], - ); - - auctionPeriod = ONE_DAY_IN_SECONDS; - rangeStart = new BigNumber(10); // 10% above fair value - rangeEnd = new BigNumber(10); // 10% below fair value - - auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( - oracleWhiteList.address, - auctionPeriod, - rangeStart, - rangeEnd, - ); - - }); - - after(async () => { - ABIDecoder.removeABI(Core.abi); - ABIDecoder.removeABI(LinearAuctionMock.abi); - }); - - beforeEach(async () => { - await blockchain.saveSnapshotAsync(); - }); - - afterEach(async () => { - await blockchain.revertAsync(); - }); - - describe('#constructor', async () => { - it('sets the correct auctionPeriod', async () => { - const result = await auctionMock.auctionPeriod.callAsync(); - expect(result).to.bignumber.equal(auctionPeriod); - }); - - it('sets the correct rangeStart', async () => { - const result = await auctionMock.rangeStart.callAsync(); - expect(result).to.bignumber.equal(rangeStart); - }); - - it('sets the correct rangeEnd', async () => { - const result = await auctionMock.rangeEnd.callAsync(); - expect(result).to.bignumber.equal(rangeEnd); - }); - }); - - describe('#initializeLinearAuction', async () => { - let subjectCaller: Address; - let subjectCurrentSet: Address; - let subjectNextSet: Address; - let subjectStartingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - subjectCaller = functionCaller; - subjectCurrentSet = set1.address; - subjectNextSet = set2.address; - subjectStartingCurrentSetQuantity = ether(10); - }); - - after(async () => { - }); - - async function subject(): Promise { - return auctionMock.initializeLinearAuction.sendTransactionAsync( - subjectCurrentSet, - subjectNextSet, - subjectStartingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - } - - it('sets the correct minimumBid', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); - expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); - }); - - it('sets the correct endTime', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); - - const { timestamp } = await web3.eth.getBlock('latest'); - const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); - expect(auction.endTime).to.bignumber.equal(expectedEndTime); - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct startPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeStart = await auctionMock.rangeStart.callAsync(); - - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - const expectedStartPrice = fairValue.sub(negativeRange); - expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); - }); - - it('sets the correct endPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - set1, - set2, - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - const expectedEndPrice = fairValue.add(positiveRange); - expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); - }); - - describe('when currentSet is greater than 10x the nextSet', async () => { - beforeEach(async () => { - const setComponents = [component1.address, component2.address]; - const setUnits = [gWei(1), gWei(1)]; - const setNaturalUnit = gWei(101); - const set3 = await coreHelper.createSetTokenAsync( - core, - setTokenFactory.address, - setComponents, - setUnits, - setNaturalUnit, - ); - - subjectNextSet = set3.address; - }); - - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - - it('sets the correct startPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeStart = await auctionMock.rangeStart.callAsync(); - const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); - const expectedStartPrice = fairValue.sub(negativeRange); - - expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); - }); - - it('sets the correct endPrice', async () => { - await subject(); - - const auction: any = await auctionMock.auction.callAsync(); - - const fairValue = await liquidatorHelper.calculateFairValueAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList, - auction.auction.pricePrecision, - ); - const rangeEnd = await auctionMock.rangeEnd.callAsync(); - const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); - const expectedEndPrice = fairValue.add(positiveRange); - - expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); - }); - }); - }); - - describe('[CONTEXT] Initialized auction', async () => { - let subjectCaller: Address; - - let startingCurrentSetQuantity: BigNumber; - - beforeEach(async () => { - subjectCaller = functionCaller; - startingCurrentSetQuantity = ether(10); - - await auctionMock.initializeLinearAuction.sendTransactionAsync( - set1.address, - set2.address, - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - describe('#getPrice', async () => { - async function subject(): Promise { - return auctionMock.getPrice.callAsync(); - } - - it('returns the correct result', async () => { - const result = await subject(); - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(result).to.bignumber.equal(currentPrice); - }); - - describe('when the auction has elapsed half the period', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.div(2)); - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('returns the correct price', async () => { - const result = await subject(); - - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(result).to.bignumber.equal(currentPrice); - }); - }); - - describe('when the timestamp has exceeded the endTime', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(100)); - - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('returns the correct price / endPrice', async () => { - const result = await subject(); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - expect(result).to.bignumber.equal(linearAuction.endPrice); - }); - }); - }); - - describe('#getPrice', async () => { - async function subject(): Promise { - return auctionMock.getPrice.callAsync(); - } - - it('returns the correct numerator', async () => { - const numerator = await subject(); - const { timestamp } = await web3.eth.getBlock('latest'); - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - expect(numerator).to.bignumber.equal(currentPrice); - }); - }); - - describe('#getTokenFlow', async () => { - let subjectQuantity: BigNumber; - - let tokenFlows: TokenFlow; - - beforeEach(async () => { - subjectQuantity = startingCurrentSetQuantity; - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const { timestamp } = await web3.eth.getBlock('latest'); - - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - - tokenFlows = liquidatorHelper.constructTokenFlow( - linearAuction, - linearAuction.auction.pricePrecision, - subjectQuantity, - currentPrice, - ); - }); - - async function subject(): Promise { - return auctionMock.getTokenFlow.callAsync(subjectQuantity); - } - - it('returns the token array', async () => { - const { addresses } = await subject(); - expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); - }); - - it('returns the correct inflow', async () => { - const { inflow } = await subject(); - expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); - }); - - it('returns the correct outflow', async () => { - const { outflow } = await subject(); - expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); - }); - - describe('when the auction has elapsed half the period', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.div(2)); - // Do dummy transaction to advance the block - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity.div(2), - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - - subjectQuantity = startingCurrentSetQuantity.div(2); - - const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); - const { timestamp } = await web3.eth.getBlock('latest'); - - const currentPrice = await liquidatorHelper.calculateCurrentPrice( - linearAuction, - new BigNumber(timestamp), - auctionPeriod, - ); - - tokenFlows = liquidatorHelper.constructTokenFlow( - linearAuction, - linearAuction.auction.pricePrecision, - subjectQuantity, - currentPrice, - ); - }); - - it('returns the token array', async () => { - const { addresses } = await subject(); - expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); - }); - - it('returns the correct inflow', async () => { - const { inflow } = await subject(); - expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); - }); - - it('returns the correct outflow', async () => { - const { outflow } = await subject(); - expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); - }); - }); - }); - - describe('#hasAuctionFailed', async () => { - async function subject(): Promise { - return auctionMock.hasAuctionFailed.callAsync(); - } - - it('returns false', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - - describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return true', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - }); - - describe('when the auction has been completed', async () => { - beforeEach(async () => { - await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( - startingCurrentSetQuantity, - { from: subjectCaller, gas: DEFAULT_GAS }, - ); - }); - - it('should return false', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.false; - }); - }); - - describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { - beforeEach(async () => { - await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - - await blockchain.mineBlockAsync(); - }); - - it('should return true', async () => { - const hasAuctionFailed = await subject(); - expect(hasAuctionFailed).to.be.true; - }); - }); - }); - }); -}); \ No newline at end of file +// require('module-alias/register'); + +// import * as ABIDecoder from 'abi-decoder'; +// import * as _ from 'lodash'; +// import * as chai from 'chai'; +// import { BigNumber } from 'bignumber.js'; +// import { Address } from 'set-protocol-utils'; + +// import ChaiSetup from '@utils/chaiSetup'; +// import { BigNumberSetup } from '@utils/bigNumberSetup'; +// import { +// CoreContract, +// OracleWhiteListContract, +// SetTokenContract, +// SetTokenFactoryContract, +// StandardTokenMockContract, +// LinearAuctionMockContract, +// TransferProxyContract, +// UpdatableOracleMockContract, +// VaultContract, +// } from '@utils/contracts'; +// import { Blockchain } from '@utils/blockchain'; +// import { getWeb3 } from '@utils/web3Helper'; +// import { +// DEFAULT_GAS, +// ONE_DAY_IN_SECONDS, +// } from '@utils/constants'; +// import { ether, gWei } from '@utils/units'; +// import { getLinearAuction, TokenFlow } from '@utils/auction'; + +// import { CoreHelper } from '@utils/helpers/coreHelper'; +// import { ERC20Helper } from '@utils/helpers/erc20Helper'; +// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +// BigNumberSetup.configure(); +// ChaiSetup.configure(); +// const web3 = getWeb3(); +// const { expect } = chai; +// const blockchain = new Blockchain(web3); +// const Core = artifacts.require('Core'); +// const LinearAuctionMock = artifacts.require('LinearAuctionMock'); + +// contract('LinearAuction', accounts => { +// const [ +// ownerAccount, +// functionCaller, +// ] = accounts; + +// let core: CoreContract; +// let transferProxy: TransferProxyContract; +// let vault: VaultContract; +// let setTokenFactory: SetTokenFactoryContract; +// let auctionMock: LinearAuctionMockContract; + +// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); +// const erc20Helper = new ERC20Helper(ownerAccount); +// const libraryMockHelper = new LibraryMockHelper(ownerAccount); +// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + +// let auctionPeriod: BigNumber; +// let rangeStart: BigNumber; +// let rangeEnd: BigNumber; +// let oracleWhiteList: OracleWhiteListContract; + +// let component1: StandardTokenMockContract; +// let component2: StandardTokenMockContract; +// let component3: StandardTokenMockContract; + +// let component1Price: BigNumber; +// let component2Price: BigNumber; +// let component3Price: BigNumber; + +// let set1: SetTokenContract; +// let set2: SetTokenContract; + +// let set1Components: Address[]; +// let set2Components: Address[]; + +// let set1Units: BigNumber[]; +// let set2Units: BigNumber[]; + +// let set1NaturalUnit: BigNumber; +// let set2NaturalUnit: BigNumber; + +// let component1Oracle: UpdatableOracleMockContract; +// let component2Oracle: UpdatableOracleMockContract; +// let component3Oracle: UpdatableOracleMockContract; + +// before(async () => { +// ABIDecoder.addABI(Core.abi); +// ABIDecoder.addABI(LinearAuctionMock.abi); + +// transferProxy = await coreHelper.deployTransferProxyAsync(); +// vault = await coreHelper.deployVaultAsync(); +// core = await coreHelper.deployCoreAsync(transferProxy, vault); + +// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); +// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + +// component1 = await erc20Helper.deployTokenAsync(ownerAccount); +// component2 = await erc20Helper.deployTokenAsync(ownerAccount); +// component3 = await erc20Helper.deployTokenAsync(ownerAccount); + +// set1Components = [component1.address, component2.address]; +// set1Units = [gWei(1), gWei(1)]; +// set1NaturalUnit = gWei(1); +// set1 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set1Components, +// set1Units, +// set1NaturalUnit, +// ); + +// set2Components = [component2.address, component3.address]; +// set2Units = [gWei(1), gWei(1)]; +// set2NaturalUnit = gWei(2); +// set2 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// set2Components, +// set2Units, +// set2NaturalUnit, +// ); + +// component1Price = ether(1); +// component2Price = ether(2); +// component3Price = ether(1); + +// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); +// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); +// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + +// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( +// [component1.address, component2.address, component3.address], +// [component1Oracle.address, component2Oracle.address, component3Oracle.address], +// ); + +// auctionPeriod = ONE_DAY_IN_SECONDS; +// rangeStart = new BigNumber(10); // 10% above fair value +// rangeEnd = new BigNumber(10); // 10% below fair value + +// auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( +// oracleWhiteList.address, +// auctionPeriod, +// rangeStart, +// rangeEnd, +// ); + +// }); + +// after(async () => { +// ABIDecoder.removeABI(Core.abi); +// ABIDecoder.removeABI(LinearAuctionMock.abi); +// }); + +// beforeEach(async () => { +// await blockchain.saveSnapshotAsync(); +// }); + +// afterEach(async () => { +// await blockchain.revertAsync(); +// }); + +// describe('#constructor', async () => { +// it('sets the correct auctionPeriod', async () => { +// const result = await auctionMock.auctionPeriod.callAsync(); +// expect(result).to.bignumber.equal(auctionPeriod); +// }); + +// it('sets the correct rangeStart', async () => { +// const result = await auctionMock.rangeStart.callAsync(); +// expect(result).to.bignumber.equal(rangeStart); +// }); + +// it('sets the correct rangeEnd', async () => { +// const result = await auctionMock.rangeEnd.callAsync(); +// expect(result).to.bignumber.equal(rangeEnd); +// }); +// }); + +// describe('#initializeLinearAuction', async () => { +// let subjectCaller: Address; +// let subjectCurrentSet: Address; +// let subjectNextSet: Address; +// let subjectStartingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// subjectCaller = functionCaller; +// subjectCurrentSet = set1.address; +// subjectNextSet = set2.address; +// subjectStartingCurrentSetQuantity = ether(10); +// }); + +// after(async () => { +// }); + +// async function subject(): Promise { +// return auctionMock.initializeLinearAuction.sendTransactionAsync( +// subjectCurrentSet, +// subjectNextSet, +// subjectStartingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// } + +// it('sets the correct minimumBid', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const pricePrecision = auction.auction.pricePrecision; +// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) +// .mul(pricePrecision); +// expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); +// }); + +// it('sets the correct endTime', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); +// const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); +// expect(auction.endTime).to.bignumber.equal(expectedEndTime); +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); + +// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct startPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// set1, +// set2, +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeStart = await auctionMock.rangeStart.callAsync(); + +// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); +// const expectedStartPrice = fairValue.sub(negativeRange); +// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); +// }); + +// it('sets the correct endPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// set1, +// set2, +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeEnd = await auctionMock.rangeEnd.callAsync(); +// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); +// const expectedEndPrice = fairValue.add(positiveRange); +// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); +// }); + +// describe('when currentSet is greater than 10x the nextSet', async () => { +// beforeEach(async () => { +// const setComponents = [component1.address, component2.address]; +// const setUnits = [gWei(1), gWei(1)]; +// const setNaturalUnit = gWei(101); +// const set3 = await coreHelper.createSetTokenAsync( +// core, +// setTokenFactory.address, +// setComponents, +// setUnits, +// setNaturalUnit, +// ); + +// subjectNextSet = set3.address; +// }); + +// it('sets the correct pricePrecision', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList +// ); + +// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); +// }); + +// it('sets the correct startPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeStart = await auctionMock.rangeStart.callAsync(); +// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); +// const expectedStartPrice = fairValue.sub(negativeRange); + +// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); +// }); + +// it('sets the correct endPrice', async () => { +// await subject(); + +// const auction: any = await auctionMock.auction.callAsync(); + +// const fairValue = await liquidatorHelper.calculateFairValueAsync( +// await coreHelper.getSetInstance(subjectCurrentSet), +// await coreHelper.getSetInstance(subjectNextSet), +// oracleWhiteList, +// auction.auction.pricePrecision, +// ); +// const rangeEnd = await auctionMock.rangeEnd.callAsync(); +// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); +// const expectedEndPrice = fairValue.add(positiveRange); + +// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); +// }); +// }); +// }); + +// describe('[CONTEXT] Initialized auction', async () => { +// let subjectCaller: Address; + +// let startingCurrentSetQuantity: BigNumber; + +// beforeEach(async () => { +// subjectCaller = functionCaller; +// startingCurrentSetQuantity = ether(10); + +// await auctionMock.initializeLinearAuction.sendTransactionAsync( +// set1.address, +// set2.address, +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// describe('#getPrice', async () => { +// async function subject(): Promise { +// return auctionMock.getPrice.callAsync(); +// } + +// it('returns the correct result', async () => { +// const result = await subject(); +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(result).to.bignumber.equal(currentPrice); +// }); + +// describe('when the auction has elapsed half the period', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('returns the correct price', async () => { +// const result = await subject(); + +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(result).to.bignumber.equal(currentPrice); +// }); +// }); + +// describe('when the timestamp has exceeded the endTime', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(100)); + +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('returns the correct price / endPrice', async () => { +// const result = await subject(); + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// expect(result).to.bignumber.equal(linearAuction.endPrice); +// }); +// }); +// }); + +// describe('#getPrice', async () => { +// async function subject(): Promise { +// return auctionMock.getPrice.callAsync(); +// } + +// it('returns the correct numerator', async () => { +// const numerator = await subject(); +// const { timestamp } = await web3.eth.getBlock('latest'); +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); +// expect(numerator).to.bignumber.equal(currentPrice); +// }); +// }); + +// describe('#getTokenFlow', async () => { +// let subjectQuantity: BigNumber; + +// let tokenFlows: TokenFlow; + +// beforeEach(async () => { +// subjectQuantity = startingCurrentSetQuantity; + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const { timestamp } = await web3.eth.getBlock('latest'); + +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); + +// tokenFlows = liquidatorHelper.constructTokenFlow( +// linearAuction, +// linearAuction.auction.pricePrecision, +// subjectQuantity, +// currentPrice, +// ); +// }); + +// async function subject(): Promise { +// return auctionMock.getTokenFlow.callAsync(subjectQuantity); +// } + +// it('returns the token array', async () => { +// const { addresses } = await subject(); +// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); +// }); + +// it('returns the correct inflow', async () => { +// const { inflow } = await subject(); +// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); +// }); + +// it('returns the correct outflow', async () => { +// const { outflow } = await subject(); +// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); +// }); + +// describe('when the auction has elapsed half the period', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); +// // Do dummy transaction to advance the block +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity.div(2), +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); + +// subjectQuantity = startingCurrentSetQuantity.div(2); + +// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); +// const { timestamp } = await web3.eth.getBlock('latest'); + +// const currentPrice = await liquidatorHelper.calculateCurrentPrice( +// linearAuction, +// new BigNumber(timestamp), +// auctionPeriod, +// ); + +// tokenFlows = liquidatorHelper.constructTokenFlow( +// linearAuction, +// linearAuction.auction.pricePrecision, +// subjectQuantity, +// currentPrice, +// ); +// }); + +// it('returns the token array', async () => { +// const { addresses } = await subject(); +// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); +// }); + +// it('returns the correct inflow', async () => { +// const { inflow } = await subject(); +// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); +// }); + +// it('returns the correct outflow', async () => { +// const { outflow } = await subject(); +// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); +// }); +// }); +// }); + +// describe('#hasAuctionFailed', async () => { +// async function subject(): Promise { +// return auctionMock.hasAuctionFailed.callAsync(); +// } + +// it('returns false', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); + +// describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return true', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); +// }); + +// describe('when the auction has been completed', async () => { +// beforeEach(async () => { +// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( +// startingCurrentSetQuantity, +// { from: subjectCaller, gas: DEFAULT_GAS }, +// ); +// }); + +// it('should return false', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.false; +// }); +// }); + +// describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { +// beforeEach(async () => { +// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + +// await blockchain.mineBlockAsync(); +// }); + +// it('should return true', async () => { +// const hasAuctionFailed = await subject(); +// expect(hasAuctionFailed).to.be.true; +// }); +// }); +// }); +// }); +// }); \ No newline at end of file diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index a73dcf8d3..68ed00873 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -31,6 +31,7 @@ import { expectRevertError } from '@utils/tokenAssertions'; BigNumberSetup.configure(); ChaiSetup.configure(); const { expect } = chai; +const Core = artifacts.require('Core'); const TwoAssetPriceBoundedLinearAuction = artifacts.require('TwoAssetPriceBoundedLinearAuction'); contract('TwoAssetPriceBoundedLinearAuction', accounts => { @@ -68,6 +69,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let rangeEnd: BigNumber; before(async () => { + ABIDecoder.addABI(Core.abi); ABIDecoder.addABI(TwoAssetPriceBoundedLinearAuction.abi); transferProxy = await coreHelper.deployTransferProxyAsync(); @@ -107,9 +109,32 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { + ABIDecoder.removeABI(Core.abi); ABIDecoder.removeABI(TwoAssetPriceBoundedLinearAuction.abi); }); + describe('#constructor', async () => { + it('sets the correct auctionPeriod', async () => { + const result = await boundsCalculator.auctionPeriod.callAsync(); + expect(result).to.bignumber.equal(auctionPeriod); + }); + + it('sets the correct rangeStart', async () => { + const result = await boundsCalculator.rangeStart.callAsync(); + expect(result).to.bignumber.equal(rangeStart); + }); + + it('sets the correct rangeEnd', async () => { + const result = await boundsCalculator.rangeEnd.callAsync(); + expect(result).to.bignumber.equal(rangeEnd); + }); + + it('sets the correct oracleWhiteList', async () => { + const result = await boundsCalculator.oracleWhiteList.callAsync(); + expect(result).to.equal(oracleWhiteList.address); + }); + }); + describe('#validateTwoAssetPriceBoundedAuction', async () => { let set1: SetTokenContract; let set2: SetTokenContract; @@ -202,6 +227,23 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { await expectRevertError(subject()); }); }); + + describe('when a passed token does not have matching oracle', async () => { + before(async () => { + const nonOracleComponent = await erc20Helper.deployTokenAsync(deployerAccount, 6); + + customComponents2 = [wrappedETH.address, nonOracleComponent.address]; + }); + + after(async () => { + customComponents1 = undefined; + customComponents2 = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('#calculateStartPrice and calculateEndPrice', async () => { @@ -224,7 +266,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { - pricePrecision: new BigNumber(0), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), @@ -296,7 +337,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { - pricePrecision: new BigNumber(0), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 8f0aaf0a5..f91615607 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -249,9 +249,8 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const pricePrecision = auction.auction.pricePrecision; - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -266,20 +265,6 @@ contract('LinearAuctionLiquidator', accounts => { expect(auction.endTime).to.bignumber.equal(expectedEndTime); }); - it('sets the correct pricePrecision', async () => { - await subject(); - - const auction: any = await liquidator.auctions.callAsync(subjectCaller); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - set1, - set2, - oracleWhiteList, - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - it('sets the correct startPrice', async () => { await subject(); @@ -326,19 +311,6 @@ contract('LinearAuctionLiquidator', accounts => { subjectNextSet = set3.address; }); - it('sets the correct pricePrecision', async () => { - await subject(); - const auction: any = await liquidator.auctions.callAsync(subjectCaller); - - const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( - await coreHelper.getSetInstance(subjectCurrentSet), - await coreHelper.getSetInstance(subjectNextSet), - oracleWhiteList - ); - - expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); - }); - it('sets the correct startPrice', async () => { await subject(); @@ -467,7 +439,6 @@ contract('LinearAuctionLiquidator', accounts => { tokenFlows = liquidatorHelper.constructTokenFlow( linearAuction, - linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, ); @@ -507,12 +478,8 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { - const auction: any = await liquidator.auctions.callAsync(liquidatorProxy.address); + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); - const pricePrecision = auction.auction.pricePrecision; - const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) - .mul(pricePrecision) - .div(2); subjectQuantity = gWei(10).plus(halfMinimumBid); }); @@ -571,7 +538,6 @@ contract('LinearAuctionLiquidator', accounts => { tokenFlows = liquidatorHelper.constructTokenFlow( linearAuction, - linearAuction.auction.pricePrecision, subjectQuantity, currentPrice, ); diff --git a/utils/auction.ts b/utils/auction.ts index 319e9e0dd..4f3cc92f0 100644 --- a/utils/auction.ts +++ b/utils/auction.ts @@ -23,7 +23,6 @@ export interface TokenFlow { } export interface Auction { - pricePrecision: BigNumber; minimumBid: BigNumber; startTime: BigNumber; startingCurrentSets: BigNumber; @@ -35,7 +34,6 @@ export interface Auction { export function getLinearAuction(input: any): LinearAuction { const { - pricePrecision, minimumBid, startTime, startingCurrentSets, @@ -46,7 +44,6 @@ export function getLinearAuction(input: any): LinearAuction { return { auction: { - pricePrecision: new BigNumber(pricePrecision), minimumBid: new BigNumber(minimumBid), startTime: new BigNumber(startTime), startingCurrentSets: new BigNumber(startingCurrentSets), diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index ce0032e35..7fbd1b868 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -353,7 +353,6 @@ export class LiquidatorHelper { currentSetToken: SetTokenContract, nextSetToken: SetTokenContract, oracleWhiteList: OracleWhiteListContract, - pricePrecision: BigNumber, from: Address = this._contractOwnerAddress, ): Promise { const currentSetUSDValue = await this.calculateSetTokenValueAsync(currentSetToken, oracleWhiteList); @@ -437,7 +436,6 @@ export class LiquidatorHelper { public constructTokenFlow( linearAuction: LinearAuction, - pricePrecision: BigNumber, quantity: BigNumber, priceScaled: BigNumber, ): TokenFlow { @@ -452,7 +450,7 @@ export class LiquidatorHelper { minimumBid, } = linearAuction.auction; - const unitsMultiplier = quantity.div(minimumBid).round(0, 3).mul(pricePrecision); + const unitsMultiplier = quantity.div(minimumBid).round(0, 3); for (let i = 0; i < combinedCurrentSetUnits.length; i++) { const flow = combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled)); From 6690d8e9b0d3dce8d23aad40c583119a5e4beaf5 Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Tue, 7 Jan 2020 13:07:50 -0800 Subject: [PATCH 31/35] [Liquidator] Add auction and linearAuction tests (#562) * Fix * uncomment * Push * Fix test --- .../liquidators/impl/LinearAuctionMock.sol | 4 +- .../core/liquidators/impl/auction.spec.ts | 961 ++++++-------- .../liquidators/impl/linearAuction.spec.ts | 1136 ++++++++--------- .../twoAssetPriceBoundedLinearAuction.spec.ts | 4 +- .../linearAuctionLiquidator.spec.ts | 2 - utils/helpers/liquidatorHelper.ts | 24 +- 6 files changed, 974 insertions(+), 1157 deletions(-) diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index 6b55a0741..a506d99c0 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -32,7 +32,7 @@ contract LinearAuctionMock is LinearAuction { } function calculateStartPrice( - State storage _linearAuction, + Auction.Setup storage _auction, ISetToken _currentSet, ISetToken _nextSet ) @@ -46,7 +46,7 @@ contract LinearAuctionMock is LinearAuction { } function calculateEndPrice( - State storage _linearAuction, + Auction.Setup storage _auction, ISetToken _currentSet, ISetToken _nextSet ) diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index 6b5ccb9dc..cadca2ccf 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -1,549 +1,424 @@ -// require('module-alias/register'); - -// import * as ABIDecoder from 'abi-decoder'; -// import * as _ from 'lodash'; -// import * as chai from 'chai'; -// import { BigNumber } from 'bignumber.js'; -// import { Address } from 'set-protocol-utils'; - -// import ChaiSetup from '@utils/chaiSetup'; -// import { BigNumberSetup } from '@utils/bigNumberSetup'; -// import { -// CoreContract, -// SetTokenContract, -// SetTokenFactoryContract, -// StandardTokenMockContract, -// AuctionMockContract, -// OracleWhiteListContract, -// TransferProxyContract, -// UpdatableOracleMockContract, -// VaultContract, -// } from '@utils/contracts'; -// import { expectRevertError } from '@utils/tokenAssertions'; -// import { Blockchain } from '@utils/blockchain'; -// import { getWeb3 } from '@utils/web3Helper'; -// import { -// DEFAULT_GAS, -// } from '@utils/constants'; -// import { ether, gWei } from '@utils/units'; - -// import { CoreHelper } from '@utils/helpers/coreHelper'; -// import { ERC20Helper } from '@utils/helpers/erc20Helper'; -// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -// BigNumberSetup.configure(); -// ChaiSetup.configure(); -// const web3 = getWeb3(); -// const { expect } = chai; -// const blockchain = new Blockchain(web3); -// const Core = artifacts.require('Core'); -// const AuctionMock = artifacts.require('AuctionMock'); - -// contract('Auction', accounts => { -// const [ -// ownerAccount, -// functionCaller, -// ] = accounts; - -// let core: CoreContract; -// let transferProxy: TransferProxyContract; -// let vault: VaultContract; -// let setTokenFactory: SetTokenFactoryContract; -// let auctionMock: AuctionMockContract; -// let oracleWhiteList: OracleWhiteListContract; - -// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); -// const erc20Helper = new ERC20Helper(ownerAccount); -// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); -// const libraryMockHelper = new LibraryMockHelper(ownerAccount); - -// let component1: StandardTokenMockContract; -// let component2: StandardTokenMockContract; -// let component3: StandardTokenMockContract; - -// let component1Price: BigNumber; -// let component2Price: BigNumber; -// let component3Price: BigNumber; - -// let component1Oracle: UpdatableOracleMockContract; -// let component2Oracle: UpdatableOracleMockContract; -// let component3Oracle: UpdatableOracleMockContract; - -// let set1: SetTokenContract; -// let set2: SetTokenContract; - -// let set1Components: Address[]; -// let set2Components: Address[]; - -// let set1Units: BigNumber[]; -// let set2Units: BigNumber[]; - -// let set1NaturalUnit: BigNumber; -// let set2NaturalUnit: BigNumber; - -// before(async () => { -// ABIDecoder.addABI(Core.abi); -// ABIDecoder.addABI(AuctionMock.abi); - -// transferProxy = await coreHelper.deployTransferProxyAsync(); -// vault = await coreHelper.deployVaultAsync(); -// core = await coreHelper.deployCoreAsync(transferProxy, vault); - -// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); - -// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - -// component1 = await erc20Helper.deployTokenAsync(ownerAccount); -// component2 = await erc20Helper.deployTokenAsync(ownerAccount); -// component3 = await erc20Helper.deployTokenAsync(ownerAccount); - -// component1Price = ether(1); -// component2Price = ether(2); -// component3Price = ether(1); - -// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); -// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); -// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - -// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( -// [component1.address, component2.address, component3.address], -// [component1Oracle.address, component2Oracle.address, component3Oracle.address], -// ); - -// set1Components = [component1.address, component2.address]; -// set1Units = [gWei(1), gWei(1)]; -// set1NaturalUnit = gWei(2); -// set1 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set1Components, -// set1Units, -// set1NaturalUnit, -// ); - -// set2Components = [component2.address, component3.address]; -// set2Units = [gWei(1), gWei(1)]; -// set2NaturalUnit = gWei(1); -// set2 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set2Components, -// set2Units, -// set2NaturalUnit, -// ); -// }); - -// after(async () => { -// ABIDecoder.removeABI(Core.abi); -// ABIDecoder.removeABI(AuctionMock.abi); -// }); - -// beforeEach(async () => { -// await blockchain.saveSnapshotAsync(); -// }); - -// afterEach(async () => { -// await blockchain.revertAsync(); -// }); - -// describe('#constructor', async () => { -// let subjectWhiteList: Address; - -// beforeEach(async () => { -// subjectWhiteList = oracleWhiteList.address; -// }); +require('module-alias/register'); + +import * as ABIDecoder from 'abi-decoder'; +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + CoreContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, + AuctionMockContract, + TransferProxyContract, + VaultContract, +} from '@utils/contracts'; +import { expectRevertError } from '@utils/tokenAssertions'; +import { Blockchain } from '@utils/blockchain'; +import { getWeb3 } from '@utils/web3Helper'; +import { + DEFAULT_GAS, +} from '@utils/constants'; +import { ether, gWei } from '@utils/units'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const web3 = getWeb3(); +const { expect } = chai; +const blockchain = new Blockchain(web3); +const Core = artifacts.require('Core'); +const AuctionMock = artifacts.require('AuctionMock'); + +contract('Auction', accounts => { + const [ + ownerAccount, + functionCaller, + ] = accounts; + + let core: CoreContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let auctionMock: AuctionMockContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + + let component1: StandardTokenMockContract; + let component2: StandardTokenMockContract; + let component3: StandardTokenMockContract; + + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + before(async () => { + ABIDecoder.addABI(Core.abi); + ABIDecoder.addABI(AuctionMock.abi); + + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + core = await coreHelper.deployCoreAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + + await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + + component1 = await erc20Helper.deployTokenAsync(ownerAccount); + component2 = await erc20Helper.deployTokenAsync(ownerAccount); + component3 = await erc20Helper.deployTokenAsync(ownerAccount); + + set1Components = [component1.address, component2.address]; + set1Units = [gWei(1), gWei(1)]; + set1NaturalUnit = gWei(2); + set1 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = [component2.address, component3.address]; + set2Units = [gWei(1), gWei(1)]; + set2NaturalUnit = gWei(1); + set2 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + }); + + after(async () => { + ABIDecoder.removeABI(Core.abi); + ABIDecoder.removeABI(AuctionMock.abi); + }); + + beforeEach(async () => { + await blockchain.saveSnapshotAsync(); + }); + + afterEach(async () => { + await blockchain.revertAsync(); + }); + + describe('#initializeAuction', async () => { + let subjectCaller: Address; + let subjectCurrentSet: Address; + let subjectNextSet: Address; + let subjectStartingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + subjectStartingCurrentSetQuantity = ether(10); + }); + + after(async () => { + }); + + async function subject(): Promise { + return auctionMock.initializeAuction.sendTransactionAsync( + subjectCurrentSet, + subjectNextSet, + subjectStartingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } -// async function subject(): Promise { -// return liquidatorHelper.deployAuctionMockAsync(subjectWhiteList); -// } + it('sets the correct minimumBid', async () => { + await subject(); -// it('sets the correct oracleWhiteList', async () => { -// auctionMock = await subject(); + const auctionSetup: any = await auctionMock.auction.callAsync(); -// const actualOracleWhiteList = await auctionMock.oracleWhiteList.callAsync(); -// expect(actualOracleWhiteList).to.bignumber.equal(oracleWhiteList.address); -// }); -// }); + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); + }); -// describe('#initializeAuction', async () => { -// let subjectCaller: Address; -// let subjectCurrentSet: Address; -// let subjectNextSet: Address; -// let subjectStartingCurrentSetQuantity: BigNumber; + it('sets the correct startTime', async () => { + await subject(); -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const { timestamp } = await web3.eth.getBlock('latest'); + expect(auctionSetup.startTime).to.bignumber.equal(timestamp); + }); -// subjectCaller = functionCaller; -// subjectCurrentSet = set1.address; -// subjectNextSet = set2.address; -// subjectStartingCurrentSetQuantity = ether(10); -// }); - -// after(async () => { -// }); - -// async function subject(): Promise { -// return auctionMock.initializeAuction.sendTransactionAsync( -// subjectCurrentSet, -// subjectNextSet, -// subjectStartingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// set1, -// set2, -// oracleWhiteList -// ); - -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct minimumBid', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const pricePrecision = auctionSetup.pricePrecision; -// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).mul(pricePrecision); -// expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); -// }); - -// it('sets the correct startTime', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// expect(auctionSetup.startTime).to.bignumber.equal(timestamp); -// }); - -// it('sets the correct startingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); -// }); - -// it('sets the correct remainingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); -// }); - -// it('sets the correct combinedTokenArray', async () => { -// await subject(); - -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const expectedResult = _.union(set1Components, set2Components); - -// expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); -// }); - -// it('sets the correct combinedCurrentSetUnits', async () => { -// await subject(); - -// const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); - -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( -// set1, -// combinedTokenArray, -// new BigNumber(auctionSetup.minimumBid), -// auctionSetup.pricePrecision -// ); - -// expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); -// }); - -// it('sets the correct combinedNextSetUnits', async () => { -// await subject(); - -// const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); -// const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( -// set2, -// combinedTokenArray, -// new BigNumber(auctionSetup.minimumBid), -// auctionSetup.pricePrecision -// ); - -// expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); -// }); - -// describe('when currentSet is greater than 10x the nextSet', async () => { -// beforeEach(async () => { -// const setComponents = [component1.address, component2.address]; -// const setUnits = [gWei(1), gWei(1)]; -// const setNaturalUnit = gWei(300); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// setComponents, -// setUnits, -// setNaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); -// }); - -// describe('when currentSet is 1-2x greater than nextSet', async () => { -// beforeEach(async () => { -// const set3Components = [component1.address, component3.address]; -// const set3Units = [gWei(1), gWei(1)]; -// const set3NaturalUnit = gWei(2); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set3Components, -// set3Units, -// set3NaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); -// expect(auctionSetup.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); -// }); - -// describe('when there is insufficient collateral to rebalance', async () => { -// beforeEach(async () => { -// subjectStartingCurrentSetQuantity = gWei(10); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#reduceRemainingCurrentSets', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// let subjectReductionQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectReductionQuantity = ether(5); -// }); - -// async function subject(): Promise { -// return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// subjectReductionQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('calculates the correct new remainingCurrentSets', async () => { -// await subject(); - -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); -// expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); -// }); -// }); - -// describe('#validateBidQuantity', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// let subjectQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectQuantity = ether(5); -// }); - -// async function subject(): Promise { -// return auctionMock.validateBidQuantity.sendTransactionAsync( -// subjectQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('does not revert', async () => { -// await subject(); -// }); - -// describe('when the quantity is not a multiple of the minimumBid', async () => { -// beforeEach(async () => { -// const auctionSetup: any = await auctionMock.auction.callAsync(); -// const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) -// .mul(auctionSetup.pricePrecision) -// .div(2); -// subjectQuantity = gWei(10).plus(halfMinimumBid); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); - -// describe('when the quantity is more than the remainingCurrentsets', async () => { -// beforeEach(async () => { -// subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#validateAuctionCompletion', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; -// let reductionQuantity: BigNumber; -// let customReductionQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// reductionQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// async function subject(): Promise { -// return auctionMock.validateAuctionCompletion.sendTransactionAsync( -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('should not revert', async () => { -// await subject(); -// }); - -// describe('when the auction is not complete', async () => { -// before(async () => { -// customReductionQuantity = startingCurrentSetQuantity.div(2); -// }); - -// after(async () => { -// customReductionQuantity = undefined; -// }); - -// it('should revert', async () => { -// await expectRevertError(subject()); -// }); -// }); -// }); - -// describe('#isAuctionActive', async () => { -// let subjectCaller: Address; -// let startingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// auctionMock = await liquidatorHelper.deployAuctionMockAsync(oracleWhiteList.address); - -// subjectCaller = functionCaller; -// }); - -// async function subject(): Promise { -// return auctionMock.isAuctionActive.callAsync( -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('should return false', async () => { -// const result = await subject(); -// expect(result).to.equal(false); -// }); - -// describe('when the auction has begun', async () => { -// beforeEach(async () => { -// startingCurrentSetQuantity = ether(10); - -// await blockchain.increaseTimeAsync(new BigNumber(1000)); - -// await auctionMock.initializeAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return true', async () => { -// const result = await subject(); -// expect(result).to.equal(true); -// }); -// }); -// }); -// }); \ No newline at end of file + it('sets the correct startingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + expect(auctionSetup.startingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); + }); + + it('sets the correct remainingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(subjectStartingCurrentSetQuantity); + }); + + it('sets the correct combinedTokenArray', async () => { + await subject(); + + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const expectedResult = _.union(set1Components, set2Components); + + expect(JSON.stringify(combinedTokenArray)).to.equal(JSON.stringify(expectedResult)); + }); + + it('sets the correct combinedCurrentSetUnits', async () => { + await subject(); + + const combinedCurrentSetUnits = await auctionMock.combinedCurrentSetUnits.callAsync(); + + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( + set1, + combinedTokenArray, + new BigNumber(auctionSetup.minimumBid), + ); + + expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); + }); + + it('sets the correct combinedNextSetUnits', async () => { + await subject(); + + const combinedNextSetUnits = await auctionMock.combinedNextSetUnits.callAsync(); + const combinedTokenArray = await auctionMock.combinedTokenArray.callAsync(); + const auctionSetup: any = await auctionMock.auction.callAsync(); + + const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( + set2, + combinedTokenArray, + new BigNumber(auctionSetup.minimumBid), + ); + + expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); + }); + + describe('when there is insufficient collateral to rebalance', async () => { + beforeEach(async () => { + subjectStartingCurrentSetQuantity = gWei(0.5); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#reduceRemainingCurrentSets', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + let subjectReductionQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectReductionQuantity = ether(5); + }); + + async function subject(): Promise { + return auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + subjectReductionQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('calculates the correct new remainingCurrentSets', async () => { + await subject(); + + const auctionSetup: any = await auctionMock.auction.callAsync(); + const expectedRemainingCurrentSets = startingCurrentSetQuantity.sub(subjectReductionQuantity); + expect(auctionSetup.remainingCurrentSets).to.bignumber.equal(expectedRemainingCurrentSets); + }); + }); + + describe('#validateBidQuantity', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + let subjectQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectQuantity = ether(5); + }); + + async function subject(): Promise { + return auctionMock.validateBidQuantity.sendTransactionAsync( + subjectQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('does not revert', async () => { + await subject(); + }); + + describe('when the quantity is not a multiple of the minimumBid', async () => { + beforeEach(async () => { + const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); + subjectQuantity = gWei(10).plus(halfMinimumBid); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + + describe('when the quantity is more than the remainingCurrentsets', async () => { + beforeEach(async () => { + subjectQuantity = startingCurrentSetQuantity.plus(ether(1)); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#validateAuctionCompletion', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + let reductionQuantity: BigNumber; + let customReductionQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + reductionQuantity = customReductionQuantity || startingCurrentSetQuantity; + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + reductionQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + async function subject(): Promise { + return auctionMock.validateAuctionCompletion.sendTransactionAsync( + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('should not revert', async () => { + await subject(); + }); + + describe('when the auction is not complete', async () => { + before(async () => { + customReductionQuantity = startingCurrentSetQuantity.div(2); + }); + + after(async () => { + customReductionQuantity = undefined; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); + }); + + describe('#isAuctionActive', async () => { + let subjectCaller: Address; + let startingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + auctionMock = await liquidatorHelper.deployAuctionMockAsync(); + + subjectCaller = functionCaller; + }); + + async function subject(): Promise { + return auctionMock.isAuctionActive.callAsync( + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('should return false', async () => { + const result = await subject(); + expect(result).to.equal(false); + }); + + describe('when the auction has begun', async () => { + beforeEach(async () => { + startingCurrentSetQuantity = ether(10); + + await blockchain.increaseTimeAsync(new BigNumber(1000)); + + await auctionMock.initializeAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return true', async () => { + const result = await subject(); + expect(result).to.equal(true); + }); + }); + }); +}); diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 03cfa8f85..075795d30 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -1,586 +1,550 @@ -// require('module-alias/register'); - -// import * as ABIDecoder from 'abi-decoder'; -// import * as _ from 'lodash'; -// import * as chai from 'chai'; -// import { BigNumber } from 'bignumber.js'; -// import { Address } from 'set-protocol-utils'; - -// import ChaiSetup from '@utils/chaiSetup'; -// import { BigNumberSetup } from '@utils/bigNumberSetup'; -// import { -// CoreContract, -// OracleWhiteListContract, -// SetTokenContract, -// SetTokenFactoryContract, -// StandardTokenMockContract, -// LinearAuctionMockContract, -// TransferProxyContract, -// UpdatableOracleMockContract, -// VaultContract, -// } from '@utils/contracts'; -// import { Blockchain } from '@utils/blockchain'; -// import { getWeb3 } from '@utils/web3Helper'; -// import { -// DEFAULT_GAS, -// ONE_DAY_IN_SECONDS, -// } from '@utils/constants'; -// import { ether, gWei } from '@utils/units'; -// import { getLinearAuction, TokenFlow } from '@utils/auction'; - -// import { CoreHelper } from '@utils/helpers/coreHelper'; -// import { ERC20Helper } from '@utils/helpers/erc20Helper'; -// import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; -// import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; - -// BigNumberSetup.configure(); -// ChaiSetup.configure(); -// const web3 = getWeb3(); -// const { expect } = chai; -// const blockchain = new Blockchain(web3); -// const Core = artifacts.require('Core'); -// const LinearAuctionMock = artifacts.require('LinearAuctionMock'); - -// contract('LinearAuction', accounts => { -// const [ -// ownerAccount, -// functionCaller, -// ] = accounts; - -// let core: CoreContract; -// let transferProxy: TransferProxyContract; -// let vault: VaultContract; -// let setTokenFactory: SetTokenFactoryContract; -// let auctionMock: LinearAuctionMockContract; - -// const coreHelper = new CoreHelper(ownerAccount, ownerAccount); -// const erc20Helper = new ERC20Helper(ownerAccount); -// const libraryMockHelper = new LibraryMockHelper(ownerAccount); -// const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); - -// let auctionPeriod: BigNumber; -// let rangeStart: BigNumber; -// let rangeEnd: BigNumber; -// let oracleWhiteList: OracleWhiteListContract; - -// let component1: StandardTokenMockContract; -// let component2: StandardTokenMockContract; -// let component3: StandardTokenMockContract; - -// let component1Price: BigNumber; -// let component2Price: BigNumber; -// let component3Price: BigNumber; - -// let set1: SetTokenContract; -// let set2: SetTokenContract; - -// let set1Components: Address[]; -// let set2Components: Address[]; - -// let set1Units: BigNumber[]; -// let set2Units: BigNumber[]; - -// let set1NaturalUnit: BigNumber; -// let set2NaturalUnit: BigNumber; - -// let component1Oracle: UpdatableOracleMockContract; -// let component2Oracle: UpdatableOracleMockContract; -// let component3Oracle: UpdatableOracleMockContract; - -// before(async () => { -// ABIDecoder.addABI(Core.abi); -// ABIDecoder.addABI(LinearAuctionMock.abi); - -// transferProxy = await coreHelper.deployTransferProxyAsync(); -// vault = await coreHelper.deployVaultAsync(); -// core = await coreHelper.deployCoreAsync(transferProxy, vault); - -// setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); -// await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); - -// component1 = await erc20Helper.deployTokenAsync(ownerAccount); -// component2 = await erc20Helper.deployTokenAsync(ownerAccount); -// component3 = await erc20Helper.deployTokenAsync(ownerAccount); - -// set1Components = [component1.address, component2.address]; -// set1Units = [gWei(1), gWei(1)]; -// set1NaturalUnit = gWei(1); -// set1 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set1Components, -// set1Units, -// set1NaturalUnit, -// ); - -// set2Components = [component2.address, component3.address]; -// set2Units = [gWei(1), gWei(1)]; -// set2NaturalUnit = gWei(2); -// set2 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// set2Components, -// set2Units, -// set2NaturalUnit, -// ); - -// component1Price = ether(1); -// component2Price = ether(2); -// component3Price = ether(1); - -// component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); -// component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); -// component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); - -// oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( -// [component1.address, component2.address, component3.address], -// [component1Oracle.address, component2Oracle.address, component3Oracle.address], -// ); - -// auctionPeriod = ONE_DAY_IN_SECONDS; -// rangeStart = new BigNumber(10); // 10% above fair value -// rangeEnd = new BigNumber(10); // 10% below fair value - -// auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( -// oracleWhiteList.address, -// auctionPeriod, -// rangeStart, -// rangeEnd, -// ); - -// }); - -// after(async () => { -// ABIDecoder.removeABI(Core.abi); -// ABIDecoder.removeABI(LinearAuctionMock.abi); -// }); - -// beforeEach(async () => { -// await blockchain.saveSnapshotAsync(); -// }); - -// afterEach(async () => { -// await blockchain.revertAsync(); -// }); - -// describe('#constructor', async () => { -// it('sets the correct auctionPeriod', async () => { -// const result = await auctionMock.auctionPeriod.callAsync(); -// expect(result).to.bignumber.equal(auctionPeriod); -// }); - -// it('sets the correct rangeStart', async () => { -// const result = await auctionMock.rangeStart.callAsync(); -// expect(result).to.bignumber.equal(rangeStart); -// }); - -// it('sets the correct rangeEnd', async () => { -// const result = await auctionMock.rangeEnd.callAsync(); -// expect(result).to.bignumber.equal(rangeEnd); -// }); -// }); - -// describe('#initializeLinearAuction', async () => { -// let subjectCaller: Address; -// let subjectCurrentSet: Address; -// let subjectNextSet: Address; -// let subjectStartingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// subjectCaller = functionCaller; -// subjectCurrentSet = set1.address; -// subjectNextSet = set2.address; -// subjectStartingCurrentSetQuantity = ether(10); -// }); - -// after(async () => { -// }); - -// async function subject(): Promise { -// return auctionMock.initializeLinearAuction.sendTransactionAsync( -// subjectCurrentSet, -// subjectNextSet, -// subjectStartingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// } - -// it('sets the correct minimumBid', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const pricePrecision = auction.auction.pricePrecision; -// const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit) -// .mul(pricePrecision); -// expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); -// }); - -// it('sets the correct endTime', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); -// const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); -// expect(auction.endTime).to.bignumber.equal(expectedEndTime); -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); - -// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct startPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// set1, -// set2, -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeStart = await auctionMock.rangeStart.callAsync(); - -// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); -// const expectedStartPrice = fairValue.sub(negativeRange); -// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); -// }); - -// it('sets the correct endPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// set1, -// set2, -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeEnd = await auctionMock.rangeEnd.callAsync(); -// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); -// const expectedEndPrice = fairValue.add(positiveRange); -// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); -// }); - -// describe('when currentSet is greater than 10x the nextSet', async () => { -// beforeEach(async () => { -// const setComponents = [component1.address, component2.address]; -// const setUnits = [gWei(1), gWei(1)]; -// const setNaturalUnit = gWei(101); -// const set3 = await coreHelper.createSetTokenAsync( -// core, -// setTokenFactory.address, -// setComponents, -// setUnits, -// setNaturalUnit, -// ); - -// subjectNextSet = set3.address; -// }); - -// it('sets the correct pricePrecision', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const expectedPricePrecision = await liquidatorHelper.calculatePricePrecisionAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList -// ); - -// expect(auction.auction.pricePrecision).to.bignumber.equal(expectedPricePrecision); -// }); - -// it('sets the correct startPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeStart = await auctionMock.rangeStart.callAsync(); -// const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); -// const expectedStartPrice = fairValue.sub(negativeRange); - -// expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); -// }); - -// it('sets the correct endPrice', async () => { -// await subject(); - -// const auction: any = await auctionMock.auction.callAsync(); - -// const fairValue = await liquidatorHelper.calculateFairValueAsync( -// await coreHelper.getSetInstance(subjectCurrentSet), -// await coreHelper.getSetInstance(subjectNextSet), -// oracleWhiteList, -// auction.auction.pricePrecision, -// ); -// const rangeEnd = await auctionMock.rangeEnd.callAsync(); -// const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); -// const expectedEndPrice = fairValue.add(positiveRange); - -// expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); -// }); -// }); -// }); - -// describe('[CONTEXT] Initialized auction', async () => { -// let subjectCaller: Address; - -// let startingCurrentSetQuantity: BigNumber; - -// beforeEach(async () => { -// subjectCaller = functionCaller; -// startingCurrentSetQuantity = ether(10); - -// await auctionMock.initializeLinearAuction.sendTransactionAsync( -// set1.address, -// set2.address, -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// describe('#getPrice', async () => { -// async function subject(): Promise { -// return auctionMock.getPrice.callAsync(); -// } - -// it('returns the correct result', async () => { -// const result = await subject(); -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(result).to.bignumber.equal(currentPrice); -// }); - -// describe('when the auction has elapsed half the period', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('returns the correct price', async () => { -// const result = await subject(); - -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(result).to.bignumber.equal(currentPrice); -// }); -// }); - -// describe('when the timestamp has exceeded the endTime', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(100)); - -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('returns the correct price / endPrice', async () => { -// const result = await subject(); - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// expect(result).to.bignumber.equal(linearAuction.endPrice); -// }); -// }); -// }); - -// describe('#getPrice', async () => { -// async function subject(): Promise { -// return auctionMock.getPrice.callAsync(); -// } - -// it('returns the correct numerator', async () => { -// const numerator = await subject(); -// const { timestamp } = await web3.eth.getBlock('latest'); -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); -// expect(numerator).to.bignumber.equal(currentPrice); -// }); -// }); - -// describe('#getTokenFlow', async () => { -// let subjectQuantity: BigNumber; - -// let tokenFlows: TokenFlow; - -// beforeEach(async () => { -// subjectQuantity = startingCurrentSetQuantity; - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const { timestamp } = await web3.eth.getBlock('latest'); - -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); - -// tokenFlows = liquidatorHelper.constructTokenFlow( -// linearAuction, -// linearAuction.auction.pricePrecision, -// subjectQuantity, -// currentPrice, -// ); -// }); - -// async function subject(): Promise { -// return auctionMock.getTokenFlow.callAsync(subjectQuantity); -// } - -// it('returns the token array', async () => { -// const { addresses } = await subject(); -// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); -// }); - -// it('returns the correct inflow', async () => { -// const { inflow } = await subject(); -// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); -// }); - -// it('returns the correct outflow', async () => { -// const { outflow } = await subject(); -// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); -// }); - -// describe('when the auction has elapsed half the period', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.div(2)); -// // Do dummy transaction to advance the block -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity.div(2), -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); - -// subjectQuantity = startingCurrentSetQuantity.div(2); - -// const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); -// const { timestamp } = await web3.eth.getBlock('latest'); - -// const currentPrice = await liquidatorHelper.calculateCurrentPrice( -// linearAuction, -// new BigNumber(timestamp), -// auctionPeriod, -// ); - -// tokenFlows = liquidatorHelper.constructTokenFlow( -// linearAuction, -// linearAuction.auction.pricePrecision, -// subjectQuantity, -// currentPrice, -// ); -// }); - -// it('returns the token array', async () => { -// const { addresses } = await subject(); -// expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); -// }); - -// it('returns the correct inflow', async () => { -// const { inflow } = await subject(); -// expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); -// }); - -// it('returns the correct outflow', async () => { -// const { outflow } = await subject(); -// expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); -// }); -// }); -// }); - -// describe('#hasAuctionFailed', async () => { -// async function subject(): Promise { -// return auctionMock.hasAuctionFailed.callAsync(); -// } - -// it('returns false', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); - -// describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return true', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); -// }); - -// describe('when the auction has been completed', async () => { -// beforeEach(async () => { -// await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( -// startingCurrentSetQuantity, -// { from: subjectCaller, gas: DEFAULT_GAS }, -// ); -// }); - -// it('should return false', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.false; -// }); -// }); - -// describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { -// beforeEach(async () => { -// await blockchain.increaseTimeAsync(auctionPeriod.add(1)); - -// await blockchain.mineBlockAsync(); -// }); - -// it('should return true', async () => { -// const hasAuctionFailed = await subject(); -// expect(hasAuctionFailed).to.be.true; -// }); -// }); -// }); -// }); -// }); \ No newline at end of file +require('module-alias/register'); + +import * as ABIDecoder from 'abi-decoder'; +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; +import { Address } from 'set-protocol-utils'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + CoreContract, + OracleWhiteListContract, + SetTokenContract, + SetTokenFactoryContract, + StandardTokenMockContract, + LinearAuctionMockContract, + TransferProxyContract, + UpdatableOracleMockContract, + VaultContract, +} from '@utils/contracts'; +import { Blockchain } from '@utils/blockchain'; +import { getWeb3 } from '@utils/web3Helper'; +import { + DEFAULT_GAS, + ONE_DAY_IN_SECONDS, +} from '@utils/constants'; +import { ether, gWei } from '@utils/units'; +import { getLinearAuction, TokenFlow } from '@utils/auction'; + +import { CoreHelper } from '@utils/helpers/coreHelper'; +import { ERC20Helper } from '@utils/helpers/erc20Helper'; +import { LibraryMockHelper } from '@utils/helpers/libraryMockHelper'; +import { LiquidatorHelper } from '@utils/helpers/liquidatorHelper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const web3 = getWeb3(); +const { expect } = chai; +const blockchain = new Blockchain(web3); +const Core = artifacts.require('Core'); +const LinearAuctionMock = artifacts.require('LinearAuctionMock'); + +contract('LinearAuction', accounts => { + const [ + ownerAccount, + functionCaller, + ] = accounts; + + let core: CoreContract; + let transferProxy: TransferProxyContract; + let vault: VaultContract; + let setTokenFactory: SetTokenFactoryContract; + let auctionMock: LinearAuctionMockContract; + + const coreHelper = new CoreHelper(ownerAccount, ownerAccount); + const erc20Helper = new ERC20Helper(ownerAccount); + const libraryMockHelper = new LibraryMockHelper(ownerAccount); + const liquidatorHelper = new LiquidatorHelper(ownerAccount, erc20Helper); + + let auctionPeriod: BigNumber; + let rangeStart: BigNumber; + let rangeEnd: BigNumber; + let oracleWhiteList: OracleWhiteListContract; + + let component1: StandardTokenMockContract; + let component2: StandardTokenMockContract; + let component3: StandardTokenMockContract; + + let component1Price: BigNumber; + let component2Price: BigNumber; + let component3Price: BigNumber; + + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + + let component1Oracle: UpdatableOracleMockContract; + let component2Oracle: UpdatableOracleMockContract; + let component3Oracle: UpdatableOracleMockContract; + + before(async () => { + ABIDecoder.addABI(Core.abi); + ABIDecoder.addABI(LinearAuctionMock.abi); + + transferProxy = await coreHelper.deployTransferProxyAsync(); + vault = await coreHelper.deployVaultAsync(); + core = await coreHelper.deployCoreAsync(transferProxy, vault); + + setTokenFactory = await coreHelper.deploySetTokenFactoryAsync(core.address); + await coreHelper.setDefaultStateAndAuthorizationsAsync(core, vault, transferProxy, setTokenFactory); + + component1 = await erc20Helper.deployTokenAsync(ownerAccount); + component2 = await erc20Helper.deployTokenAsync(ownerAccount); + component3 = await erc20Helper.deployTokenAsync(ownerAccount); + + set1Components = [component1.address, component2.address]; + set1Units = [gWei(1), gWei(1)]; + set1NaturalUnit = gWei(1); + set1 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2Components = [component2.address, component3.address]; + set2Units = [gWei(1), gWei(1)]; + set2NaturalUnit = gWei(2); + set2 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + + component1Price = ether(1); + component2Price = ether(2); + component3Price = ether(1); + + component1Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component1Price); + component2Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component2Price); + component3Oracle = await libraryMockHelper.deployUpdatableOracleMockAsync(component3Price); + + oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( + [component1.address, component2.address, component3.address], + [component1Oracle.address, component2Oracle.address, component3Oracle.address], + ); + + auctionPeriod = ONE_DAY_IN_SECONDS; + rangeStart = new BigNumber(10); // 10% above fair value + rangeEnd = new BigNumber(10); // 10% below fair value + + auctionMock = await liquidatorHelper.deployLinearAuctionMockAsync( + oracleWhiteList.address, + auctionPeriod, + rangeStart, + rangeEnd, + ); + + }); + + after(async () => { + ABIDecoder.removeABI(Core.abi); + ABIDecoder.removeABI(LinearAuctionMock.abi); + }); + + beforeEach(async () => { + await blockchain.saveSnapshotAsync(); + }); + + afterEach(async () => { + await blockchain.revertAsync(); + }); + + describe('#constructor', async () => { + it('sets the correct auctionPeriod', async () => { + const result = await auctionMock.auctionPeriod.callAsync(); + expect(result).to.bignumber.equal(auctionPeriod); + }); + + it('sets the correct rangeStart', async () => { + const result = await auctionMock.rangeStart.callAsync(); + expect(result).to.bignumber.equal(rangeStart); + }); + + it('sets the correct rangeEnd', async () => { + const result = await auctionMock.rangeEnd.callAsync(); + expect(result).to.bignumber.equal(rangeEnd); + }); + }); + + describe('#initializeLinearAuction', async () => { + let subjectCaller: Address; + let subjectCurrentSet: Address; + let subjectNextSet: Address; + let subjectStartingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + subjectCaller = functionCaller; + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + subjectStartingCurrentSetQuantity = ether(10); + }); + + after(async () => { + }); + + async function subject(): Promise { + return auctionMock.initializeLinearAuction.sendTransactionAsync( + subjectCurrentSet, + subjectNextSet, + subjectStartingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + } + + it('sets the correct minimumBid', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); + }); + + it('sets the correct endTime', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + const auctionPeriod = await auctionMock.auctionPeriod.callAsync(); + + const { timestamp } = await web3.eth.getBlock('latest'); + const expectedEndTime = new BigNumber(timestamp).plus(auctionPeriod); + expect(auction.endTime).to.bignumber.equal(expectedEndTime); + }); + + it('sets the correct startPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + set1, + set2, + oracleWhiteList, + ); + const rangeStart = await auctionMock.rangeStart.callAsync(); + + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); + }); + + it('sets the correct endPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + set1, + set2, + oracleWhiteList, + ); + const rangeEnd = await auctionMock.rangeEnd.callAsync(); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); + }); + + describe('when currentSet is greater than 10x the nextSet', async () => { + beforeEach(async () => { + const setComponents = [component1.address, component2.address]; + const setUnits = [gWei(1), gWei(1)]; + const setNaturalUnit = gWei(101); + const set3 = await coreHelper.createSetTokenAsync( + core, + setTokenFactory.address, + setComponents, + setUnits, + setNaturalUnit, + ); + + subjectNextSet = set3.address; + }); + + it('sets the correct startPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + await coreHelper.getSetInstance(subjectCurrentSet), + await coreHelper.getSetInstance(subjectNextSet), + oracleWhiteList, + ); + const rangeStart = await auctionMock.rangeStart.callAsync(); + const negativeRange = fairValue.mul(rangeStart).div(100).round(0, 3); + const expectedStartPrice = fairValue.sub(negativeRange); + + expect(auction.startPrice).to.bignumber.equal(expectedStartPrice); + }); + + it('sets the correct endPrice', async () => { + await subject(); + + const auction: any = await auctionMock.auction.callAsync(); + + const fairValue = await liquidatorHelper.calculateFairValueAsync( + await coreHelper.getSetInstance(subjectCurrentSet), + await coreHelper.getSetInstance(subjectNextSet), + oracleWhiteList, + ); + const rangeEnd = await auctionMock.rangeEnd.callAsync(); + const positiveRange = fairValue.mul(rangeEnd).div(100).round(0, 3); + const expectedEndPrice = fairValue.add(positiveRange); + + expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); + }); + }); + }); + + describe('[CONTEXT] Initialized auction', async () => { + let subjectCaller: Address; + + let startingCurrentSetQuantity: BigNumber; + + beforeEach(async () => { + subjectCaller = functionCaller; + startingCurrentSetQuantity = ether(10); + + await auctionMock.initializeLinearAuction.sendTransactionAsync( + set1.address, + set2.address, + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + describe('#getPrice', async () => { + async function subject(): Promise { + return auctionMock.getPrice.callAsync(); + } + + it('returns the correct result', async () => { + const result = await subject(); + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(result).to.bignumber.equal(currentPrice); + }); + + describe('when the auction has elapsed half the period', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.div(2)); + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('returns the correct price', async () => { + const result = await subject(); + + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(result).to.bignumber.equal(currentPrice); + }); + }); + + describe('when the timestamp has exceeded the endTime', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(100)); + + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('returns the correct price / endPrice', async () => { + const result = await subject(); + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + expect(result).to.bignumber.equal(linearAuction.endPrice); + }); + }); + }); + + describe('#getPrice', async () => { + async function subject(): Promise { + return auctionMock.getPrice.callAsync(); + } + + it('returns the correct numerator', async () => { + const numerator = await subject(); + const { timestamp } = await web3.eth.getBlock('latest'); + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + expect(numerator).to.bignumber.equal(currentPrice); + }); + }); + + describe('#getTokenFlow', async () => { + let subjectQuantity: BigNumber; + + let tokenFlows: TokenFlow; + + beforeEach(async () => { + subjectQuantity = startingCurrentSetQuantity; + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const { timestamp } = await web3.eth.getBlock('latest'); + + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + + tokenFlows = liquidatorHelper.constructTokenFlow( + linearAuction, + subjectQuantity, + currentPrice, + ); + }); + + async function subject(): Promise { + return auctionMock.getTokenFlow.callAsync(subjectQuantity); + } + + it('returns the token array', async () => { + const { addresses } = await subject(); + expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); + }); + + it('returns the correct inflow', async () => { + const { inflow } = await subject(); + expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); + }); + + it('returns the correct outflow', async () => { + const { outflow } = await subject(); + expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); + }); + + describe('when the auction has elapsed half the period', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.div(2)); + // Do dummy transaction to advance the block + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity.div(2), + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + + subjectQuantity = startingCurrentSetQuantity.div(2); + + const linearAuction = getLinearAuction(await auctionMock.auction.callAsync()); + const { timestamp } = await web3.eth.getBlock('latest'); + + const currentPrice = await liquidatorHelper.calculateCurrentPrice( + linearAuction, + new BigNumber(timestamp), + auctionPeriod, + ); + + tokenFlows = liquidatorHelper.constructTokenFlow( + linearAuction, + subjectQuantity, + currentPrice, + ); + }); + + it('returns the token array', async () => { + const { addresses } = await subject(); + expect(JSON.stringify(addresses)).to.equal(JSON.stringify(tokenFlows.addresses)); + }); + + it('returns the correct inflow', async () => { + const { inflow } = await subject(); + expect(JSON.stringify(inflow)).to.equal(JSON.stringify(tokenFlows.inflow)); + }); + + it('returns the correct outflow', async () => { + const { outflow } = await subject(); + expect(JSON.stringify(outflow)).to.equal(JSON.stringify(tokenFlows.outflow)); + }); + }); + }); + + describe('#hasAuctionFailed', async () => { + async function subject(): Promise { + return auctionMock.hasAuctionFailed.callAsync(); + } + + it('returns false', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + + describe('when the timestamp has exceeded the endTime and still biddable quantity', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return true', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + }); + + describe('when the auction has been completed', async () => { + beforeEach(async () => { + await auctionMock.reduceRemainingCurrentSets.sendTransactionAsync( + startingCurrentSetQuantity, + { from: subjectCaller, gas: DEFAULT_GAS }, + ); + }); + + it('should return false', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.false; + }); + }); + + describe('when the timestamp has exceeded endTime and there is not biddable quantity', async () => { + beforeEach(async () => { + await blockchain.increaseTimeAsync(auctionPeriod.add(1)); + + await blockchain.mineBlockAsync(); + }); + + it('should return true', async () => { + const hasAuctionFailed = await subject(); + expect(hasAuctionFailed).to.be.true; + }); + }); + }); + }); +}); diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 68ed00873..878c3da34 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -221,6 +221,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { after(async () => { customComponents1 = undefined; customComponents2 = undefined; + + customUnits1 = undefined; + customUnits2 = undefined; }); it('should revert', async () => { @@ -236,7 +239,6 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); after(async () => { - customComponents1 = undefined; customComponents2 = undefined; }); diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index f91615607..2f4560dd7 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -250,7 +250,6 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); - expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -479,7 +478,6 @@ contract('LinearAuctionLiquidator', accounts => { describe('when the quantity is not a multiple of the minimumBid', async () => { beforeEach(async () => { const halfMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit).div(2); - subjectQuantity = gWei(10).plus(halfMinimumBid); }); diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 7fbd1b868..78715cc81 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -154,43 +154,21 @@ export class LiquidatorHelper { return combinedUnits.map(unit => unit.mul(quantity).div(naturalUnit)); } - public async calculatePricePrecisionAsync( - currentSet: SetTokenContract, - nextSet: SetTokenContract, - oracleWhiteList: OracleWhiteListContract - ): Promise { - const currentSetValue = await this.calculateSetTokenValueAsync(currentSet, oracleWhiteList); - const nextSetValue = await this.calculateSetTokenValueAsync(nextSet, oracleWhiteList); - const minimumPricePrecision = new BigNumber(1000); - - if (currentSetValue.greaterThan(nextSetValue)) { - const orderOfMag = this._libraryMockHelper.ceilLog10(currentSetValue.div(nextSetValue)); - - return minimumPricePrecision.mul(10 ** (orderOfMag.toNumber() - 1)); - } - - return minimumPricePrecision; - } - public async constructCombinedUnitArrayAsync( setToken: SetTokenContract, combinedTokenArray: Address[], minimumBid: BigNumber, - priceDivisor: BigNumber, ): Promise { const setTokenComponents = await setToken.getComponents.callAsync(); const setTokenUnits = await setToken.getUnits.callAsync(); const setTokenNaturalUnit = await setToken.naturalUnit.callAsync(); - // Calculate minimumBidAmount - const maxNaturalUnit = minimumBid.div(priceDivisor); - // Create combined unit array for target Set const combinedSetTokenUnits: BigNumber[] = []; combinedTokenArray.forEach(address => { const index = setTokenComponents.indexOf(address); if (index != -1) { - const totalTokenAmount = setTokenUnits[index].mul(maxNaturalUnit).div(setTokenNaturalUnit); + const totalTokenAmount = setTokenUnits[index].mul(minimumBid).div(setTokenNaturalUnit); combinedSetTokenUnits.push(totalTokenAmount); } else { combinedSetTokenUnits.push(new BigNumber(0)); From 9bfef08ae3b9bb40e1f174d566a33736faefbcb2 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 13:37:46 -0800 Subject: [PATCH 32/35] Add check to make sure auction bounds aren't the same. --- contracts/core/liquidators/impl/LinearAuction.sol | 6 ++++++ .../impl/twoAssetPriceBoundedLinearAuction.spec.ts | 8 ++++---- .../core/liquidators/linearAuctionLiquidator.spec.ts | 10 ++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index c5567b835..73595dc19 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -84,6 +84,12 @@ contract LinearAuction is Auction { _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); + + require( + _linearAuction.startPrice != _linearAuction.endPrice, + "LinearAuction.initializeLinearAuction: NextSet must have different composition from currentSet." + ); + _linearAuction.endTime = block.timestamp.add(auctionPeriod); } diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index 878c3da34..c08a222aa 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -258,7 +258,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { beforeEach(async () => { combinedTokenArray = [wrappedETH.address, usdc.address]; combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(1152)]; + combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; await boundsCalculator.parameterizeAuction.sendTransactionAsync( combinedTokenArray, @@ -272,9 +272,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), remainingCurrentSets: new BigNumber(0), - combinedTokenArray: [wrappedETH.address, usdc.address], - combinedCurrentSetUnits: [new BigNumber(10 ** 12), new BigNumber(128)], - combinedNextSetUnits: [new BigNumber(10 ** 12), new BigNumber(1152)], + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, }, endTime: new BigNumber(0), startPrice: new BigNumber(0), diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index 2f4560dd7..b4bcbde9b 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -384,6 +384,16 @@ contract('LinearAuctionLiquidator', accounts => { await expectRevertError(subject()); }); }); + + describe('when currentSet and nextSet have same composition', async () => { + beforeEach(async () => { + subjectNextSet = set1.address; + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('[CONTEXT] Initialized auction', async () => { From cb2e049c07ae66815a262310ced5c25aa37b638d Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 17:52:59 -0800 Subject: [PATCH 33/35] Update javadoc and comments for Liquidator refactor. --- contracts/core/liquidators/impl/Auction.sol | 21 +++--- .../core/liquidators/impl/LinearAuction.sol | 6 +- .../TwoAssetPriceBoundedLinearAuction.sol | 65 ++++++++++++++----- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 1ba12e0d3..fad280cf5 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -22,12 +22,10 @@ import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol"; -import { CommonMath } from "../../../lib/CommonMath.sol"; import { ICore } from "../../interfaces/ICore.sol"; import { ISetToken } from "../../interfaces/ISetToken.sol"; import { Rebalance } from "../../lib/Rebalance.sol"; import { SetMath } from "../../lib/SetMath.sol"; -import { SetUSDValuation } from "../impl/SetUSDValuation.sol"; /** @@ -54,6 +52,9 @@ contract Auction { uint256[] combinedNextSetUnits; } + /* ============ Structs ============ */ + uint256 constant private CURVE_DENOMINATOR = 10 ** 18; + /* ============ Auction Struct Methods ============ */ /* @@ -138,7 +139,7 @@ contract Auction { * * @param _auction Auction Setup object * @param _quantity Amount of currentSets bidder is seeking to rebalance - * @param _price Scaled value representing the auction numeartor + * @param _price Value representing the auction numeartor */ function calculateTokenFlow( Setup storage _auction, @@ -221,7 +222,7 @@ contract Auction { * @param _currentUnit Amount of token i in currentSet per minimum bid amount * @param _nextSetUnit Amount of token i in nextSet per minimum bid amount * @param _unitsMultiplier Bid amount normalized to number of minimum bid amounts - * @param _priceScaled Auction price as a scaled value + * @param _price Auction price numerator with 10 ** 18 as denominator * @return inflowUnit Amount of token i transferred into the system * @return outflowUnit Amount of token i transferred to the bidder */ @@ -229,7 +230,7 @@ contract Auction { uint256 _currentUnit, uint256 _nextSetUnit, uint256 _unitsMultiplier, - uint256 _priceScaled + uint256 _price ) internal pure @@ -266,19 +267,19 @@ contract Auction { uint256 outflowUnit; // Use if statement to check if token inflow or outflow - if (_nextSetUnit.scale() > _currentUnit.mul(_priceScaled)) { + if (_nextSetUnit.mul(CURVE_DENOMINATOR) > _currentUnit.mul(_price)) { // Calculate inflow amount inflowUnit = _unitsMultiplier.mul( - _nextSetUnit.scale().sub(_currentUnit.mul(_priceScaled)) - ).div(_priceScaled); + _nextSetUnit.mul(CURVE_DENOMINATOR).sub(_currentUnit.mul(_price)) + ).div(_price); // Set outflow amount to 0 for component i, since tokens need to be injected in rebalance outflowUnit = 0; } else { // Calculate outflow amount outflowUnit = _unitsMultiplier.mul( - _currentUnit.mul(_priceScaled).sub(_nextSetUnit.scale()) - ).div(_priceScaled); + _currentUnit.mul(_price).sub(_nextSetUnit.mul(CURVE_DENOMINATOR)) + ).div(_price); // Set inflow amount to 0 for component i, since tokens need to be returned in rebalance inflowUnit = 0; diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 73595dc19..3f859beb2 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -125,7 +125,7 @@ contract LinearAuction is Auction { } /** - * Returns the linear price based on the current timestamp. Returns the endPrice + * Returns the price based on the current timestamp. Returns the endPrice * if time has exceeded the auction period * * @param _linearAuction Linear Auction State object @@ -147,7 +147,7 @@ contract LinearAuction is Auction { /** * Abstract function that must be implemented. - * Calculates the linear auction start price with a scaled value + * Calculates the linear auction start price */ function calculateStartPrice( Auction.Setup storage _auction, @@ -160,7 +160,7 @@ contract LinearAuction is Auction { /** * Abstract function that must be implemented. - * Calculates the linear auction end price with a scaled value + * Calculates the linear auction end price */ function calculateEndPrice( Auction.Setup storage _auction, diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 78c561cc9..c73d0ec72 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -50,7 +50,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 public rangeEnd; // Percentage above FairValue to end auction at /** - * LinearAuction constructor + * TwoAssetPriceBoundedLinearAuction constructor * * @param _auctionPeriod Length of auction * @param _rangeStart Percentage below FairValue to begin auction at @@ -93,7 +93,12 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { } /** - * Calculates the linear auction start price with a scaled value + * Calculates the linear auction start price. A target asset pair (i.e. ETH/DAI) price is calculated + * to start the auction at, that asset pair price is then translated into the equivalent auction price. + * + * @param _auction Auction object + * @param _currentSet CurrentSet, unused in this implementation + * @param _nextSet NextSet, unused in this implementation */ function calculateStartPrice( Auction.Setup storage _auction, @@ -138,7 +143,12 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { } /** - * Calculates the linear auction end price with a scaled value + * Calculates the linear auction end price. A target asset pair (i.e. ETH/DAI) price is calculated + * to end the auction at, that asset pair price is then translated into the equivalent auction price. + * + * @param _auction Auction object + * @param _currentSet CurrentSet, unused in this implementation + * @param _nextSet NextSet, unused in this implementation */ function calculateEndPrice( Auction.Setup storage _auction, @@ -182,6 +192,26 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ); } + /** + * Determines if asset pair price is increasing or decreasing as time passed in auction. Used to set the + * auction price bounds. Below a refers to any asset and subscripts c, n, d mean currentSetUnit, nextSetUnit + * and fullUnit amount, respectively. pP and pD refer to auction price and auction denominator. Asset pair + * price is defined as such: + * + * assetPrice = abs(assetTwoOutflow/assetOneOutflow) + * + * The equation for an outflow is given by (a_c/a_d)*pP - (a_n/a_d)*pD). It can be proven that the derivative + * of this equation is always increasing. Thus by determining the sign of the assetOneOutflow (where a negative + * amount signifies an inflow) it can be determined whether the asset pair price is increasing or decreasing. + * + * For example, if assetOneOutflow is negative it means that the denominator is getting smaller as time passes + * and thus the assetPrice is increasing during the auction. + * + * @param _auction Auction object + * @param _spotPrice Current spot price provided by asset oracles + * @param _assetOneFullUnit Units in one full unit of assetOne + * @param _assetTwoFullUnit Units in one full unit of assetTwo + */ function isTokenFlowIncreasing( Auction.Setup storage _auction, uint256 _spotPrice, @@ -200,31 +230,31 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { _assetTwoFullUnit ); - // Equation for assetOne net outflow is assetOneCurrentUnits*auctionPrice - assetOneNextUnits*auctionDenominator. - // Thus if assetOneNextUnits*auctionDenominator > assetOneCurrentUnits*auctionPrice then assetOne is - // an inflow. When assetOne is an inflow (negative outflow), it implies that assetTwo is an outflow, - // furthermore we are guaranteed that both flows have a positive derivative with respect to auction price (and - // auction price always increases). Since we define price as abs(assetTwoOutflow/assetOneOutflow), with assetOneFlow - // getting less negative and assetTwoFlow getting more positive it implies that the asset price is increasing as - // time passes in the auction. + // Determine whether outflow for assetOne is posistive or negative, if positive then asset pair price is + // increasing, else decreasing. return _auction.combinedNextSetUnits[0].mul(CURVE_DENOMINATOR) > _auction.combinedCurrentSetUnits[0].mul(auctionFairValue); } /** * Convert an asset pair price to the equivalent auction price where a1 refers to assetOne and a2 refers to assetTwo - * and subscripts c, n, d mean currentSetUnit, nextSetUnit and fullUnit amount, respectively. aP and aD refer to auction + * and subscripts c, n, d mean currentSetUnit, nextSetUnit and fullUnit amount, respectively. pP and pD refer to auction * price and auction denominator: * * assetPrice = abs(assetTwoOutflow/assetOneOutflow) * - * assetPrice = ((a2_c/a2_d)*aP - (a2_n/a2_d)*aD) / ((a1_c/a1_d)*aP - (a1_n/a1_d)*aD) + * assetPrice = ((a2_c/a2_d)*pP - (a2_n/a2_d)*pD) / ((a1_c/a1_d)*pP - (a1_n/a1_d)*pD) * - * We know assetPrice so we isolate for aP: + * We know assetPrice so we isolate for pP: * - * aP = aD((a2_n/a2_d)+assetPrice*(a1_n/a1_d)) / (a2_c/a2_d)+assetPrice*(a1_c/a1_d) + * pP = pD((a2_n/a2_d)+assetPrice*(a1_n/a1_d)) / (a2_c/a2_d)+assetPrice*(a1_c/a1_d) * * This gives us the auction price that matches with the passed asset pair price. + * + * @param _auction Auction object + * @param _targetPrice Target asset pair price + * @param _assetOneFullUnit Units in one full unit of assetOne + * @param _assetTwoFullUnit Units in one full unit of assetTwo */ function convertAssetPairPriceToAuctionPrice( Auction.Setup storage _auction, @@ -251,11 +281,16 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ); // Here the scale required to account for the 18 decimal price cancels out since it was applied to both the numerator - // and denominator. However there was an extra scale applied to the denominator that we need to remove, in order to + // and denominator. However, there was an extra scale applied to the denominator that we need to remove, in order to // do so we'll just apply another scale to the numerator before dividing since 1/(1/10 ** 18) = 10 ** 18! return calcNumerator.scale().div(calcDenominator); } + /** + * Get fullUnit amount and price of given asset. + * + * @param _asset Address of auction to get information from + */ function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { address assetOracle = oracleWhiteList.getOracleAddressByToken(_asset); uint256 assetPrice = IOracle(assetOracle).read(); From 1e98c65f063d6bebaa438e6542eafa092f027746 Mon Sep 17 00:00:00 2001 From: bweick Date: Tue, 7 Jan 2020 18:12:14 -0800 Subject: [PATCH 34/35] Remove CommonMath usage from Auction. --- contracts/core/liquidators/impl/Auction.sol | 1 - .../impl/TwoAssetPriceBoundedLinearAuction.sol | 12 ++++++------ .../core/liquidators/impl/LinearAuctionMock.sol | 3 +++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index fad280cf5..7966fd29f 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -38,7 +38,6 @@ import { SetMath } from "../../lib/SetMath.sol"; */ contract Auction { using SafeMath for uint256; - using CommonMath for uint256; using AddressArrayUtils for address[]; /* ============ Structs ============ */ diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index c73d0ec72..656c33768 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -259,8 +259,8 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { function convertAssetPairPriceToAuctionPrice( Auction.Setup storage _auction, uint256 _targetPrice, - uint256 assetOneFullUnit, - uint256 assetTwoFullUnit + uint256 _assetOneFullUnit, + uint256 _assetTwoFullUnit ) internal view @@ -269,15 +269,15 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { // Calculate the numerator for the above equation. In order to ensure no rounding down errors we distribute the auction // denominator. Additionally, since the price is passed as an 18 decimal number in order to maintain consistency we // have to scale the first term up accordingly - uint256 calcNumerator = _auction.combinedNextSetUnits[1].mul(CURVE_DENOMINATOR).scale().div(assetTwoFullUnit).add( - _targetPrice.mul(_auction.combinedNextSetUnits[0]).mul(CURVE_DENOMINATOR).div(assetOneFullUnit) + uint256 calcNumerator = _auction.combinedNextSetUnits[1].mul(CURVE_DENOMINATOR).scale().div(_assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedNextSetUnits[0]).mul(CURVE_DENOMINATOR).div(_assetOneFullUnit) ); // Calculate the denominator for the above equation. As above we we have to scale the first term match the 18 decimal // price. Furthermore since we are not guaranteed that targetPrice * a1_c > a1_d we have to scale the second term and // thus also the first term in order to match (hence the two scale() in the first term) - uint256 calcDenominator = _auction.combinedCurrentSetUnits[1].scale().scale().div(assetTwoFullUnit).add( - _targetPrice.mul(_auction.combinedCurrentSetUnits[0]).scale().div(assetOneFullUnit) + uint256 calcDenominator = _auction.combinedCurrentSetUnits[1].scale().scale().div(_assetTwoFullUnit).add( + _targetPrice.mul(_auction.combinedCurrentSetUnits[0]).scale().div(_assetOneFullUnit) ); // Here the scale required to account for the 18 decimal price cancels out since it was applied to both the numerator diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index a506d99c0..df7dd53d1 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -2,6 +2,7 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; +import { CommonMath } from "../../../../lib/CommonMath.sol"; import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; import { ISetToken } from "../../../../core/interfaces/ISetToken.sol"; @@ -9,6 +10,8 @@ import { Rebalance } from "../../../../core/lib/Rebalance.sol"; import { SetUSDValuation } from "../../../../core/liquidators/impl/SetUSDValuation.sol"; contract LinearAuctionMock is LinearAuction { + using CommonMath for uint256; + LinearAuction.State public auction; IOracleWhiteList public oracleWhiteList; From dfe83805c68041e3dc887a61b3f0d5b54ae34b59 Mon Sep 17 00:00:00 2001 From: bweick Date: Wed, 8 Jan 2020 18:42:59 -0800 Subject: [PATCH 35/35] Brian/dynamic minimum bid (#564) * Added dynamic minimumBid calculation. Altered some token flow logic to match new minimumBid calc. * Added comments and javadocs. * Add commonMath divCil function (#566) Div-ceil * Moved minimumBid calcs to LinearAuction level. Added ceilDiv function. Removed one assertion. * Fix erring helper. * Bump package. * Remove .only * Fix erring tests due to Mock logic. Co-authored-by: Felix Feng --- contracts/core/liquidators/impl/Auction.sol | 47 ++---- .../core/liquidators/impl/LinearAuction.sol | 33 +++- .../TwoAssetPriceBoundedLinearAuction.sol | 99 +++++++++++- contracts/lib/CommonMath.sol | 11 ++ .../core/liquidators/impl/AuctionMock.sol | 19 +++ .../liquidators/impl/LinearAuctionMock.sol | 17 +++ .../TwoAssetPriceBoundedLinearAuctionMock.sol | 10 ++ contracts/mocks/lib/CommonMathMock.sol | 11 ++ package.json | 2 +- .../core/liquidators/impl/auction.spec.ts | 23 +-- .../liquidators/impl/linearAuction.spec.ts | 11 ++ .../twoAssetPriceBoundedLinearAuction.spec.ts | 142 +++++++++++++++++- .../linearAuctionLiquidator.spec.ts | 55 +++++-- test/contracts/lib/commonMath.spec.ts | 40 +++++ utils/auction.ts | 3 + utils/helpers/liquidatorHelper.ts | 52 ++++++- 16 files changed, 488 insertions(+), 87 deletions(-) diff --git a/contracts/core/liquidators/impl/Auction.sol b/contracts/core/liquidators/impl/Auction.sol index 7966fd29f..459666b2f 100644 --- a/contracts/core/liquidators/impl/Auction.sol +++ b/contracts/core/liquidators/impl/Auction.sol @@ -42,6 +42,7 @@ contract Auction { /* ============ Structs ============ */ struct Setup { + uint256 maxNaturalUnit; uint256 minimumBid; uint256 startTime; uint256 startingCurrentSets; @@ -72,15 +73,11 @@ contract Auction { ) internal { - uint256 minimumBid = calculateMinimumBid(_currentSet, _nextSet); - - // remainingCurrentSets must be greater than minimumBid or no bidding would be allowed - require( - _startingCurrentSetQuantity >= minimumBid, - "Auction.initializeAuction: Not enough collateral to rebalance" + _auction.maxNaturalUnit = Math.max( + _currentSet.naturalUnit(), + _nextSet.naturalUnit() ); - _auction.minimumBid = minimumBid; _auction.startingCurrentSets = _startingCurrentSetQuantity; _auction.remainingCurrentSets = _startingCurrentSetQuantity; _auction.startTime = block.timestamp; @@ -150,7 +147,7 @@ contract Auction { returns (Rebalance.TokenFlow memory) { // Normalized quantity amount - uint256 unitsMultiplier = _quantity.div(_auction.minimumBid); + uint256 unitsMultiplier = _quantity.div(_auction.maxNaturalUnit); address[] memory memCombinedTokenArray = _auction.combinedTokenArray; @@ -175,26 +172,6 @@ contract Auction { return Rebalance.composeTokenFlow(memCombinedTokenArray, inflowUnitArray, outflowUnitArray); } - /** - * Calculate the minimumBid allowed for the rebalance - * - * @param _currentSet The Set to rebalance from - * @param _nextSet The Set to rebalance to - * @return Minimum bid amount - */ - function calculateMinimumBid( - ISetToken _currentSet, - ISetToken _nextSet - ) - internal - view - returns (uint256) - { - uint256 currentSetNaturalUnit = _currentSet.naturalUnit(); - uint256 nextNaturalUnit = _nextSet.naturalUnit(); - return Math.max(currentSetNaturalUnit, nextNaturalUnit); - } - /** * Computes the union of the currentSet and nextSet components * @@ -311,7 +288,7 @@ contract Auction { for (uint256 i = 0; i < combinedTokenArray.length; i++) { combinedUnits[i] = calculateCombinedUnit( _set, - _auction.minimumBid, + _auction.maxNaturalUnit, combinedTokenArray[i] ); } @@ -323,13 +300,13 @@ contract Auction { * Calculations the unit amount of Token to include in the the combined Set units. * * @param _setToken Information on the SetToken - * @param _minimumBid Minimum bid amount + * @param _maxNaturalUnit Max natural unit of two sets in rebalance * @param _component Current component in iteration * @return Unit inflow/outflow */ function calculateCombinedUnit( ISetToken _setToken, - uint256 _minimumBid, + uint256 _maxNaturalUnit, address _component ) private @@ -347,7 +324,7 @@ contract Auction { return calculateTransferValue( _setToken.getUnits()[indexCurrent], _setToken.naturalUnit(), - _minimumBid + _maxNaturalUnit ); } @@ -360,18 +337,18 @@ contract Auction { * * @param _unit Units of the component token * @param _naturalUnit Natural unit of the Set token - * @param _minimumBid Minimum bid amount + * @param _maxNaturalUnit Max natural unit of two sets in rebalance * @return uint256 Amount of tokens per standard bid amount (minimumBid/priceDivisor) */ function calculateTransferValue( uint256 _unit, uint256 _naturalUnit, - uint256 _minimumBid + uint256 _maxNaturalUnit ) private pure returns (uint256) { - return SetMath.setToComponent(_minimumBid, _unit, _naturalUnit); + return SetMath.setToComponent(_maxNaturalUnit, _unit, _naturalUnit); } } \ No newline at end of file diff --git a/contracts/core/liquidators/impl/LinearAuction.sol b/contracts/core/liquidators/impl/LinearAuction.sol index 3f859beb2..b096ffb96 100644 --- a/contracts/core/liquidators/impl/LinearAuction.sol +++ b/contracts/core/liquidators/impl/LinearAuction.sol @@ -82,14 +82,19 @@ contract LinearAuction is Auction { _startingCurrentSetQuantity ); - _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); - _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); - + uint256 minimumBid = calculateMinimumBid(_linearAuction.auction, _currentSet, _nextSet); + + // remainingCurrentSets must be greater than minimumBid or no bidding would be allowed require( - _linearAuction.startPrice != _linearAuction.endPrice, - "LinearAuction.initializeLinearAuction: NextSet must have different composition from currentSet." + _startingCurrentSetQuantity >= minimumBid, + "Auction.initializeAuction: Not enough collateral to rebalance" ); + _linearAuction.auction.minimumBid = minimumBid; + + _linearAuction.startPrice = calculateStartPrice(_linearAuction.auction, _currentSet, _nextSet); + _linearAuction.endPrice = calculateEndPrice(_linearAuction.auction, _currentSet, _nextSet); + _linearAuction.endTime = block.timestamp.add(auctionPeriod); } @@ -145,6 +150,24 @@ contract LinearAuction is Auction { } } + /** + * Abstract function that must be implemented. + * Calculate the minimumBid allowed for the rebalance. + * + * @param _auction Auction object + * @param _currentSet The Set to rebalance from + * @param _nextSet The Set to rebalance to + * @return Minimum bid amount + */ + function calculateMinimumBid( + Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256); + /** * Abstract function that must be implemented. * Calculates the linear auction start price diff --git a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol index 656c33768..fdd616920 100644 --- a/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol +++ b/contracts/core/liquidators/impl/TwoAssetPriceBoundedLinearAuction.sol @@ -18,6 +18,7 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; @@ -32,19 +33,27 @@ import { LinearAuction } from "./LinearAuction.sol"; * @title TwoAssetPriceBoundedLinearAuction * @author Set Protocol * + * Contract to calculate minimumBid and auction start bounds for auctions containing only + * an asset pair. */ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { using SafeMath for uint256; using CommonMath for uint256; + /* ============ Struct ============ */ struct AssetInfo { uint256 price; uint256 fullUnit; } + /* ============ Constants ============ */ uint256 constant private CURVE_DENOMINATOR = 10 ** 18; + uint256 constant private ONE = 1; + // Minimum token flow allowed at spot price in auction + uint256 constant private MIN_SPOT_TOKEN_FLOW_SCALED = 10 ** 21; uint256 constant private ONE_HUNDRED = 100; + /* ============ State Variables ============ */ IOracleWhiteList public oracleWhiteList; uint256 public rangeStart; // Percentage below FairValue to begin auction at uint256 public rangeEnd; // Percentage above FairValue to end auction at @@ -70,6 +79,8 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { rangeEnd = _rangeEnd; } + /* ============ Internal Functions ============ */ + /** * Validates that the auction only includes two components and the components are valid. */ @@ -92,6 +103,73 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ); } + /** + * Calculates the minimumBid. First calculates the minimum token flow for the pair at fair value using + * maximum natural unit of two Sets. If that token flow is below 1000 units then calculate minimumBid + * as such: + * + * minimumBid = maxNaturalUnit*1000/min(tokenFlow) + * + * Else, set minimumBid equal to maxNaturalUnit. This is to ensure that around fair value there is ample + * granualarity in asset pair price changes and not large discontinuities. + * + * @param _auction Auction object + * @param _currentSet CurrentSet, unused in this implementation + * @param _nextSet NextSet, unused in this implementation + */ + function calculateMinimumBid( + Auction.Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256) + { + // Get full Unit amount and price for each asset + AssetInfo memory assetOne = getAssetInfo(_auction.combinedTokenArray[0]); + AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); + + // Calculate current spot price as assetOne/assetTwo + uint256 spotPrice = calculateSpotPrice(assetOne.price, assetTwo.price); + + // Calculate auction price at current asset pair spot price + uint256 auctionFairValue = convertAssetPairPriceToAuctionPrice( + _auction, + spotPrice, + assetOne.fullUnit, + assetTwo.fullUnit + ); + + uint256 minimumBidMultiplier = 0; + for (uint8 i = 0; i < _auction.combinedTokenArray.length; i++) { + // Get token flow at fair value for asset i, using an amount equal to ONE maxNaturalUnit + // Hence the ONE.scale() + ( + uint256 tokenInflowScaled, + uint256 tokenOutflowScaled + ) = Auction.calculateInflowOutflow( + _auction.combinedCurrentSetUnits[i], + _auction.combinedNextSetUnits[i], + ONE.scale(), + auctionFairValue + ); + + // One returned number from previous function will be zero so use max to get tokenFlow + uint256 tokenFlowScaled = Math.max(tokenInflowScaled, tokenOutflowScaled); + + // Divide minimum spot token flow (1000 units) by token flow if more than minimumBidMultiplier + // update minimumBidMultiplier + uint256 currentMinBidMultiplier = MIN_SPOT_TOKEN_FLOW_SCALED.divCeil(tokenFlowScaled); + minimumBidMultiplier = currentMinBidMultiplier > minimumBidMultiplier ? + currentMinBidMultiplier : + minimumBidMultiplier; + } + + // Multiply the minimumBidMultiplier by maxNaturalUnit to get minimumBid + return _auction.maxNaturalUnit.mul(minimumBidMultiplier); + } + /** * Calculates the linear auction start price. A target asset pair (i.e. ETH/DAI) price is calculated * to start the auction at, that asset pair price is then translated into the equivalent auction price. @@ -114,7 +192,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); // Calculate current asset pair spot price as assetOne/assetTwo - uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + uint256 spotPrice = calculateSpotPrice(assetOne.price, assetTwo.price); // Check to see if asset pair price is increasing or decreasing as time passes bool isTokenFlowIncreasing = isTokenFlowIncreasing( @@ -164,7 +242,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { AssetInfo memory assetTwo = getAssetInfo(_auction.combinedTokenArray[1]); // Calculate current spot price as assetOne/assetTwo - uint256 spotPrice = assetOne.price.scale().div(assetTwo.price); + uint256 spotPrice = calculateSpotPrice(assetOne.price, assetTwo.price); // Check to see if asset pair price is increasing or decreasing as time passes bool isTokenFlowIncreasing = isTokenFlowIncreasing( @@ -192,6 +270,8 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { ); } + /* ============ Private Functions ============ */ + /** * Determines if asset pair price is increasing or decreasing as time passed in auction. Used to set the * auction price bounds. Below a refers to any asset and subscripts c, n, d mean currentSetUnit, nextSetUnit @@ -218,7 +298,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 _assetOneFullUnit, uint256 _assetTwoFullUnit ) - internal + private view returns (bool) { @@ -230,7 +310,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { _assetTwoFullUnit ); - // Determine whether outflow for assetOne is posistive or negative, if positive then asset pair price is + // Determine whether outflow for assetOne is positive or negative, if positive then asset pair price is // increasing, else decreasing. return _auction.combinedNextSetUnits[0].mul(CURVE_DENOMINATOR) > _auction.combinedCurrentSetUnits[0].mul(auctionFairValue); @@ -262,7 +342,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { uint256 _assetOneFullUnit, uint256 _assetTwoFullUnit ) - internal + private view returns (uint256) { @@ -291,7 +371,7 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { * * @param _asset Address of auction to get information from */ - function getAssetInfo(address _asset) internal view returns(AssetInfo memory) { + function getAssetInfo(address _asset) private view returns(AssetInfo memory) { address assetOracle = oracleWhiteList.getOracleAddressByToken(_asset); uint256 assetPrice = IOracle(assetOracle).read(); @@ -302,4 +382,11 @@ contract TwoAssetPriceBoundedLinearAuction is LinearAuction { fullUnit: CommonMath.safePower(10, decimals) }); } + + /** + * Calculate asset pair price given two prices. + */ + function calculateSpotPrice(uint256 _assetOnePrice, uint256 _assetTwoPrice) private view returns(uint256) { + return _assetOnePrice.scale().div(_assetTwoPrice); + } } \ No newline at end of file diff --git a/contracts/lib/CommonMath.sol b/contracts/lib/CommonMath.sol index e7b9eac1a..c9a7dd274 100644 --- a/contracts/lib/CommonMath.sol +++ b/contracts/lib/CommonMath.sol @@ -103,6 +103,17 @@ library CommonMath { return result; } + /** + * @dev Performs division where if there is a modulo, the value is rounded up + */ + function divCeil(uint256 a, uint256 b) + internal + pure + returns(uint256) + { + return a.mod(b) > 0 ? a.div(b).add(1) : a.div(b); + } + /** * Checks for rounding errors and returns value of potential partial amounts of a principal * diff --git a/contracts/mocks/core/liquidators/impl/AuctionMock.sol b/contracts/mocks/core/liquidators/impl/AuctionMock.sol index 81d75d93f..472a9af42 100644 --- a/contracts/mocks/core/liquidators/impl/AuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/AuctionMock.sol @@ -1,5 +1,7 @@ pragma solidity 0.5.7; +import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; + import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; import { ISetToken } from "../../../../core/interfaces/ISetToken.sol"; import { IOracleWhiteList } from "../../../../core/interfaces/IOracleWhiteList.sol"; @@ -16,6 +18,8 @@ contract AuctionMock is Auction { external { super.initializeAuction(auction, _currentSet, _nextSet, _startingCurrentSetQuantity); + + auction.minimumBid = calculateMinimumBid(auction, _currentSet, _nextSet); } function reduceRemainingCurrentSets( @@ -53,5 +57,20 @@ contract AuctionMock is Auction { function combinedNextSetUnits() external view returns(uint256[] memory) { return auction.combinedNextSetUnits; } + + function calculateMinimumBid( + Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256) + { + return Math.max( + _currentSet.naturalUnit(), + _nextSet.naturalUnit() + ); + } } diff --git a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol index df7dd53d1..34f105132 100644 --- a/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/LinearAuctionMock.sol @@ -1,6 +1,8 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; +import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; + import { Auction } from "../../../../core/liquidators/impl/Auction.sol"; import { CommonMath } from "../../../../lib/CommonMath.sol"; import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; @@ -121,5 +123,20 @@ contract LinearAuctionMock is LinearAuction { function calculateUSDValueOfSet(ISetToken _set) internal view returns(uint256) { return SetUSDValuation.calculateSetTokenDollarValue(_set, oracleWhiteList); } + + function calculateMinimumBid( + Setup storage _auction, + ISetToken _currentSet, + ISetToken _nextSet + ) + internal + view + returns (uint256) + { + return Math.max( + _currentSet.naturalUnit(), + _nextSet.naturalUnit() + ); + } } diff --git a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol index 9a0d46926..0f1ebe46a 100644 --- a/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol +++ b/contracts/mocks/core/liquidators/impl/TwoAssetPriceBoundedLinearAuctionMock.sol @@ -18,6 +18,7 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; import { ERC20Detailed } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; +import { Math } from "openzeppelin-solidity/contracts/math/Math.sol"; import { IOracle } from "set-protocol-strategies/contracts/meta-oracles/interfaces/IOracle.sol"; import { LinearAuction } from "../../../../core/liquidators/impl/LinearAuction.sol"; @@ -54,6 +55,15 @@ contract TwoAssetPriceBoundedLinearAuctionMock is TwoAssetPriceBoundedLinearAuct validateTwoAssetPriceBoundedAuction(_currentSet, _nextSet); } + function calculateMinimumBid(ISetToken _currentSet, ISetToken _nextSet) external returns(uint256) { + auctionInfo.auction.maxNaturalUnit = Math.max( + _currentSet.naturalUnit(), + _nextSet.naturalUnit() + ); + + return super.calculateMinimumBid(auctionInfo.auction, _currentSet, _nextSet); + } + function calculateStartPriceMock() external view returns(uint256) { ISetToken currentSet = ISetToken(address(0)); ISetToken nextSet = ISetToken(address(0)); diff --git a/contracts/mocks/lib/CommonMathMock.sol b/contracts/mocks/lib/CommonMathMock.sol index e7252be67..aef5ddbf3 100644 --- a/contracts/mocks/lib/CommonMathMock.sol +++ b/contracts/mocks/lib/CommonMathMock.sol @@ -52,6 +52,17 @@ contract CommonMathMock { return CommonMath.deScale(a); } + function testDivCeil( + uint256 a, + uint256 b + ) + external + pure + returns(uint256) + { + return CommonMath.divCeil(a, b); + } + function testGetPartialAmount( uint256 _principal, uint256 _numerator, diff --git a/package.json b/package.json index c43ee4187..6ca64ec5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "set-protocol-contracts", - "version": "1.3.15-beta", + "version": "1.3.16-beta", "description": "Smart contracts for {Set} Protocol", "main": "dist/artifacts/index.js", "typings": "dist/typings/artifacts/index.d.ts", diff --git a/test/contracts/core/liquidators/impl/auction.spec.ts b/test/contracts/core/liquidators/impl/auction.spec.ts index cadca2ccf..d05b8fc80 100644 --- a/test/contracts/core/liquidators/impl/auction.spec.ts +++ b/test/contracts/core/liquidators/impl/auction.spec.ts @@ -148,15 +148,6 @@ contract('Auction', accounts => { ); } - it('sets the correct minimumBid', async () => { - await subject(); - - const auctionSetup: any = await auctionMock.auction.callAsync(); - - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); - expect(auctionSetup.minimumBid).to.bignumber.equal(expectedMinimumBid); - }); - it('sets the correct startTime', async () => { await subject(); @@ -200,7 +191,7 @@ contract('Auction', accounts => { const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( set1, combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), + new BigNumber(auctionSetup.maxNaturalUnit), ); expect(JSON.stringify(combinedCurrentSetUnits)).to.equal(JSON.stringify(expectedResult)); @@ -216,21 +207,11 @@ contract('Auction', accounts => { const expectedResult = await liquidatorHelper.constructCombinedUnitArrayAsync( set2, combinedTokenArray, - new BigNumber(auctionSetup.minimumBid), + new BigNumber(auctionSetup.maxNaturalUnit), ); expect(JSON.stringify(combinedNextSetUnits)).to.equal(JSON.stringify(expectedResult)); }); - - describe('when there is insufficient collateral to rebalance', async () => { - beforeEach(async () => { - subjectStartingCurrentSetQuantity = gWei(0.5); - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); }); describe('#reduceRemainingCurrentSets', async () => { diff --git a/test/contracts/core/liquidators/impl/linearAuction.spec.ts b/test/contracts/core/liquidators/impl/linearAuction.spec.ts index 075795d30..b6392c1de 100644 --- a/test/contracts/core/liquidators/impl/linearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/linearAuction.spec.ts @@ -19,6 +19,7 @@ import { UpdatableOracleMockContract, VaultContract, } from '@utils/contracts'; +import { expectRevertError } from '@utils/tokenAssertions'; import { Blockchain } from '@utils/blockchain'; import { getWeb3 } from '@utils/web3Helper'; import { @@ -308,6 +309,16 @@ contract('LinearAuction', accounts => { expect(auction.endPrice).to.bignumber.equal(expectedEndPrice); }); }); + + describe('when there is insufficient collateral to rebalance', async () => { + beforeEach(async () => { + subjectStartingCurrentSetQuantity = gWei(0.5); + }); + + it('should revert', async () => { + await expectRevertError(subject()); + }); + }); }); describe('[CONTEXT] Initialized auction', async () => { diff --git a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts index c08a222aa..237a5215e 100644 --- a/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts +++ b/test/contracts/core/liquidators/impl/twoAssetPriceBoundedLinearAuction.spec.ts @@ -55,14 +55,17 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let wrappedETH: StandardTokenMockContract; let wrappedBTC: StandardTokenMockContract; let usdc: StandardTokenMockContract; + let dai: StandardTokenMockContract; let wrappedETHPrice: BigNumber; let wrappedBTCPrice: BigNumber; let usdcPrice: BigNumber; + let daiPrice: BigNumber; let wrappedETHOracle: UpdatableOracleMockContract; let wrappedBTCOracle: UpdatableOracleMockContract; let usdcOracle: UpdatableOracleMockContract; + let daiOracle: UpdatableOracleMockContract; let auctionPeriod: BigNumber; let rangeStart: BigNumber; @@ -82,18 +85,21 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { wrappedETH = await erc20Helper.deployTokenAsync(deployerAccount, 18); wrappedBTC = await erc20Helper.deployTokenAsync(deployerAccount, 8); usdc = await erc20Helper.deployTokenAsync(deployerAccount, 6); + dai = await erc20Helper.deployTokenAsync(deployerAccount, 18); wrappedETHPrice = ether(128); wrappedBTCPrice = ether(7500); usdcPrice = ether(1); + daiPrice = ether(1); wrappedETHOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedETHPrice); wrappedBTCOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(wrappedBTCPrice); usdcOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(usdcPrice); + daiOracle = await libraryMockHelper.deployUpdatableOracleMockAsync(daiPrice); oracleWhiteList = await coreHelper.deployOracleWhiteListAsync( - [wrappedETH.address, wrappedBTC.address, usdc.address], - [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address], + [wrappedETH.address, wrappedBTC.address, usdc.address, dai.address], + [wrappedETHOracle.address, wrappedBTCOracle.address, usdcOracle.address, daiOracle.address], ); auctionPeriod = new BigNumber(14400); // 4 hours @@ -248,6 +254,130 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { }); }); + describe('#calculateMinimumBid', async () => { + let set1: SetTokenContract; + let set2: SetTokenContract; + + let set1Components: Address[]; + let set2Components: Address[]; + + let set1Units: BigNumber[]; + let set2Units: BigNumber[]; + + let set1NaturalUnit: BigNumber; + let set2NaturalUnit: BigNumber; + let combinedTokenArray: Address[]; + let combinedCurrentSetUnits: BigNumber[]; + let combinedNextSetUnits: BigNumber[]; + + let linearAuction: LinearAuction; + + let subjectCurrentSet: Address; + let subjectNextSet: Address; + + before(async () => { + set1Components = [wrappedBTC.address, usdc.address]; + set1Units = [new BigNumber(100), new BigNumber(7500)]; + set1NaturalUnit = new BigNumber(10 ** 12); + + set2Components = [wrappedBTC.address, usdc.address]; + set2Units = [new BigNumber(100), new BigNumber(7806)]; + set2NaturalUnit = new BigNumber(10 ** 12); + }); + + beforeEach(async () => { + set1 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set1Components, + set1Units, + set1NaturalUnit, + ); + + set2 = await coreHelper.createSetTokenAsync( + coreMock, + setTokenFactory.address, + set2Components, + set2Units, + set2NaturalUnit, + ); + + combinedTokenArray = set1Components; + combinedCurrentSetUnits = set1Units; + combinedNextSetUnits = set2Units; + + await boundsCalculator.parameterizeAuction.sendTransactionAsync( + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits + ); + + linearAuction = { + auction: { + maxNaturalUnit: BigNumber.max(set1NaturalUnit, set2NaturalUnit), + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; + + subjectCurrentSet = set1.address; + subjectNextSet = set2.address; + }); + + async function subject(): Promise { + return boundsCalculator.calculateMinimumBid.callAsync( + subjectCurrentSet, + subjectNextSet + ); + } + + it('calculates the correct minimumBid', async () => { + const result = await subject(); + + const expectedResult = await liquidatorHelper.calculateMinimumBidAsync( + linearAuction, + set1, + set2, + wrappedBTCPrice.div(usdcPrice), + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + + describe('when using assets that do not require a bump in minimumBid', async () => { + before(async () => { + set1Components = [wrappedETH.address, dai.address]; + set1Units = [new BigNumber(10 ** 6), new BigNumber(128 * 10 ** 6)]; + set1NaturalUnit = new BigNumber(10 ** 6); + + set2Components = [wrappedETH.address, dai.address]; + set2Units = [new BigNumber(10 ** 6), new BigNumber(133224489)]; + set2NaturalUnit = new BigNumber(10 ** 6); + }); + + it('calculates the correct minimumBid', async () => { + const result = await subject(); + + const expectedResult = await liquidatorHelper.calculateMinimumBidAsync( + linearAuction, + set1, + set2, + wrappedETHPrice.div(usdcPrice) + ); + + expect(result).to.bignumber.equal(expectedResult); + }); + }); + }); + describe('#calculateStartPrice and calculateEndPrice', async () => { let combinedTokenArray: Address[]; let combinedCurrentSetUnits: BigNumber[]; @@ -256,9 +386,9 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { let linearAuction: LinearAuction; beforeEach(async () => { - combinedTokenArray = [wrappedETH.address, usdc.address]; - combinedCurrentSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; - combinedNextSetUnits = [new BigNumber(10 ** 12), new BigNumber(128)]; + combinedTokenArray = [wrappedBTC.address, usdc.address]; + combinedCurrentSetUnits = [new BigNumber(100), new BigNumber(7500)]; + combinedNextSetUnits = [new BigNumber(100), new BigNumber(7806)]; await boundsCalculator.parameterizeAuction.sendTransactionAsync( combinedTokenArray, @@ -268,6 +398,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { + maxNaturalUnit: new BigNumber(10 ** 12), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), @@ -339,6 +470,7 @@ contract('TwoAssetPriceBoundedLinearAuction', accounts => { linearAuction = { auction: { + maxNaturalUnit: new BigNumber(10 ** 12), minimumBid: new BigNumber(0), startTime: new BigNumber(0), startingCurrentSets: new BigNumber(0), diff --git a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts index b4bcbde9b..42045c75e 100644 --- a/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts +++ b/test/contracts/core/liquidators/linearAuctionLiquidator.spec.ts @@ -30,7 +30,7 @@ import { ONE_DAY_IN_SECONDS, } from '@utils/constants'; import { ether, gWei } from '@utils/units'; -import { getLinearAuction, TokenFlow } from '@utils/auction'; +import { getLinearAuction, LinearAuction, TokenFlow } from '@utils/auction'; import { CoreHelper } from '@utils/helpers/coreHelper'; import { ERC20Helper } from '@utils/helpers/erc20Helper'; @@ -217,6 +217,8 @@ contract('LinearAuctionLiquidator', accounts => { }); describe('#startRebalance', async () => { + let linearAuction: LinearAuction; + let subjectCaller: Address; let subjectCurrentSet: Address; let subjectNextSet: Address; @@ -224,6 +226,39 @@ contract('LinearAuctionLiquidator', accounts => { let subjectLiquidatorData: string; beforeEach(async () => { + const maxNaturalUnit = BigNumber.max( + await set1.naturalUnit.callAsync(), + await set2.naturalUnit.callAsync() + ); + + const combinedTokenArray = _.union(set1Components, set2Components); + const combinedCurrentSetUnits = await liquidatorHelper.constructCombinedUnitArrayAsync( + set1, + combinedTokenArray, + maxNaturalUnit, + ); + const combinedNextSetUnits = await liquidatorHelper.constructCombinedUnitArrayAsync( + set2, + combinedTokenArray, + maxNaturalUnit, + ); + + linearAuction = { + auction: { + maxNaturalUnit, + minimumBid: new BigNumber(0), + startTime: new BigNumber(0), + startingCurrentSets: new BigNumber(0), + remainingCurrentSets: new BigNumber(0), + combinedTokenArray, + combinedCurrentSetUnits, + combinedNextSetUnits, + }, + endTime: new BigNumber(0), + startPrice: new BigNumber(0), + endPrice: new BigNumber(0), + }; + subjectCaller = functionCaller; subjectCurrentSet = set1.address; subjectNextSet = set2.address; @@ -249,7 +284,13 @@ contract('LinearAuctionLiquidator', accounts => { const auction: any = await liquidator.auctions.callAsync(subjectCaller); - const expectedMinimumBid = BigNumber.max(set1NaturalUnit, set2NaturalUnit); + const expectedMinimumBid = await liquidatorHelper.calculateMinimumBidAsync( + linearAuction, + set1, + set2, + component1Price.div(component2Price) + ); + expect(auction.auction.minimumBid).to.bignumber.equal(expectedMinimumBid); }); @@ -384,16 +425,6 @@ contract('LinearAuctionLiquidator', accounts => { await expectRevertError(subject()); }); }); - - describe('when currentSet and nextSet have same composition', async () => { - beforeEach(async () => { - subjectNextSet = set1.address; - }); - - it('should revert', async () => { - await expectRevertError(subject()); - }); - }); }); describe('[CONTEXT] Initialized auction', async () => { diff --git a/test/contracts/lib/commonMath.spec.ts b/test/contracts/lib/commonMath.spec.ts index 7464766ca..14988dbf3 100644 --- a/test/contracts/lib/commonMath.spec.ts +++ b/test/contracts/lib/commonMath.spec.ts @@ -170,6 +170,46 @@ contract('CommonMathMock', accounts => { }); }); + describe('#testDivCeil', async () => { + let subjectA: BigNumber; + let subjectB: BigNumber; + const caller: Address = ownerAccount; + + beforeEach(async () => { + subjectA = new BigNumber(26); + subjectB = new BigNumber(11); + }); + + async function subject(): Promise { + return commonMathLibrary.testDivCeil.callAsync( + subjectA, + subjectB, + { from: caller }, + ); + } + + it('returns the correct value', async () => { + const result = await subject(); + + const expectedResult = new BigNumber(3); + expect(result).to.be.bignumber.equal(expectedResult); + }); + + describe('when there is no rounding', async () => { + beforeEach(async () => { + subjectA = new BigNumber(6); + subjectB = new BigNumber(2); + }); + + it('returns the correct value', async () => { + const result = await subject(); + + const expectedResult = new BigNumber(subjectA).div(subjectB); + expect(result).to.be.bignumber.equal(expectedResult); + }); + }); + }); + describe('getPartialAmount', async () => { let subjectPrincipal: BigNumber; let subjectNumerator: BigNumber; diff --git a/utils/auction.ts b/utils/auction.ts index 4f3cc92f0..3da4a6c12 100644 --- a/utils/auction.ts +++ b/utils/auction.ts @@ -23,6 +23,7 @@ export interface TokenFlow { } export interface Auction { + maxNaturalUnit: BigNumber; minimumBid: BigNumber; startTime: BigNumber; startingCurrentSets: BigNumber; @@ -40,10 +41,12 @@ export function getLinearAuction(input: any): LinearAuction { remainingCurrentSets, combinedCurrentSetUnits, combinedNextSetUnits, + maxNaturalUnit, } = input.auction; return { auction: { + maxNaturalUnit: new BigNumber(maxNaturalUnit), minimumBid: new BigNumber(minimumBid), startTime: new BigNumber(startTime), startingCurrentSets: new BigNumber(startingCurrentSets), diff --git a/utils/helpers/liquidatorHelper.ts b/utils/helpers/liquidatorHelper.ts index 78715cc81..20665f1d3 100644 --- a/utils/helpers/liquidatorHelper.ts +++ b/utils/helpers/liquidatorHelper.ts @@ -24,6 +24,7 @@ import { LinearAuction, TokenFlow } from '../auction'; +import { ether } from '@utils/units'; const AuctionMock = artifacts.require('AuctionMock'); const LinearAuctionLiquidator = artifacts.require('LinearAuctionLiquidator'); @@ -177,6 +178,53 @@ export class LiquidatorHelper { return combinedSetTokenUnits; } + public async calculateMinimumBidAsync( + linearAuction: LinearAuction, + currentSet: SetTokenContract, + nextSet: SetTokenContract, + assetPairPrice: BigNumber, + ): Promise { + const maxNaturalUnit = BigNumber.max( + await currentSet.naturalUnit.callAsync(), + await nextSet.naturalUnit.callAsync() + ); + + const [assetOneDecimals, assetTwoDecimals] = await this.getTokensDecimalsAsync( + linearAuction.auction.combinedTokenArray + ); + + const assetOneFullUnit = new BigNumber(10 ** assetOneDecimals.toNumber()); + const assetTwoFullUnit = new BigNumber(10 ** assetTwoDecimals.toNumber()); + + const auctionFairValue = this.calculateAuctionBound( + linearAuction, + assetOneFullUnit, + assetTwoFullUnit, + assetPairPrice + ); + + const tokenFlow = this.constructTokenFlow( + linearAuction, + maxNaturalUnit.mul(ether(1)), + auctionFairValue + ); + + const tokenFlowList = [ + BigNumber.max(tokenFlow.inflow[0], tokenFlow.outflow[0]), + BigNumber.max(tokenFlow.inflow[1], tokenFlow.outflow[1]), + ]; + + let minimumBidMultiplier: BigNumber = ZERO; + for (let i = 0; i < linearAuction.auction.combinedTokenArray.length; i++) { + const currentMinBidMultiplier = ether(1000).div(tokenFlowList[i]).round(0, 2); + minimumBidMultiplier = currentMinBidMultiplier.greaterThan(minimumBidMultiplier) ? + currentMinBidMultiplier : + minimumBidMultiplier; + } + + return maxNaturalUnit.mul(minimumBidMultiplier); + } + public async calculateAuctionBoundsAsync( linearAuction: LinearAuction, startBound: BigNumber, @@ -425,10 +473,10 @@ export class LiquidatorHelper { combinedTokenArray, combinedCurrentSetUnits, combinedNextSetUnits, - minimumBid, + maxNaturalUnit, } = linearAuction.auction; - const unitsMultiplier = quantity.div(minimumBid).round(0, 3); + const unitsMultiplier = quantity.div(maxNaturalUnit).round(0, 3); for (let i = 0; i < combinedCurrentSetUnits.length; i++) { const flow = combinedNextSetUnits[i].mul(SCALE_FACTOR).sub(combinedCurrentSetUnits[i].mul(priceScaled));