diff --git a/contracts/core/lib/auction-price-libraries/IAuctionPriceCurve.sol b/contracts/core/lib/auction-price-libraries/IAuctionPriceCurve.sol new file mode 100644 index 000000000..ae26787ab --- /dev/null +++ b/contracts/core/lib/auction-price-libraries/IAuctionPriceCurve.sol @@ -0,0 +1,42 @@ +/* + Copyright 2018 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.4.24; + +/** + * @title IAuctionPriceCurve + * @author Set Protocol + * + * The IAuctionPriceCurve interface provides a structured way to interact with any AuctionLibrary + */ + +interface IAuctionPriceCurve { + + /* + * Calculate the current priceRatio for an auction given defined price and time parameters + * + * @param _auctionStartTime Time of auction start + * @param _auctionStartPrice The price to start the auction at + * @param _curveCoefficient The slope (or convexity) of the price curve + */ + function getCurrentPrice( + uint256 _auctionStartTime, + uint256 _auctionStartPrice, + uint256 _curveCoefficient + ) + external + view; +} \ No newline at end of file diff --git a/contracts/core/lib/auction-price-libraries/LinearAuctionPriceCurve.sol b/contracts/core/lib/auction-price-libraries/LinearAuctionPriceCurve.sol new file mode 100644 index 000000000..f6ca0921b --- /dev/null +++ b/contracts/core/lib/auction-price-libraries/LinearAuctionPriceCurve.sol @@ -0,0 +1,55 @@ +/* + Copyright 2018 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.4.24; + +import { SafeMath } from "zeppelin-solidity/contracts/math/SafeMath.sol"; + + +/** + * @title LinearAuctionPriceCurve + * @author Set Protocol + * + * Contract used in rebalancing auctions to calculate price based off of a linear curve + */ + + +contract LinearAuctionPriceCurve { + using SafeMath for uint256; + + /* + * Calculate the current priceRatio for an auction given defined price and time parameters + * + * @param _auctionStartTime Time of auction start + * @param _auctionStartPrice The price to start the auction at + * @param _curveCoefficient The slope (or convexity) of the price curve + */ + function getCurrentPrice( + uint256 _auctionStartTime, + uint256 _auctionStartPrice, + uint256 _curveCoefficient + ) + external + view + returns (uint256) + { + // Calculate how much time has elapsed since start of auction and divide by + // timeIncrement of 30 seconds, so price changes every 30 seconds + uint256 elapsed = block.timestamp.sub(_auctionStartTime).div(30); + + return _curveCoefficient.mul(elapsed).add(_auctionStartPrice); + } +} diff --git a/contracts/mocks/core/lib/ConstantAuctionPriceCurve.sol b/contracts/mocks/core/lib/ConstantAuctionPriceCurve.sol new file mode 100644 index 000000000..fba7ba795 --- /dev/null +++ b/contracts/mocks/core/lib/ConstantAuctionPriceCurve.sol @@ -0,0 +1,65 @@ +/* + Copyright 2018 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.4.24; + + +/** + * @title ConstantAuctionPriceCurve + * @author Set Protocol + * + * Contract used in rebalancing auction testing to return consistent price + * + */ + +contract ConstantAuctionPriceCurve { + + uint256 public constantPrice; + + /* + * Declare price you want this library to return when queried + * + * @param _price The price you want this library to always return + */ + constructor( + uint256 _price + ) + public + { + // Set price to be returned by library + constantPrice = _price; + } + + + /* + * Return constant price amount + * + * @param -- Unused auction start time to conform to IAuctionPriceCurve -- + * @param -- Unused auction start price to conform to IAuctionPriceCurve -- + * @param -- Unused curve coefficient to conform to IAuctionPriceCurve -- + */ + function getCurrentPrice( + uint256, + uint256, + uint256 + ) + external + view + returns (uint256) + { + return constantPrice; + } +} diff --git a/test/core/lib/auction-price-libraries/linearAuctionPriceCurve.spec.ts b/test/core/lib/auction-price-libraries/linearAuctionPriceCurve.spec.ts new file mode 100644 index 000000000..a689039d0 --- /dev/null +++ b/test/core/lib/auction-price-libraries/linearAuctionPriceCurve.spec.ts @@ -0,0 +1,91 @@ +require('module-alias/register'); + +import * as chai from 'chai'; +import * as setProtocolUtils from 'set-protocol-utils'; +import { Address } from 'set-protocol-utils'; +import { BigNumber } from 'bignumber.js'; + +import { LinearAuctionPriceCurveContract } from '@utils/contracts'; +import { Blockchain } from '@utils/blockchain'; +import { ERC20Wrapper } from '@utils/erc20Wrapper'; +import { CoreWrapper } from '@utils/coreWrapper'; +import { RebalancingTokenWrapper } from '@utils/RebalancingTokenWrapper'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import ChaiSetup from '@utils/chaiSetup'; +import { DEFAULT_GAS } from '@utils/constants'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const { SetProtocolUtils: SetUtils } = setProtocolUtils; +const { expect } = chai; + + +contract('LinearAuctionPriceCurve', accounts => { + const [ + ownerAccount, + ] = accounts; + + let auctionCurve: LinearAuctionPriceCurveContract; + + const coreWrapper = new CoreWrapper(ownerAccount, ownerAccount); + const erc20Wrapper = new ERC20Wrapper(ownerAccount); + const blockchain = new Blockchain(web3); + const rebalancingTokenWrapper = new RebalancingTokenWrapper( + ownerAccount, + coreWrapper, + erc20Wrapper, + blockchain + ); + + beforeEach(async () => { + await blockchain.saveSnapshotAsync(); + auctionCurve = await coreWrapper.deployLinearAuctionPriceCurveAsync(); + }); + + afterEach(async () => { + await blockchain.revertAsync(); + }); + + describe('#getCurrentPrice', async () => { + let subjectAuctionStartTime: BigNumber; + let subjectAuctionStartPrice: BigNumber; + let subjectCurveCoefficient: BigNumber; + let subjectCaller: Address; + + beforeEach(async () => { + subjectAuctionStartPrice = new BigNumber(500); + subjectCurveCoefficient = new BigNumber (5); + subjectAuctionStartTime = SetUtils.generateTimestamp(0); + subjectCaller = ownerAccount; + }); + + async function subject(): Promise { + return auctionCurve.getCurrentPrice.callAsync( + subjectAuctionStartTime, + subjectAuctionStartPrice, + subjectCurveCoefficient, + { from: subjectCaller, gas: DEFAULT_GAS} + ); + } + + it('starts with the correct price', async () => { + const returnedPrice = await subject(); + + expect(returnedPrice).to.be.bignumber.equal(subjectAuctionStartPrice); + }); + + it('returns the correct price after one hour', async () => { + const timeJump = new BigNumber(3600); + await blockchain.increaseTimeAsync(timeJump); + + const returnedPrice = await subject(); + + const expectedPrice = rebalancingTokenWrapper.getExpectedLinearAuctionPrice( + timeJump, + subjectCurveCoefficient, + subjectAuctionStartPrice + ); + expect(returnedPrice).to.be.bignumber.equal(expectedPrice); + }); + }); +}); \ No newline at end of file diff --git a/utils/RebalancingTokenWrapper.ts b/utils/RebalancingTokenWrapper.ts index f85ea5329..901ab1c1f 100644 --- a/utils/RebalancingTokenWrapper.ts +++ b/utils/RebalancingTokenWrapper.ts @@ -16,7 +16,8 @@ import { ONE_DAY_IN_SECONDS, DEFAULT_UNIT_SHARES, DEFAULT_REBALANCING_NATURAL_UNIT, - UNLIMITED_ALLOWANCE_IN_BASE_UNITS + UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + AUCTION_TIME_INCREMENT } from './constants'; import { CoreWrapper } from './coreWrapper'; @@ -264,4 +265,14 @@ export class RebalancingTokenWrapper { const unitShares = issueAmount.div(naturalUnitsOutstanding).round(0, 3); return {unitShares, issueAmount}; } + + public getExpectedLinearAuctionPrice( + elapsedTime: BigNumber, + curveCoefficient: BigNumber, + auctionStartPrice: BigNumber + ): BigNumber { + const elaspedTimeFromStart = elapsedTime.div(AUCTION_TIME_INCREMENT).round(0, 3); + const expectedPrice = curveCoefficient.mul(elaspedTimeFromStart).add(auctionStartPrice); + return expectedPrice; + } } diff --git a/utils/constants.ts b/utils/constants.ts index 7ae0b872e..433cb5481 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -13,6 +13,7 @@ export const STANDARD_NATURAL_UNIT = ether(1); export const STANDARD_QUANTITY_ISSUED: BigNumber = ether(10); export const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); export const ZERO: BigNumber = new BigNumber(0); +export const AUCTION_TIME_INCREMENT = new BigNumber(30); // Unix seconds export const PRIVATE_KEYS = [ '767df558efc63b6ba9a9257e68509c38f5c48d5938a41ab191a9a073ebff7c4f', diff --git a/utils/contracts.ts b/utils/contracts.ts index 8a38a20c2..e5b26eb10 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -10,6 +10,7 @@ import { DetailedERC20Contract } from '../types/generated/detailed_erc20'; import { ERC20WrapperMockContract } from '../types/generated/erc20_wrapper_mock'; import { InvalidReturnTokenMockContract } from '../types/generated/invalid_return_token_mock'; import { KyberNetworkWrapperContract } from '../types/generated/kyber_network_wrapper'; +import { LinearAuctionPriceCurveContract } from '../types/generated/linear_auction_price_curve'; import { NoDecimalTokenMockContract } from '../types/generated/no_decimal_token_mock'; import { NoXferReturnTokenMockContract } from '../types/generated/no_xfer_return_token_mock'; import { OrderLibraryMockContract } from '../types/generated/order_library_mock'; @@ -37,6 +38,7 @@ export { ERC20WrapperMockContract, InvalidReturnTokenMockContract, KyberNetworkWrapperContract, + LinearAuctionPriceCurveContract, NoDecimalTokenMockContract, NoXferReturnTokenMockContract, OrderLibraryMockContract, diff --git a/utils/coreWrapper.ts b/utils/coreWrapper.ts index 0d25a66d7..ffe852ce9 100644 --- a/utils/coreWrapper.ts +++ b/utils/coreWrapper.ts @@ -6,6 +6,7 @@ import { AuthorizableContract, CoreContract, CoreMockContract, + LinearAuctionPriceCurveContract, OrderLibraryMockContract, SetTokenContract, RebalancingSetTokenContract, @@ -22,6 +23,7 @@ const Authorizable = artifacts.require('Authorizable'); const Core = artifacts.require('Core'); const CoreMock = artifacts.require('CoreMock'); const ERC20Wrapper = artifacts.require('ERC20Wrapper'); +const LinearAuctionPriceCurve = artifacts.require('LinearAuctionPriceCurve'); const OrderLibrary = artifacts.require('OrderLibrary'); const OrderLibraryMock = artifacts.require('OrderLibraryMock'); const RebalancingSetToken = artifacts.require('RebalancingSetToken'); @@ -149,6 +151,19 @@ export class CoreWrapper { ); } + public async deployLinearAuctionPriceCurveAsync( + from: Address = this._tokenOwnerAddress + ): Promise { + const truffleLinearAuctionPriceCurve = await LinearAuctionPriceCurve.new( + { from }, + ); + + return new LinearAuctionPriceCurveContract( + web3.eth.contract(truffleLinearAuctionPriceCurve.abi).at(truffleLinearAuctionPriceCurve.address), + { from, gas: DEFAULT_GAS }, + ); + } + public async deploySetTokenAsync( factory: Address, componentAddresses: Address[],