diff --git a/contracts/meta-oracles/LinearizedPriceDataSource.sol b/contracts/meta-oracles/LinearizedPriceDataSource.sol index 980dcc8..ac43267 100644 --- a/contracts/meta-oracles/LinearizedPriceDataSource.sol +++ b/contracts/meta-oracles/LinearizedPriceDataSource.sol @@ -23,6 +23,8 @@ import { TimeLockUpgrade } from "set-protocol-contracts/contracts/lib/TimeLockUp import { DataSourceLinearInterpolationLibrary } from "./lib/DataSourceLinearInterpolationLibrary.sol"; import { IOracle } from "./interfaces/IOracle.sol"; import { IDataSource } from "./interfaces/IDataSource.sol"; +import { LinkedListHelper } from "./lib/LinkedListHelper.sol"; +import { LinkedListLibraryV2 } from "./lib/LinkedListLibraryV2.sol"; import { TimeSeriesStateLibrary } from "./lib/TimeSeriesStateLibrary.sol"; @@ -39,6 +41,7 @@ contract LinearizedPriceDataSource is IDataSource { using SafeMath for uint256; + using LinkedListHelper for LinkedListLibraryV2.LinkedList; /* ============ State Variables ============ */ // Amount of time after which read interpolates price result, in seconds @@ -91,9 +94,9 @@ contract LinearizedPriceDataSource is * @returns Returns the datapoint from the oracle contract */ function read( - TimeSeriesStateLibrary.State calldata _timeSeriesState + TimeSeriesStateLibrary.State memory _timeSeriesState ) - external + public view returns (uint256) { @@ -114,11 +117,13 @@ contract LinearizedPriceDataSource is if (timeFromExpectedUpdate < interpolationThreshold) { return oracleValue; } else { + uint256 mostRecentPrice = _timeSeriesState.timeSeriesData.getLatestValue(); + return DataSourceLinearInterpolationLibrary.interpolateDelayedPriceUpdate( oracleValue, _timeSeriesState.updateInterval, timeFromExpectedUpdate, - _timeSeriesState.timeSeriesDataArray[0] + mostRecentPrice ); } } diff --git a/contracts/meta-oracles/TimeSeriesFeed.sol b/contracts/meta-oracles/TimeSeriesFeed.sol index 05449d5..48f5949 100644 --- a/contracts/meta-oracles/TimeSeriesFeed.sol +++ b/contracts/meta-oracles/TimeSeriesFeed.sol @@ -148,7 +148,7 @@ contract TimeSeriesFeed is /* * Generate struct that holds TimeSeriesFeed's current nextAvailableUpdate, updateInterval, - * and previously logged prices. + * and the LinkedList struct * * @returns Struct containing the above params */ @@ -157,16 +157,10 @@ contract TimeSeriesFeed is view returns (TimeSeriesStateLibrary.State memory) { - // Get timeSeriesData price values from most recent to oldest - uint256[] memory timeSeriesDataArray = timeSeriesData.readList( - timeSeriesData.dataArray.length - ); - return TimeSeriesStateLibrary.State({ nextEarliestUpdate: nextEarliestUpdate, updateInterval: updateInterval, - timeSeriesDataArray: timeSeriesDataArray + timeSeriesData: timeSeriesData }); - } } \ No newline at end of file diff --git a/contracts/meta-oracles/lib/LinkedListHelper.sol b/contracts/meta-oracles/lib/LinkedListHelper.sol new file mode 100644 index 0000000..19706e8 --- /dev/null +++ b/contracts/meta-oracles/lib/LinkedListHelper.sol @@ -0,0 +1,44 @@ +/* + 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 { LinkedListLibraryV2 } from "./LinkedListLibraryV2.sol"; + + +/** + * @title LinkedListHelper + * @author Set Protocol + * + * Convenience methods for the LinkedListLibrary + */ +library LinkedListHelper { + using LinkedListLibraryV2 for LinkedListLibraryV2.LinkedList; + + /* ============ Structs ============ */ + + function getLatestValue( + LinkedListLibraryV2.LinkedList memory _self + ) + internal + view + returns (uint256) + { + uint256[] memory currentTimeSeriesValues = _self.readListMemory(1); + return currentTimeSeriesValues[0]; + } +} \ No newline at end of file diff --git a/contracts/meta-oracles/lib/LinkedListLibraryV2.sol b/contracts/meta-oracles/lib/LinkedListLibraryV2.sol index ccf2343..e8259d3 100644 --- a/contracts/meta-oracles/lib/LinkedListLibraryV2.sol +++ b/contracts/meta-oracles/lib/LinkedListLibraryV2.sol @@ -158,6 +158,22 @@ library LinkedListLibraryV2 { internal view returns (uint256[] memory) + { + LinkedList memory linkedListMemory = _self; + + return readListMemory( + linkedListMemory, + _dataPoints + ); + } + + function readListMemory( + LinkedList memory _self, + uint256 _dataPoints + ) + internal + view + returns (uint256[] memory) { // Make sure query isn't for more data than collected require( @@ -180,4 +196,5 @@ library LinkedListLibraryV2 { return outputArray; } + } \ No newline at end of file diff --git a/contracts/meta-oracles/lib/TimeSeriesStateLibrary.sol b/contracts/meta-oracles/lib/TimeSeriesStateLibrary.sol index 39bede7..3de9847 100644 --- a/contracts/meta-oracles/lib/TimeSeriesStateLibrary.sol +++ b/contracts/meta-oracles/lib/TimeSeriesStateLibrary.sol @@ -16,6 +16,7 @@ pragma solidity 0.5.7; pragma experimental "ABIEncoderV2"; +import { LinkedListLibraryV2 } from "./LinkedListLibraryV2.sol"; /** @@ -28,6 +29,6 @@ library TimeSeriesStateLibrary { struct State { uint256 nextEarliestUpdate; uint256 updateInterval; - uint256[] timeSeriesDataArray; + LinkedListLibraryV2.LinkedList timeSeriesData; } } \ No newline at end of file diff --git a/contracts/mocks/meta-oracles/lib/LinkedListHelperMock.sol b/contracts/mocks/meta-oracles/lib/LinkedListHelperMock.sol new file mode 100644 index 0000000..f65245e --- /dev/null +++ b/contracts/mocks/meta-oracles/lib/LinkedListHelperMock.sol @@ -0,0 +1,42 @@ +/* + 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 { LinkedListLibraryV2 } from "../../../meta-oracles/lib/LinkedListLibraryV2.sol"; +import { LinkedListHelper } from "../../../meta-oracles/lib/LinkedListHelper.sol"; + +/** + * @title LinkedListHelperMock + * @author Set Protocol + * + * Convenience methods for the LinkedListLibrary + */ +contract LinkedListHelperMock { + + /* ============ Public Function ============ */ + + function getLatestValueMock( + LinkedListLibraryV2.LinkedList memory _self + ) + public + view + returns (uint256) + { + return LinkedListHelper.getLatestValue(_self); + } +} \ No newline at end of file diff --git a/package.json b/package.json index ef17e0f..2f03e25 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "module-alias": "^2.1.0", "openzeppelin-solidity": "^2.2", "set-protocol-contracts": "^1.2.3", - "set-protocol-utils": "^1.0.0-beta.38", + "set-protocol-utils": "^1.0.0-beta.39", "tiny-promisify": "^1.0.0", "truffle-flattener": "^1.4.0", "ts-mocha": "^6.0.0", diff --git a/test/contracts/meta-oracles/lib/linkedListHelperMock.spec.ts b/test/contracts/meta-oracles/lib/linkedListHelperMock.spec.ts new file mode 100644 index 0000000..3ca7a64 --- /dev/null +++ b/test/contracts/meta-oracles/lib/linkedListHelperMock.spec.ts @@ -0,0 +1,85 @@ +require('module-alias/register'); + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import { BigNumber } from 'bignumber.js'; + +import ChaiSetup from '@utils/chaiSetup'; +import { BigNumberSetup } from '@utils/bigNumberSetup'; +import { + LinkedListHelperMockContract, +} from '@utils/contracts'; +import { Blockchain } from '@utils/blockchain'; +import { ether } from '@utils/units'; +import { + DEFAULT_GAS +} from '@utils/constants'; +import { getWeb3 } from '@utils/web3Helper'; + +import { LibraryMockWrapper } from '@utils/wrappers/libraryMockWrapper'; + +BigNumberSetup.configure(); +ChaiSetup.configure(); +const web3 = getWeb3(); +const { expect } = chai; +const blockchain = new Blockchain(web3); + +contract('LinkedListHelper', accounts => { + const [ + deployerAccount, + ] = accounts; + + let linkedListLibraryMock: LinkedListHelperMockContract; + + const libraryMockWrapper = new LibraryMockWrapper(deployerAccount); + + beforeEach(async () => { + blockchain.saveSnapshotAsync(); + + linkedListLibraryMock = await libraryMockWrapper.deployLinkedListHelperMockAsync(); + }); + + afterEach(async () => { + blockchain.revertAsync(); + }); + + describe('#getLatestValue', async () => { + let lastUpdatedIndex: BigNumber; + let dataArray: BigNumber[]; + let dataSizeLimit: BigNumber; + + let subjectLinkedList: any; + + beforeEach(async () => { + lastUpdatedIndex = new BigNumber(4); + dataSizeLimit = new BigNumber(5); + + dataArray = [ + ether(160), + ether(175), + ether(157), + ether(162), + ether(173), + ]; + + subjectLinkedList = { + dataSizeLimit, + lastUpdatedIndex, + dataArray, + }; + }); + + async function subject(): Promise { + return linkedListLibraryMock.getLatestValueMock.callAsync( + subjectLinkedList, + { gas: DEFAULT_GAS } + ); + } + + it('gets the correct last value', async () => { + const latestValue = await subject(); + + expect(latestValue).to.bignumber.equal(dataArray[lastUpdatedIndex.toNumber()]); + }); + }); +}); \ No newline at end of file diff --git a/test/contracts/meta-oracles/linearizedPriceDataSource.spec.ts b/test/contracts/meta-oracles/linearizedPriceDataSource.spec.ts index af56a0b..8cd9791 100644 --- a/test/contracts/meta-oracles/linearizedPriceDataSource.spec.ts +++ b/test/contracts/meta-oracles/linearizedPriceDataSource.spec.ts @@ -155,13 +155,17 @@ contract('LinearizedPriceDataSource', accounts => { const nextEarliestUpdate = new BigNumber(block.timestamp); const updateInterval = ONE_DAY_IN_SECONDS; - const timeSeriesDataArray = [ether(100)]; subjectTimeSeriesState = { nextEarliestUpdate, updateInterval, - timeSeriesDataArray, + timeSeriesData: { + dataSizeLimit: new BigNumber(5), + lastUpdatedIndex: new BigNumber(0), + dataArray: [ether(100)], + }, } as TimeSeriesFeedState; + subjectTimeFastForward = ZERO; }); @@ -201,7 +205,7 @@ contract('LinearizedPriceDataSource', accounts => { const timeFromExpectedUpdate = new BigNumber(block.timestamp).sub(subjectTimeSeriesState.nextEarliestUpdate); const timeFromLastUpdate = timeFromExpectedUpdate.add(subjectTimeSeriesState.updateInterval); - const previousLoggedPrice = subjectTimeSeriesState.timeSeriesDataArray[0]; + const previousLoggedPrice = subjectTimeSeriesState.timeSeriesData.dataArray[0]; const expectedNewPrice = newEthPrice .mul(subjectTimeSeriesState.updateInterval) .add(previousLoggedPrice.mul(timeFromExpectedUpdate)) @@ -232,7 +236,7 @@ contract('LinearizedPriceDataSource', accounts => { const timeFromExpectedUpdate = new BigNumber(block.timestamp).sub(subjectTimeSeriesState.nextEarliestUpdate); const timeFromLastUpdate = timeFromExpectedUpdate.add(subjectTimeSeriesState.updateInterval); - const previousLoggedPrice = subjectTimeSeriesState.timeSeriesDataArray[0]; + const previousLoggedPrice = subjectTimeSeriesState.timeSeriesData.dataArray[0]; const expectedNewPrice = newEthPrice .mul(subjectTimeSeriesState.updateInterval) .add(previousLoggedPrice.mul(timeFromExpectedUpdate)) diff --git a/test/contracts/meta-oracles/timeSeriesFeed.spec.ts b/test/contracts/meta-oracles/timeSeriesFeed.spec.ts index 665e54c..f38f0f7 100644 --- a/test/contracts/meta-oracles/timeSeriesFeed.spec.ts +++ b/test/contracts/meta-oracles/timeSeriesFeed.spec.ts @@ -330,7 +330,7 @@ contract('TimeSeriesFeed', accounts => { describe('#getTimeSeriesFeedState', async () => { let ethPrice: BigNumber; - let numberOfUpdates: number = undefined; + const numberOfUpdates: number = 20; let maxDataPoints: BigNumber; let updateInterval: BigNumber; @@ -355,7 +355,7 @@ contract('TimeSeriesFeed', accounts => { updatedPrices = await oracleWrapper.batchUpdateTimeSeriesFeedAsync( timeSeriesFeed, ethMedianizer, - numberOfUpdates || 20, + numberOfUpdates, ); }); @@ -366,27 +366,17 @@ contract('TimeSeriesFeed', accounts => { it('returns the correct TimeSeriesState struct', async () => { const timeSeriesState = await subject(); - const expectedDailyPriceOutput = updatedPrices.reverse(); - expectedDailyPriceOutput.push(ethPrice); + const [actualNextEarliestUpdate, actualUpdateInterval, timeSeriesData] = timeSeriesState; const expectedNextEarliestUpdate = await timeSeriesFeed.nextEarliestUpdate.callAsync(); - expect(timeSeriesState.nextEarliestUpdate).to.be.bignumber.equal(expectedNextEarliestUpdate); - expect(timeSeriesState.updateInterval).to.be.bignumber.equal(updateInterval); - expect(JSON.stringify(timeSeriesState.timeSeriesDataArray)).to.equal(JSON.stringify(expectedDailyPriceOutput)); - }); - - describe('when more than maxDataPoints has been passed', async () => { - before(async () => { - numberOfUpdates = 205; - }); + const expectedDataArray = [ethPrice].concat(updatedPrices); - it('should returns last maxDataPoints values in order', async () => { - const timeSeriesState = await subject(); - - const expectedDailyPriceOutput = updatedPrices.slice(-maxDataPoints.toNumber()).reverse(); - expect(JSON.stringify(timeSeriesState.timeSeriesDataArray)).to.equal(JSON.stringify(expectedDailyPriceOutput)); - }); + expect(actualNextEarliestUpdate).to.be.bignumber.equal(expectedNextEarliestUpdate); + expect(actualUpdateInterval).to.be.bignumber.equal(updateInterval); + expect(JSON.stringify(timeSeriesData.dataArray)).to.equal(JSON.stringify(expectedDataArray)); + expect(timeSeriesData.lastUpdatedIndex).to.bignumber.equal(numberOfUpdates); + expect(timeSeriesData.dataSizeLimit).to.bignumber.equal(maxDataPoints); }); }); }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0ea85a4..1cd6eff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,7 @@ "esModuleInterop": true }, "include": [ - "types/*.ts", + "types/**/*.ts", "utils/**/*.ts", "test/**/*.ts" ] diff --git a/types/contract_templates/partials/params.mustache b/types/contract_templates/partials/params.mustache index c7690ed..aade8cc 100644 --- a/types/contract_templates/partials/params.mustache +++ b/types/contract_templates/partials/params.mustache @@ -1,3 +1,19 @@ {{#each inputs}} -this.formatABIDataItem('{{type}}', [{{#components}}{ name: '{{name}}', type: '{{type}}', components: undefined },{{/components}}], {{name}}), +this.formatABIDataItem( + '{{type}}', + [ + {{#components}} + { + name: '{{name}}', + type: '{{type}}', + {{^components}}components: undefined{{/components}}{{#components.length}}components: [{{/components.length}}{{#components}} + { + name: '{{name}}', + type: '{{type}}', + components: undefined + },{{/components}} + {{#components.length}}]{{/components.length}} + }, + {{/components}} + ],{{name}}), {{/each}} diff --git a/types/contract_templates/partials/return_type.mustache b/types/contract_templates/partials/return_type.mustache index 56f6551..e43e26d 100644 --- a/types/contract_templates/partials/return_type.mustache +++ b/types/contract_templates/partials/return_type.mustache @@ -1,6 +1,6 @@ {{#hasReturnValue}} {{#singleReturnValue}} -{{#returnType outputs.0.type components}}{{/returnType}} +{{#returnType outputs.0.type outputs.0.components}}{{/returnType}} {{/singleReturnValue}} {{^singleReturnValue}} [{{#each outputs}}{{#returnType type components}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}] diff --git a/types/deployment_stage_interface.ts b/types/deployment_stage_interface.ts deleted file mode 100644 index a7213da..0000000 --- a/types/deployment_stage_interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface DeploymentStageInterface { - deploy(web3: any): Promise; -} \ No newline at end of file diff --git a/utils/contracts.ts b/utils/contracts.ts index b5db56f..7d17285 100644 --- a/utils/contracts.ts +++ b/utils/contracts.ts @@ -9,6 +9,7 @@ export { FeedFactoryContract } from '../types/generated/feed_factory'; export { FlexibleTimingManagerLibraryMockContract } from '../types/generated/flexible_timing_manager_library_mock'; export { HistoricalPriceFeedContract } from '../types/generated/historical_price_feed'; export { LegacyMakerOracleAdapterContract } from '../types/generated/legacy_maker_oracle_adapter'; +export { LinkedListHelperMockContract } from '../types/generated/linked_list_helper_mock'; export { LinkedListLibraryMockContract } from '../types/generated/linked_list_library_mock'; export { LinkedListLibraryMockV2Contract } from '../types/generated/linked_list_library_mock_v2'; export { LinearizedPriceDataSourceContract } from '../types/generated/linearized_price_data_source'; diff --git a/utils/wrappers/libraryMockWrapper.ts b/utils/wrappers/libraryMockWrapper.ts index 6643b0e..3ddb834 100644 --- a/utils/wrappers/libraryMockWrapper.ts +++ b/utils/wrappers/libraryMockWrapper.ts @@ -2,6 +2,7 @@ import { Address } from 'set-protocol-utils'; import { DataSourceLinearInterpolationLibraryMockContract, FlexibleTimingManagerLibraryMockContract, + LinkedListHelperMockContract, LinkedListLibraryMockContract, LinkedListLibraryMockV2Contract, ManagerLibraryMockContract, @@ -14,6 +15,7 @@ import { const web3 = getWeb3(); const DataSourceLinearInterpolationLibraryMock = artifacts.require('DataSourceLinearInterpolationLibraryMock'); const FlexibleTimingManagerLibraryMock = artifacts.require('FlexibleTimingManagerLibraryMock'); +const LinkedListHelperMock = artifacts.require('LinkedListHelperMock'); const LinkedListLibraryMock = artifacts.require('LinkedListLibraryMock'); const LinkedListLibraryMockV2 = artifacts.require('LinkedListLibraryMockV2'); const ManagerLibraryMock = artifacts.require('ManagerLibraryMock'); @@ -70,6 +72,19 @@ export class LibraryMockWrapper { ); } + public async deployLinkedListHelperMockAsync( + from: Address = this._contractOwnerAddress + ): Promise { + const linkedListHelper = await LinkedListHelperMock.new( + { from }, + ); + + return new LinkedListHelperMockContract( + new web3.eth.Contract(linkedListHelper.abi, linkedListHelper.address), + { from }, + ); + } + public async deployLinkedListLibraryMockAsync( from: Address = this._contractOwnerAddress ): Promise { diff --git a/yarn.lock b/yarn.lock index 880e7ed..218cae3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4873,10 +4873,10 @@ set-protocol-utils@^1.0.0-beta.32: web3 "1.0.0-beta.36" web3-utils "1.0.0-beta.36" -set-protocol-utils@^1.0.0-beta.38: - version "1.0.0-beta.38" - resolved "https://registry.yarnpkg.com/set-protocol-utils/-/set-protocol-utils-1.0.0-beta.38.tgz#29372394f6d506c74c1eab230270e069f66ad363" - integrity sha512-3PV7AVuhd9rC/mTZivZ0HEKVmXnYa6XjH/neRcupUnmInBKu8k3HzNwMbP89TMt9VeM7nRS2hnqdyZ+4lAU6Og== +set-protocol-utils@^1.0.0-beta.39: + version "1.0.0-beta.39" + resolved "https://registry.yarnpkg.com/set-protocol-utils/-/set-protocol-utils-1.0.0-beta.39.tgz#44d3b0c786cf5c22000a43297f281cb2f9627a3a" + integrity sha512-zlxFlyrwc2iKuzLJdwvOzp17is7WPlRvi1soH+6BTEw4Z+Ujtd2PhHsjtaSq+MiIYNzDsbH0LELOMGe5MRBVlw== dependencies: "@0xproject/base-contract" "^1.0.4" "@0xproject/order-utils" "^1.0.1"