From f3d798f3a5fcf7ad6f8ebf505d5680815f87b4b2 Mon Sep 17 00:00:00 2001 From: Jared Flatow Date: Tue, 28 Jul 2020 14:50:36 -0700 Subject: [PATCH] Address audit feedback, fix up poster, and other improvements --- .build/ropsten.json | 3 +- .circleci/config.yml | 72 +- .codecov.yml | 23 + .gitignore | 2 + Dockerfile | 6 +- README.md | 4 +- contracts/AnchoredView/AnchoredView.sol | 254 --- .../AnchoredView/SymbolConfiguration.sol | 261 --- contracts/DelFiPrice.sol | 128 -- contracts/OpenOraclePriceData.sol | 21 +- contracts/OpenOracleView.sol | 4 +- contracts/Uniswap/UniswapAnchoredView.sol | 153 +- contracts/Uniswap/UniswapConfig.sol | 46 +- contracts/Uniswap/UniswapLib.sol | 4 +- docker-compose.yml | 78 - jest.config.js | 2 +- package.json | 10 +- poster/README.md | 74 +- poster/package.json | 10 +- poster/src/index.ts | 63 +- poster/src/interfaces.ts | 52 +- poster/src/mainnet_uniswap_mocker.ts | 74 + poster/src/post_with_retries.ts | 92 +- poster/src/poster.ts | 326 ++-- poster/src/prev_price.ts | 81 +- poster/src/sources/coinbase.ts | 38 + poster/src/util.ts | 136 ++ poster/tests/post_with_retries_test.ts | 12 +- poster/tests/poster_test.ts | 254 ++- poster/yarn.lock | 50 +- script/coverage | 31 + script/test | 21 + tests/AnchoredViewTest.js | 482 ------ tests/DelFiPriceTest.js | 516 ------ tests/Helpers.js | 39 +- tests/IntegrationTest.js | 67 - tests/Matchers.js | 40 +- tests/NonReporterPricesTest.js | 59 +- tests/PostRealWorldPricesTest.js | 527 +++--- tests/UniswapAnchoredViewTest.js | 509 +++++- tests/UniswapConfigTest.js | 43 +- tests/contracts/MockUniswapAnchoredView.sol | 3 +- tests/contracts/MockUniswapTokenPair.sol | 10 + yarn.lock | 1470 +++++++++-------- 44 files changed, 2813 insertions(+), 3337 deletions(-) create mode 100644 .codecov.yml delete mode 100644 contracts/AnchoredView/AnchoredView.sol delete mode 100644 contracts/AnchoredView/SymbolConfiguration.sol delete mode 100644 contracts/DelFiPrice.sol delete mode 100644 docker-compose.yml create mode 100644 poster/src/mainnet_uniswap_mocker.ts create mode 100644 poster/src/sources/coinbase.ts create mode 100644 poster/src/util.ts create mode 100755 script/coverage create mode 100755 script/test delete mode 100644 tests/AnchoredViewTest.js delete mode 100644 tests/DelFiPriceTest.js delete mode 100644 tests/IntegrationTest.js diff --git a/.build/ropsten.json b/.build/ropsten.json index 6837f92a..a9ff2daf 100644 --- a/.build/ropsten.json +++ b/.build/ropsten.json @@ -3,5 +3,6 @@ "DelFiPrice": "0xA7637AD217af1DD781b7DDeD452CE339cfb4a312", "AnchoredPriceView": "0x4745ed21F933C445F6021E1949817c6C281329d2", "MockAnchorOracle": "0xF054B68676900ECB13b7107019b062662B50d3d7", - "AnchoredView": "0xcaF366e452f9613Cb3fabe094619cc1e1e2aC149" + "AnchoredView": "0xcaF366e452f9613Cb3fabe094619cc1e1e2aC149", + "UniswapAnchoredView": "0xCCD252F17E7F69C1ce813DDE398e878A8D8A2202" } \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 66d2d38f..f0018493 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,13 +1,14 @@ version: 2.1 +orbs: + codecov: codecov/codecov@1.0.3 + jobs: test_oracle: docker: - - image: circleci/node:11 + - image: circleci/node:12 working_directory: ~/repo steps: - - setup_remote_docker: - docker_layer_caching: true - run: | sudo wget https://github.com/ethereum/solidity/releases/download/v0.6.10/solc-static-linux -O /usr/local/bin/solc @@ -41,17 +42,71 @@ jobs: - sdk/javascript/node_modules key: v1-sdk-dependencies-{{ checksum "sdk/javascript/package.json" }} - run: mkdir ~/junit-oracle - - run: npx saddle compile - - run: docker-compose -p open-oracle build - - run: JEST_JUNIT_OUTPUT=~/junit-oracle/test-results.xml npx saddle test tests/*Test.js -- --ci --reporters=default --reporters=jest-junit + - run: JEST_JUNIT_OUTPUT=~/junit-oracle/test-results.xml script/test tests/*Test.js -- --ci --reporters=default --reporters=jest-junit - store_test_results: path: ~/junit-oracle - store_artifacts: path: ~/junit-oracle + test_coverage: + docker: + - image: circleci/node:13 + working_directory: ~/repo + steps: + - run: + | + sudo wget https://github.com/ethereum/solidity/releases/download/v0.6.10/solc-static-linux -O /usr/local/bin/solc + sudo chmod +x /usr/local/bin/solc + - checkout + - restore_cache: + keys: + - cov-v1-dependencies-{{ checksum "package.json" }} + - cov-v1-dependencies- + - restore_cache: + keys: + - cov-v1-poster-dependencies-{{ checksum "poster/package.json" }} + - cov-v1-poster-dependencies- + - restore_cache: + keys: + - cov-v1-sdk-dependencies-{{ checksum "sdk/javascript/package.json" }} + - cov-v1-sdk-dependencies- + - run: yarn install + - save_cache: + paths: + - node_modules + key: cov-v1-dependencies-{{ checksum "package.json" }} + - run: cd poster && yarn install && yarn prepare + - save_cache: + paths: + - poster/node_modules + key: cov-v1-poster-dependencies-{{ checksum "poster/package.json" }} + - run: cd sdk/javascript && yarn install && yarn prepare + - save_cache: + paths: + - sdk/javascript/node_modules + key: cov-v1-sdk-dependencies-{{ checksum "sdk/javascript/package.json" }} + - run: mkdir ~/junit-oracle + - run: + shell: /bin/bash -eox pipefail -O globstar + name: yarn coverage + no_output_timeout: 30m + command: JEST_JUNIT_OUTPUT_DIR=~/junit-oracle JEST_JUNIT_OUTPUT_NAME=test-results.xml script/coverage $(circleci tests glob 'tests/**/**Test.js' | circleci tests split --split-by=timings) -- --maxWorkers=4 + - store_test_results: + path: ~/junit-oracle + - store_artifacts: + path: ~/repo/coverage/coverage-final.json + destination: coverage-final.json + - store_artifacts: + path: ~/repo/coverage/lcov-report + destination: coverage + - codecov/upload: + file: ~/repo/coverage/coverage-final.json + parallelism: 3 + resource_class: xlarge + test_poster: docker: - - image: circleci/node:11 + - image: circleci/node:12 working_directory: ~/repo/poster steps: - checkout: @@ -74,7 +129,7 @@ jobs: test_reporter_javascript: docker: - - image: circleci/node:11 + - image: circleci/node:12 working_directory: ~/repo/sdk/javascript steps: - checkout: @@ -100,5 +155,6 @@ workflows: test: jobs: - test_oracle + - test_coverage - test_poster - test_reporter_javascript diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..58d36073 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,23 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: no + +ignore: + - "tests/contracts" diff --git a/.gitignore b/.gitignore index f189ce7c..bf0af602 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ *.tsbuilt* *.DS_Store* .build/contracts.json +.build/contracts-trace.json .build/test.json .build/development.json yarn-error.log junit.xml +coverage/* diff --git a/Dockerfile b/Dockerfile index 379f7521..bcf2a81a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:13.6.0-alpine3.10 +FROM node:14.5.0-alpine3.10 WORKDIR /open-oracle RUN wget https://github.com/ethereum/solidity/releases/download/v0.6.10/solc-static-linux -O /usr/local/bin/solc && chmod +x /usr/local/bin/solc RUN apk update && apk add --no-cache --virtual .gyp \ @@ -10,9 +10,9 @@ RUN apk update && apk add --no-cache --virtual .gyp \ git RUN yarn global add node-gyp npx -COPY package.json /open-oracle/package.json +COPY package.json yarn.lock /open-oracle/ -RUN yarn install +RUN yarn install --frozen-lockfile ENV PROVIDER PROVIDER COPY contracts contracts diff --git a/README.md b/README.md index 6422261d..e23b6e8a 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,6 @@ The poster is a simple application that reads from a given feed (or set of feeds ## Contributing -Note: the code in this repository is held under the MIT license. Any contributors must agree to release contributed code under this same license. Please submit an issue (or create a pull request) for any issues or contributions to the project. +Note: all code contributed to this repository must be licensed under each of 1. MIT, 2. BSD-3, and 3. GPLv3. By contributing code to this repository, you accept that your code is allowed to be released under any or all of these licenses or licenses in substantially similar form to these listed above. + +Please submit an issue (or create a pull request) for any issues or contributions to the project. Make sure that all test cases pass, including the integration tests in the root of this project. diff --git a/contracts/AnchoredView/AnchoredView.sol b/contracts/AnchoredView/AnchoredView.sol deleted file mode 100644 index 55fd5414..00000000 --- a/contracts/AnchoredView/AnchoredView.sol +++ /dev/null @@ -1,254 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity ^0.6.10; -pragma experimental ABIEncoderV2; - -import "./SymbolConfiguration.sol"; -import "../OpenOraclePriceData.sol"; - -interface AnchorOracle { - function numBlocksPerPeriod() external view returns (uint); // approximately 1 hour: 60 seconds/minute * 60 minutes/hour * 1 block/15 seconds - - function assetPrices(address asset) external view returns (uint); - - struct Anchor { - // floor(block.number / numBlocksPerPeriod) + 1 - uint period; - - // Price in ETH, scaled by 10**18 - uint priceMantissa; - } - function anchors(address asset) external view returns (Anchor memory); -} - - -/** - * @notice Price feed conforming to Price Oracle Proxy interface. - * @dev Use a single open oracle reporter and anchored to and falling back to the Compound v2 oracle system. - * @dev The reporter must report at a minimum the USD/ETH price, so that anchor ETH/TOKEN prices can be converted to USD/TOKEN - * @author Compound Labs, Inc. - */ -contract AnchoredView is SymbolConfiguration { - /// @notice The mapping of anchored reporter prices by symbol - mapping(string => uint) public _prices; - - /// @notice Circuit breaker for using anchor price oracle directly, ignoring reporter - bool public reporterBreaker; - - /// @notice Circuit breaker for using reporter price without anchor - bool public anchorBreaker; - - /// @notice the Open Oracle Reporter price reporter - address public immutable reporter; - - /// @notice The anchor oracle ( Compound Oracle V1 ) - AnchorOracle public immutable anchor; - - /// @notice The Open Oracle Price Data contract - OpenOraclePriceData public immutable priceData; - - /// @dev The highest ratio of the new median price to the anchor price that will still trigger the median price to be updated - uint immutable upperBoundAnchorRatio; - - /// @dev The lowest ratio of the new median price to the anchor price that will still trigger the median price to be updated - uint immutable lowerBoundAnchorRatio; - - /// @dev Average blocks per day, for checking anchor staleness (1 day / 15) - uint constant blocksInADay = 5760; - - /// @notice The event emitted when the median price is updated - event PriceUpdated(string symbol, uint price); - - /// @notice The event emitted when new prices are posted but the stored price is not updated due to the anchor - event PriceGuarded(string symbol, uint reporter, uint anchor); - - /// @notice The event emitted when reporter invalidates itself - event ReporterInvalidated(address reporter); - - /// @notice The event emitted when the anchor is cut for staleness - event AnchorCut(address anchor); - - /** - * @param data_ Address of the Oracle Data contract - * @param reporter_ The reporter address whose price will be used if it matches the anchor - * @param anchor_ The PriceOracleProxy that will be used to verify reporter price, or serve prices not given by the reporter - * @param anchorToleranceMantissa_ The tolerance allowed between the anchor and median. A tolerance of 10e16 means a new median that is 10% off from the anchor will still be saved - * @param tokens_ The CTokens struct that contains addresses for CToken contracts - */ - constructor(OpenOraclePriceData data_, - address reporter_, - AnchorOracle anchor_, - uint anchorToleranceMantissa_, - CTokens memory tokens_) SymbolConfiguration(tokens_) public { - reporter = reporter_; - anchor = anchor_; - priceData = data_; - - require(anchorToleranceMantissa_ < 100e16, "Anchor Tolerance is too high"); - upperBoundAnchorRatio = 100e16 + anchorToleranceMantissa_; - lowerBoundAnchorRatio = 100e16 - anchorToleranceMantissa_; - } - - /** - * @notice Post open oracle reporter prices, and recalculate stored price by comparing to anchor - * @dev We let anyone pay to post anything, but only prices from configured reporter will be stored in the view - * @param messages The messages to post to the oracle - * @param signatures The signatures for the corresponding messages - * @param symbols The symbols to compare to anchor for authoritative reading - */ - function postPrices(bytes[] calldata messages, bytes[] calldata signatures, string[] calldata symbols) external { - require(messages.length == signatures.length, "messages and signatures must be 1:1"); - - // Save the prices - for (uint i = 0; i < messages.length; i++) { - priceData.put(messages[i], signatures[i]); - } - - // load usdc for using in loop to convert anchor prices to dollars - uint usdcPrice = readAnchor(cUsdcAddress); - - // Try to update the view storage - for (uint i = 0; i < symbols.length; i++) { - CTokenMetadata memory tokenConfig = getCTokenConfig(symbols[i]); - // symbol is not supported in the view, but allow writing to data - if (tokenConfig.cTokenAddress == address(0)) continue; - - uint reporterPrice = priceData.getPrice(reporter, tokenConfig.openOracleKey); - uint anchorPrice = getAnchorInUsd(tokenConfig, usdcPrice); - uint anchorRatio = mul(anchorPrice, 100e16) / reporterPrice; - bool withinAnchor = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio; - - if (withinAnchor || anchorBreaker) { - // only update and emit event if value changes - if (_prices[tokenConfig.openOracleKey] != reporterPrice) { - _prices[tokenConfig.openOracleKey] = reporterPrice; - emit PriceUpdated(tokenConfig.openOracleKey, reporterPrice); - } - } else { - emit PriceGuarded(tokenConfig.openOracleKey, reporterPrice, anchorPrice); - } - } - } - /** - * @notice Returns price denominated in USD, with 6 decimals - * @dev If price was posted by reporter, return it. Otherwise, return anchor price converted through reporter ETH price. - */ - function prices(string calldata symbol) external view returns (uint) { - CTokenMetadata memory tokenConfig = getCTokenConfig(symbol); - - if (tokenConfig.priceSource == PriceSource.REPORTER) return _prices[symbol]; - if (tokenConfig.priceSource == PriceSource.FIXED_USD) return tokenConfig.fixedReporterPrice; - if (tokenConfig.priceSource == PriceSource.ANCHOR) { - uint usdPerEth = _prices["ETH"]; - require(usdPerEth > 0, "eth price not set, cannot convert eth to dollars"); - - uint ethPerToken = readAnchor(tokenConfig); - return mul(usdPerEth, ethPerToken) / tokenConfig.baseUnit; - } - } - - /** - * @dev fetch price in eth from proxy and convert to usd price using anchor usdc price. - * @dev Anchor price has 36 - underlying decimals, so scale back up to 36 decimals before dividing by by usdc price (30 decimals), yielding 6 decimal usd price - */ - function getAnchorInUsd(address cToken, uint ethPerUsdc) public view returns (uint) { - CTokenMetadata memory tokenConfig = getCTokenConfig(cToken); - return getAnchorInUsd(tokenConfig, ethPerUsdc); - } - - function getAnchorInUsd(CTokenMetadata memory tokenConfig, uint ethPerUsdc) internal view returns (uint) { - if (tokenConfig.anchorSource == AnchorSource.FIXED_USD) { - return tokenConfig.fixedAnchorPrice; - } - - uint ethPerToken = readAnchor(tokenConfig); - - return mul(ethPerToken, tokenConfig.baseUnit) / ethPerUsdc; - } - - /** - * @notice Implements the method of the PriceOracle interface of Compound v2 and returns returns the Eth price for an asset. - * @dev converts from 1e6 decimals of Open Oracle to 1e(36 - underlyingDecimals) of PriceOracleProxy - * @param cToken The cToken address for price retrieval - * @return The price for the given cToken address - */ - function getUnderlyingPrice(address cToken) public view returns (uint) { - CTokenMetadata memory tokenConfig = getCTokenConfig(cToken); - if (reporterBreaker == true) { - return readAnchor(tokenConfig); - } - - if (tokenConfig.priceSource == PriceSource.FIXED_USD) { - uint usdPerToken = tokenConfig.fixedReporterPrice; - return mul(usdPerToken, 1e30) / tokenConfig.baseUnit; - } - - if (tokenConfig.priceSource == PriceSource.REPORTER) { - uint usdPerToken = _prices[tokenConfig.openOracleKey]; - return mul(usdPerToken, 1e30) / tokenConfig.baseUnit; - } - - if (tokenConfig.priceSource == PriceSource.ANCHOR) { - // convert anchor price to usd, via reporter eth price - uint usdPerEth = _prices["ETH"]; - require(usdPerEth != 0, "no reporter price for usd/eth exists, cannot convert anchor price to usd terms"); - - // factoring out extra 6 decimals from reporter eth price brings us back to decimals given by anchor - uint ethPerToken = readAnchor(tokenConfig); - return mul(usdPerEth, ethPerToken) / 1e6; - } - } - - /** - * @notice Get the underlying price of a listed cToken asset - * @param cToken The cToken to get the underlying price of - * @return The underlying asset price mantissa (scaled by 1e18) - */ - function readAnchor(address cToken) public view returns (uint) { - return readAnchor(getCTokenConfig(cToken)); - } - - function readAnchor(CTokenMetadata memory tokenConfig) internal view returns (uint) { - if (tokenConfig.anchorSource == AnchorSource.FIXED_ETH) return tokenConfig.fixedAnchorPrice; - - return anchor.assetPrices(tokenConfig.anchorOracleKey); - } - - /// @notice invalidate the reporter, and fall back to using anchor directly in all cases - function invalidate(bytes memory message, bytes memory signature) public { - (string memory decoded_message, ) = abi.decode(message, (string, address)); - require(keccak256(abi.encodePacked(decoded_message)) == keccak256(abi.encodePacked("rotate")), "invalidation message must be 'rotate'"); - require(priceData.source(message, signature) == reporter, "invalidation message must come from the reporter"); - - reporterBreaker = true; - emit ReporterInvalidated(reporter); - } - - /// @notice invalidate the anchor, and fall back to using reporter without anchor - - /// @dev determine if anchor is stale by checking when usdc was last updated - /// @dev all anchor prices are converted through usdc price, so if it is stale they are all stale - function cutAnchor() external { - AnchorOracle.Anchor memory latestUsdcAnchor = anchor.anchors(cUsdcAnchorKey); - - uint usdcAnchorBlockNumber = mul(latestUsdcAnchor.period, anchor.numBlocksPerPeriod()); - uint blocksSinceUpdate = block.number - usdcAnchorBlockNumber; - - // one day in 15 second blocks without an update - if (blocksSinceUpdate > blocksInADay) { - anchorBreaker = true; - emit AnchorCut(address(anchor)); - } - } - - - // @notice overflow proof multiplication - function mul(uint a, uint b) internal pure returns (uint) { - if (a == 0) return 0; - - uint c = a * b; - require(c / a == b, "multiplication overflow"); - - return c; - } -} diff --git a/contracts/AnchoredView/SymbolConfiguration.sol b/contracts/AnchoredView/SymbolConfiguration.sol deleted file mode 100644 index f8da58d3..00000000 --- a/contracts/AnchoredView/SymbolConfiguration.sol +++ /dev/null @@ -1,261 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity ^0.6.10; -pragma experimental ABIEncoderV2; - - -interface CErc20 { - function underlying() external view returns (address); -} - -contract SymbolConfiguration { - /// special cased anchor oracle keys - address public constant cUsdcAnchorKey = address(1); - address public constant cUsdtAnchorKey = address(1); - address public constant cDaiAnchorKey = address(2); - - /// @notice standard amount for the Dollar - uint public constant oneDollar = 1e6; - - // Address of the oracle key (underlying) for cTokens non special keyed tokens - address public immutable cRepAnchorKey; - address public immutable cWbtcAnchorKey; - address public immutable cBatAnchorKey; - address public immutable cZrxAnchorKey; - - // Frozen prices for SAI and eth, so no oracle key - uint public constant saiAnchorPrice = 5285551943761727; - uint public constant ethAnchorPrice = 1e18; - - /// @notice The CToken contracts addresses - struct CTokens { - address cEthAddress; - address cUsdcAddress; - address cDaiAddress; - address cRepAddress; - address cWbtcAddress; - address cBatAddress; - address cZrxAddress; - address cSaiAddress; - address cUsdtAddress; - } - - enum PriceSource {ANCHOR, FIXED_USD, REPORTER} - enum AnchorSource {ANCHOR, FIXED_USD, FIXED_ETH} - - /// @notice Immutable configuration for a cToken - struct CTokenMetadata { - address cTokenAddress; - address anchorOracleKey; - string openOracleKey; - uint baseUnit; - PriceSource priceSource; - AnchorSource anchorSource; - uint fixedAnchorPrice; - uint fixedReporterPrice; - } - - // The binary representation for token symbols, used for string comparison - bytes32 constant symbolEth = keccak256(abi.encodePacked("ETH")); - bytes32 constant symbolUsdc = keccak256(abi.encodePacked("USDC")); - bytes32 constant symbolDai = keccak256(abi.encodePacked("DAI")); - bytes32 constant symbolRep = keccak256(abi.encodePacked("REP")); - bytes32 constant symbolWbtc = keccak256(abi.encodePacked("BTC")); - bytes32 constant symbolBat = keccak256(abi.encodePacked("BAT")); - bytes32 constant symbolZrx = keccak256(abi.encodePacked("ZRX")); - bytes32 constant symbolSai = keccak256(abi.encodePacked("SAI")); - bytes32 constant symbolUsdt = keccak256(abi.encodePacked("USDT")); - - // Address of the cToken contracts - address public immutable cEthAddress; - address public immutable cUsdcAddress; - address public immutable cDaiAddress; - address public immutable cRepAddress; - address public immutable cWbtcAddress; - address public immutable cBatAddress; - address public immutable cZrxAddress; - address public immutable cSaiAddress; - address public immutable cUsdtAddress; - - /// @param tokens_ The CTokens struct that contains addresses for CToken contracts - constructor(CTokens memory tokens_) public { - cEthAddress = tokens_.cEthAddress; - cUsdcAddress = tokens_.cUsdcAddress; - cDaiAddress = tokens_.cDaiAddress; - cRepAddress = tokens_.cRepAddress; - cWbtcAddress = tokens_.cWbtcAddress; - cBatAddress = tokens_.cBatAddress; - cZrxAddress = tokens_.cZrxAddress; - cSaiAddress = tokens_.cSaiAddress; - cUsdtAddress = tokens_.cUsdtAddress; - - cRepAnchorKey = CErc20(tokens_.cRepAddress).underlying(); - cWbtcAnchorKey = CErc20(tokens_.cWbtcAddress).underlying(); - cBatAnchorKey = CErc20(tokens_.cBatAddress).underlying(); - cZrxAnchorKey = CErc20(tokens_.cZrxAddress).underlying(); - } - - /** - * @notice Returns the CTokenMetadata for a symbol - * @param symbol The symbol to map to cTokenMetadata - * @return The configuration metadata for the symbol - */ - function getCTokenConfig(string memory symbol) public view returns (CTokenMetadata memory) { - address cToken = getCTokenAddress(symbol); - return getCTokenConfig(cToken); - } - - /** - * @notice Returns the CTokenMetadata for an address - * @param cToken The address to map to cTokenMetadata - * @return The configuration metadata for the address - */ - function getCTokenConfig(address cToken) public view returns(CTokenMetadata memory) { - if (cToken == cEthAddress) { - return CTokenMetadata({ - openOracleKey: "ETH", - anchorOracleKey: address(0), - baseUnit: 1e18, - cTokenAddress: cEthAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.FIXED_ETH, - fixedReporterPrice: 0, - fixedAnchorPrice: 1e18 - }); - } - - if (cToken == cUsdcAddress) { - return CTokenMetadata({ - openOracleKey: "USDC", - anchorOracleKey: cUsdcAnchorKey, - baseUnit: 1e6, - cTokenAddress: cUsdcAddress, - priceSource: PriceSource.FIXED_USD, - anchorSource: AnchorSource.FIXED_USD, - fixedReporterPrice: oneDollar, - fixedAnchorPrice: oneDollar - }); - } - - if (cToken == cDaiAddress) { - return CTokenMetadata({ - openOracleKey: "DAI", - anchorOracleKey: cDaiAnchorKey, - baseUnit: 1e18, - cTokenAddress: cDaiAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.ANCHOR, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - if (cToken == cRepAddress) { - return CTokenMetadata({ - openOracleKey: "REP", - anchorOracleKey: cRepAnchorKey, - baseUnit: 1e18, - cTokenAddress: cRepAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.ANCHOR, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - if (cToken == cWbtcAddress) { - return CTokenMetadata({ - openOracleKey: "BTC", - anchorOracleKey: cWbtcAnchorKey, - baseUnit: 1e8, - cTokenAddress: cWbtcAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.ANCHOR, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - if (cToken == cBatAddress) { - return CTokenMetadata({ - openOracleKey: "BAT", - anchorOracleKey: cBatAnchorKey, - baseUnit: 1e18, - cTokenAddress: cBatAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.ANCHOR, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - if (cToken == cZrxAddress){ - return CTokenMetadata({ - openOracleKey: "ZRX", - anchorOracleKey: cZrxAnchorKey, - baseUnit: 1e18, - cTokenAddress: cZrxAddress, - priceSource: PriceSource.REPORTER, - anchorSource: AnchorSource.ANCHOR, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - if (cToken == cSaiAddress){ - return CTokenMetadata({ - openOracleKey: "SAI", - anchorOracleKey: address(0), - baseUnit: 1e18, - cTokenAddress: cSaiAddress, - priceSource: PriceSource.ANCHOR, - anchorSource: AnchorSource.FIXED_ETH, - fixedAnchorPrice: saiAnchorPrice, - fixedReporterPrice: 0 - }); - } - - if (cToken == cUsdtAddress){ - return CTokenMetadata({ - openOracleKey: "USDT", - anchorOracleKey: cUsdtAnchorKey, - baseUnit: 1e6, - cTokenAddress: cUsdtAddress, - priceSource: PriceSource.FIXED_USD, - anchorSource: AnchorSource.FIXED_USD, - fixedReporterPrice: oneDollar, - fixedAnchorPrice: oneDollar - }); - } - - return CTokenMetadata({ - openOracleKey: "UNCONFIGURED", - anchorOracleKey: address(0), - baseUnit: 0, - cTokenAddress: address(0), - priceSource: PriceSource.FIXED_USD, - anchorSource: AnchorSource.FIXED_USD, - fixedReporterPrice: 0, - fixedAnchorPrice: 0 - }); - } - - /** - * @notice Returns the cToken address for symbol - * @param symbol The symbol to map to cToken address - * @return The cToken address for the given symbol - */ - function getCTokenAddress(string memory symbol) public view returns (address) { - bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); - if (symbolHash == symbolEth) return cEthAddress; - if (symbolHash == symbolUsdc) return cUsdcAddress; - if (symbolHash == symbolDai) return cDaiAddress; - if (symbolHash == symbolRep) return cRepAddress; - if (symbolHash == symbolWbtc) return cWbtcAddress; - if (symbolHash == symbolBat) return cBatAddress; - if (symbolHash == symbolZrx) return cZrxAddress; - if (symbolHash == symbolSai) return cSaiAddress; - if (symbolHash == symbolUsdt) return cUsdtAddress; - return address(0); - } -} diff --git a/contracts/DelFiPrice.sol b/contracts/DelFiPrice.sol deleted file mode 100644 index 1826f62f..00000000 --- a/contracts/DelFiPrice.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity ^0.6.10; -pragma experimental ABIEncoderV2; - -import "./OpenOraclePriceData.sol"; -import "./OpenOracleView.sol"; - -/** - * @notice The DelFi Price Feed View - * @author Compound Labs, Inc. - */ -contract DelFiPrice is OpenOracleView { - /// @notice The event emitted when the median price is updated - event PriceUpdated(string symbol, uint64 price); - - /// @notice The event emitted when new prices are posted but the median price is not updated due to the anchor - event PriceGuarded(string symbol, uint64 median, uint64 anchor); - - /// @dev The reporter address whose prices checked against the median for safety - address anchor; - - /// @dev The highest ratio of the new median price to the anchor price that will still trigger the median price to be updated - uint256 upperBoundAnchorRatio; - - /// @dev The lowest ratio of the new median price to the anchor price that will still trigger the median price to be updated - uint256 lowerBoundAnchorRatio; - - /// @notice The mapping of medianized prices per symbol - mapping(string => uint64) public prices; - - /** - * @param data_ Address of the Oracle Data contract - * @param sources_ The reporter addresses whose prices will be used to calculate the median - * @param anchor_ The reporter address whose prices checked against the median for safety - * @param anchorToleranceMantissa_ The tolerance allowed between the anchor and median. A tolerance of 10e16 means a new median that is 10% off from the anchor will still be saved - */ - constructor(OpenOraclePriceData data_, address[] memory sources_, address anchor_, uint anchorToleranceMantissa_) public OpenOracleView(data_, sources_) { - anchor = anchor_; - require(anchorToleranceMantissa_ < 100e16, "Anchor Tolerance is too high"); - upperBoundAnchorRatio = 100e16 + anchorToleranceMantissa_; - lowerBoundAnchorRatio = 100e16 - anchorToleranceMantissa_; - } - - /** - * @notice Primary entry point to post and recalculate prices - * @dev We let anyone pay to post anything, but only sources count for prices - * @param messages The messages to post to the oracle - * @param signatures The signatures for the corresponding messages - */ - function postPrices(bytes[] calldata messages, bytes[] calldata signatures, string[] calldata symbols) external { - require(messages.length == signatures.length, "messages and signatures must be 1:1"); - - // Save the prices - for (uint i = 0; i < messages.length; i++) { - OpenOraclePriceData(address(data)).put(messages[i], signatures[i]); - } - - // Try to update the median - for (uint i = 0; i < symbols.length; i++) { - string memory symbol = symbols[i]; - uint64 medianPrice = medianPrice(symbol, sources); - uint64 anchorPrice = OpenOraclePriceData(address(data)).getPrice(anchor, symbol); - if (anchorPrice == 0) { - emit PriceGuarded(symbol, medianPrice, anchorPrice); - } else { - uint256 anchorRatioMantissa = uint256(medianPrice) * 100e16 / anchorPrice; - // Only update the view's price if the median of the sources is within a bound, and it is a new median - if (anchorRatioMantissa <= upperBoundAnchorRatio && anchorRatioMantissa >= lowerBoundAnchorRatio) { - // only update and emit event if the median price is new, otherwise do nothing - if (prices[symbol] != medianPrice) { - prices[symbol] = medianPrice; - emit PriceUpdated(symbol, medianPrice); - } - } else { - emit PriceGuarded(symbol, medianPrice, anchorPrice); - } - } - } - } - - /** - * @notice Calculates the median price over any set of sources - * @param symbol The symbol to calculate the median price of - * @param sources_ The sources to use when calculating the median price - * @return median The median price over the set of sources - */ - function medianPrice(string memory symbol, address[] memory sources_) public view returns (uint64 median) { - require(sources_.length > 0, "sources list must not be empty"); - - uint N = sources_.length; - uint64[] memory postedPrices = new uint64[](N); - for (uint i = 0; i < N; i++) { - postedPrices[i] = OpenOraclePriceData(address(data)).getPrice(sources_[i], symbol); - } - - uint64[] memory sortedPrices = sort(postedPrices); - // if N is even, get the left and right medians and average them - if (N % 2 == 0) { - uint64 left = sortedPrices[(N / 2) - 1]; - uint64 right = sortedPrices[N / 2]; - uint128 sum = uint128(left) + uint128(right); - return uint64(sum / 2); - } else { - // if N is odd, just return the median - return sortedPrices[N / 2]; - } - } - - /** - * @notice Helper to sort an array of uints - * @param array Array of integers to sort - * @return The sorted array of integers - */ - function sort(uint64[] memory array) private pure returns (uint64[] memory) { - uint N = array.length; - for (uint i = 0; i < N; i++) { - for (uint j = i + 1; j < N; j++) { - if (array[i] > array[j]) { - uint64 tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - } - return array; - } -} \ No newline at end of file diff --git a/contracts/OpenOraclePriceData.sol b/contracts/OpenOraclePriceData.sol index d586ae98..3db8975d 100644 --- a/contracts/OpenOraclePriceData.sol +++ b/contracts/OpenOraclePriceData.sol @@ -34,13 +34,11 @@ contract OpenOraclePriceData is OpenOracleData { * @return The keys that were written */ function put(bytes calldata message, bytes calldata signature) external returns (string memory) { - // Recover the source address - address source = source(message, signature); - - // Decode the message and check the kind - (string memory kind, uint64 timestamp, string memory key, uint64 value) = abi.decode(message, (string, uint64, string, uint64)); - require(keccak256(abi.encodePacked(kind)) == keccak256(abi.encodePacked("prices")), "Kind of data must be 'prices'"); + (address source, uint64 timestamp, string memory key, uint64 value) = decodeMessage(message, signature); + return putInternal(source, timestamp, key, value); + } + function putInternal(address source, uint64 timestamp, string memory key, uint64 value) internal returns (string memory) { // Only update if newer than stored, according to source Datum storage prior = data[source][key]; if (timestamp > prior.timestamp && timestamp < block.timestamp + 60 minutes && source != address(0)) { @@ -49,10 +47,19 @@ contract OpenOraclePriceData is OpenOracleData { } else { emit NotWritten(prior.timestamp, timestamp, block.timestamp); } - return key; } + function decodeMessage(bytes calldata message, bytes calldata signature) internal pure returns (address, uint64, string memory, uint64) { + // Recover the source address + address source = source(message, signature); + + // Decode the message and check the kind + (string memory kind, uint64 timestamp, string memory key, uint64 value) = abi.decode(message, (string, uint64, string, uint64)); + require(keccak256(abi.encodePacked(kind)) == keccak256(abi.encodePacked("prices")), "Kind of data must be 'prices'"); + return (source, timestamp, key, value); + } + /** * @notice Read a single key from an authenticated source * @param source The verifiable author of the data diff --git a/contracts/OpenOracleView.sol b/contracts/OpenOracleView.sol index 01c736eb..7de70afe 100644 --- a/contracts/OpenOracleView.sol +++ b/contracts/OpenOracleView.sol @@ -12,7 +12,7 @@ contract OpenOracleView { /** * @notice The Oracle Data Contract backing this View */ - OpenOracleData public data; + OpenOracleData public priceData; /** * @notice The static list of sources used by this View @@ -29,7 +29,7 @@ contract OpenOracleView { */ constructor(OpenOracleData data_, address[] memory sources_) public { require(sources_.length > 0, "Must initialize with sources"); - data = data_; + priceData = data_; sources = sources_; } } diff --git a/contracts/Uniswap/UniswapAnchoredView.sol b/contracts/Uniswap/UniswapAnchoredView.sol index bbd16d50..81695a84 100644 --- a/contracts/Uniswap/UniswapAnchoredView.sol +++ b/contracts/Uniswap/UniswapAnchoredView.sol @@ -18,16 +18,22 @@ contract UniswapAnchoredView is UniswapConfig { /// @notice The Open Oracle Price Data contract OpenOraclePriceData public immutable priceData; - /// @notice the Open Oracle Reporter + /// @notice The number of wei in 1 ETH + uint public constant ethBaseUnit = 1e18; + + /// @notice A common scaling factor to maintain precision + uint public constant expScale = 1e18; + + /// @notice The Open Oracle Reporter address public immutable reporter; - /// @notice The highest ratio of the new median price to the anchor price that will still trigger the median price to be updated + /// @notice The highest ratio of the new price to the anchor price that will still trigger the price to be updated uint public immutable upperBoundAnchorRatio; - /// @notice The lowest ratio of the new median price to the anchor price that will still trigger the median price to be updated + /// @notice The lowest ratio of the new price to the anchor price that will still trigger the price to be updated uint public immutable lowerBoundAnchorRatio; - /// @notice The minimum amount of time required for the old uniswap price accumulator to be replaced + /// @notice The minimum amount of time in seconds required for the old uniswap price accumulator to be replaced uint public immutable anchorPeriod; /// @notice Official prices by symbol hash @@ -36,34 +42,37 @@ contract UniswapAnchoredView is UniswapConfig { /// @notice Circuit breaker for using anchor price oracle directly, ignoring reporter bool public reporterInvalidated; - /// @notice The old observation for each uniswap market - mapping(address => Observation) public oldObservations; - /// @notice The new observation for each uniswap market - mapping(address => Observation) public newObservations; + /// @notice The old observation for each symbolHash + mapping(bytes32 => Observation) public oldObservations; - /// @notice The event emitted when the stored price is updated - event PriceUpdated(string symbol, uint price); + /// @notice The new observation for each symbolHash + mapping(bytes32 => Observation) public newObservations; /// @notice The event emitted when new prices are posted but the stored price is not updated due to the anchor event PriceGuarded(string symbol, uint reporter, uint anchor); - /// @notice The event emitted when reporter invalidates itself - event ReporterInvalidated(address reporter); + /// @notice The event emitted when the stored price is updated + event PriceUpdated(string symbol, uint price); + + /// @notice The event emitted when anchor price is updated + event AnchorPriceUpdated(string symbol, uint anchorPrice, uint oldTimestamp, uint newTimestamp); /// @notice The event emitted when the uniswap window changes - event UniswapWindowUpdate(address indexed uniswapMarket, uint oldTimestamp, uint newTimestamp, uint oldPrice, uint newPrice); + event UniswapWindowUpdated(bytes32 indexed symbolHash, uint oldTimestamp, uint newTimestamp, uint oldPrice, uint newPrice); - /// @notice The event emitted when anchor price is updated - event AnchorPriceUpdate(address indexed uniswapMarket, uint anchorPrice, uint nowCumulativePrice, uint oldCumulativePrice, uint oldTimestamp); + /// @notice The event emitted when reporter invalidates itself + event ReporterInvalidated(address reporter); bytes32 constant ethHash = keccak256(abi.encodePacked("ETH")); bytes32 constant rotateHash = keccak256(abi.encodePacked("rotate")); /** * @notice Construct a uniswap anchored view for a set of token configurations + * @dev Note that to avoid immature TWAPs, the system must run for at least a single anchorPeriod before using. * @param reporter_ The reporter whose prices are to be used * @param anchorToleranceMantissa_ The percentage tolerance that the reporter may deviate from the uniswap anchor * @param anchorPeriod_ The minimum amount of time required for the old uniswap price accumulator to be replaced + * @param configs The static token configurations which define what prices are supported and how */ constructor(OpenOraclePriceData priceData_, address reporter_, @@ -74,20 +83,23 @@ contract UniswapAnchoredView is UniswapConfig { reporter = reporter_; anchorPeriod = anchorPeriod_; - require(anchorToleranceMantissa_ < 100e16, "anchor tolerance is too high"); - upperBoundAnchorRatio = 100e16 + anchorToleranceMantissa_; - lowerBoundAnchorRatio = 100e16 - anchorToleranceMantissa_; + // Allow the tolerance to be whatever the deployer chooses, but prevent under/overflow (and prices from being 0) + upperBoundAnchorRatio = anchorToleranceMantissa_ > uint(-1) - 100e16 ? uint(-1) : 100e16 + anchorToleranceMantissa_; + lowerBoundAnchorRatio = anchorToleranceMantissa_ < 100e16 ? 100e16 - anchorToleranceMantissa_ : 1; for (uint i = 0; i < configs.length; i++) { TokenConfig memory config = configs[i]; + require(config.baseUnit > 0, "baseUnit must be greater than zero"); address uniswapMarket = config.uniswapMarket; if (config.priceSource == PriceSource.REPORTER) { require(uniswapMarket != address(0), "reported prices must have an anchor"); + bytes32 symbolHash = config.symbolHash; uint cumulativePrice = currentCumulativePrice(config); - oldObservations[uniswapMarket].timestamp = block.timestamp; - newObservations[uniswapMarket].timestamp = block.timestamp; - oldObservations[uniswapMarket].acc = cumulativePrice; - newObservations[uniswapMarket].acc = cumulativePrice; + oldObservations[symbolHash].timestamp = block.timestamp; + newObservations[symbolHash].timestamp = block.timestamp; + oldObservations[symbolHash].acc = cumulativePrice; + newObservations[symbolHash].acc = cumulativePrice; + emit UniswapWindowUpdated(symbolHash, block.timestamp, block.timestamp, cumulativePrice, cumulativePrice); } else { require(uniswapMarket == address(0), "only reported prices utilize an anchor"); } @@ -99,7 +111,7 @@ contract UniswapAnchoredView is UniswapConfig { * @param symbol The symbol to fetch the price of * @return Price denominated in USD, with 6 decimals */ - function price(string memory symbol) public view returns (uint) { + function price(string memory symbol) external view returns (uint) { TokenConfig memory config = getTokenConfigBySymbol(symbol); return priceInternal(config); } @@ -110,7 +122,7 @@ contract UniswapAnchoredView is UniswapConfig { if (config.priceSource == PriceSource.FIXED_ETH) { uint usdPerEth = prices[ethHash]; require(usdPerEth > 0, "ETH price not set, cannot convert to dollars"); - return mul(usdPerEth, config.fixedPrice) / config.baseUnit; + return mul(usdPerEth, config.fixedPrice) / ethBaseUnit; } } @@ -118,10 +130,12 @@ contract UniswapAnchoredView is UniswapConfig { * @notice Get the underlying price of a cToken * @dev Implements the PriceOracle interface for Compound v2. * @param cToken The cToken address for price retrieval - * @return The price for the given cToken address + * @return Price denominated in USD, with 18 decimals, for the given cToken address */ - function getUnderlyingPrice(address cToken) public view returns (uint) { + function getUnderlyingPrice(address cToken) external view returns (uint) { TokenConfig memory config = getTokenConfigByCToken(cToken); + // Comptroller needs prices in the format: ${raw price} * 1e(36 - baseUnit) + // Since the prices in this view have 6 decimals, we must scale them by 1e(36 - 6 - baseUnit) return mul(1e30, priceInternal(config)) / config.baseUnit; } @@ -144,28 +158,31 @@ contract UniswapAnchoredView is UniswapConfig { // Try to update the view storage for (uint i = 0; i < symbols.length; i++) { - TokenConfig memory config = getTokenConfigBySymbol(symbols[i]); - string memory symbol = symbols[i]; - bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); - if (source(messages[i], signatures[i]) != reporter) continue; - - uint reporterPrice = priceData.getPrice(reporter, symbol); - uint anchorPrice; - if (symbolHash == ethHash) { - anchorPrice = ethPrice; - } else { - anchorPrice = fetchAnchorPrice(config, ethPrice); - } + postPriceInternal(symbols[i], ethPrice); + } + } - if (reporterInvalidated == true) { - prices[symbolHash] = anchorPrice; - emit PriceUpdated(symbol, anchorPrice); - } else if (isWithinAnchor(reporterPrice, anchorPrice)) { - prices[symbolHash] = reporterPrice; - emit PriceUpdated(symbol, reporterPrice); - } else { - emit PriceGuarded(symbol, reporterPrice, anchorPrice); - } + function postPriceInternal(string memory symbol, uint ethPrice) internal { + TokenConfig memory config = getTokenConfigBySymbol(symbol); + require(config.priceSource == PriceSource.REPORTER, "only reporter prices get posted"); + + bytes32 symbolHash = keccak256(abi.encodePacked(symbol)); + uint reporterPrice = priceData.getPrice(reporter, symbol); + uint anchorPrice; + if (symbolHash == ethHash) { + anchorPrice = ethPrice; + } else { + anchorPrice = fetchAnchorPrice(symbol, config, ethPrice); + } + + if (reporterInvalidated) { + prices[symbolHash] = anchorPrice; + emit PriceUpdated(symbol, anchorPrice); + } else if (isWithinAnchor(reporterPrice, anchorPrice)) { + prices[symbolHash] = reporterPrice; + emit PriceUpdated(symbol, reporterPrice); + } else { + emit PriceGuarded(symbol, reporterPrice, anchorPrice); } } @@ -190,17 +207,18 @@ contract UniswapAnchoredView is UniswapConfig { } /** - * @dev Fetches the current eth/usd price from unsiwap, with 6 decimals of precision. + * @dev Fetches the current eth/usd price from uniswap, with 6 decimals of precision. * Conversion factor is 1e18 for eth/usdc market, since we decode uniswap price statically with 18 decimals. */ function fetchEthPrice() internal returns (uint) { - return fetchAnchorPrice(getTokenConfigBySymbolHash(ethHash), 1e18); + return fetchAnchorPrice("ETH", getTokenConfigBySymbolHash(ethHash), ethBaseUnit); } /** * @dev Fetches the current token/usd price from uniswap, with 6 decimals of precision. + * @param conversionFactor 1e18 if seeking the ETH price, and a 6 decimal ETH-USDC price in the case of other assets */ - function fetchAnchorPrice(TokenConfig memory config, uint conversionFactor) internal virtual returns (uint) { + function fetchAnchorPrice(string memory symbol, TokenConfig memory config, uint conversionFactor) internal virtual returns (uint) { (uint nowCumulativePrice, uint oldCumulativePrice, uint oldTimestamp) = pokeWindowValues(config); // This should be impossible, but better safe than sorry @@ -208,18 +226,22 @@ contract UniswapAnchoredView is UniswapConfig { uint timeElapsed = block.timestamp - oldTimestamp; // Calculate uniswap time-weighted average price + // Underflow is a property of the accumulators: https://uniswap.org/audit.html#orgc9b3190 FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(uint224((nowCumulativePrice - oldCumulativePrice) / timeElapsed)); - uint anchorPriceUnscaled = mul(priceAverage.decode112with18(), conversionFactor); + uint rawUniswapPriceMantissa = priceAverage.decode112with18(); + uint unscaledPriceMantissa = mul(rawUniswapPriceMantissa, conversionFactor); uint anchorPrice; - // Adjust anchor price to val * 1e6 decimals format + // Adjust rawUniswapPrice according to the units of the non-ETH asset + // In the case of ETH, we would have to scale by 1e6 / USDC_UNITS, but since baseUnit2 is 1e6 (USDC), it cancels if (config.isUniswapReversed) { - anchorPrice = anchorPriceUnscaled / config.baseUnit; + // unscaledPriceMantissa * ethBaseUnit / config.baseUnit / expScale, but we simplify bc ethBaseUnit == expScale + anchorPrice = unscaledPriceMantissa / config.baseUnit; } else { - anchorPrice = mul(anchorPriceUnscaled, config.baseUnit) / 1e36; + anchorPrice = mul(unscaledPriceMantissa, config.baseUnit) / ethBaseUnit / expScale; } - emit AnchorPriceUpdate(config.uniswapMarket, anchorPrice, nowCumulativePrice, oldCumulativePrice, oldTimestamp); + emit AnchorPriceUpdated(symbol, anchorPrice, oldTimestamp, block.timestamp); return anchorPrice; } @@ -229,23 +251,22 @@ contract UniswapAnchoredView is UniswapConfig { * Update new and old observations of lagging window if period elapsed. */ function pokeWindowValues(TokenConfig memory config) internal returns (uint, uint, uint) { - address uniswapMarket = config.uniswapMarket; + bytes32 symbolHash = config.symbolHash; uint cumulativePrice = currentCumulativePrice(config); - Observation storage newObservation = newObservations[uniswapMarket]; - Observation storage oldObservation = oldObservations[uniswapMarket]; + Observation memory newObservation = newObservations[symbolHash]; // Update new and old observations if elapsed time is greater than or equal to anchor period uint timeElapsed = block.timestamp - newObservation.timestamp; if (timeElapsed >= anchorPeriod) { - emit UniswapWindowUpdate(uniswapMarket, oldObservation.timestamp, newObservation.timestamp, oldObservation.acc, newObservation.acc); - oldObservation.timestamp = newObservation.timestamp; - oldObservation.acc = newObservation.acc; + oldObservations[symbolHash].timestamp = newObservation.timestamp; + oldObservations[symbolHash].acc = newObservation.acc; - newObservation.timestamp = block.timestamp; - newObservation.acc = cumulativePrice; + newObservations[symbolHash].timestamp = block.timestamp; + newObservations[symbolHash].acc = cumulativePrice; + emit UniswapWindowUpdated(config.symbolHash, newObservation.timestamp, block.timestamp, newObservation.acc, cumulativePrice); } - return (cumulativePrice, oldObservation.acc, oldObservation.timestamp); + return (cumulativePrice, oldObservations[symbolHash].acc, oldObservations[symbolHash].timestamp); } /** @@ -256,8 +277,8 @@ contract UniswapAnchoredView is UniswapConfig { * @param signature The fingerprint of the data + private key */ function invalidateReporter(bytes memory message, bytes memory signature) external { - (string memory decoded_message, ) = abi.decode(message, (string, address)); - require(keccak256(abi.encodePacked(decoded_message)) == rotateHash, "invalid message must be 'rotate'"); + (string memory decodedMessage, ) = abi.decode(message, (string, address)); + require(keccak256(abi.encodePacked(decodedMessage)) == rotateHash, "invalid message must be 'rotate'"); require(source(message, signature) == reporter, "invalidation message must come from the reporter"); reporterInvalidated = true; emit ReporterInvalidated(reporter); diff --git a/contracts/Uniswap/UniswapConfig.sol b/contracts/Uniswap/UniswapConfig.sol index 2bdce23f..cc05feec 100644 --- a/contracts/Uniswap/UniswapConfig.sol +++ b/contracts/Uniswap/UniswapConfig.sol @@ -8,7 +8,15 @@ interface CErc20 { } contract UniswapConfig { - enum PriceSource {FIXED_ETH, FIXED_USD, REPORTER} + /// @dev Describe how to interpret the fixedPrice in the TokenConfig. + enum PriceSource { + FIXED_ETH, /// implies the fixedPrice is a constant multiple of the ETH price (which varies) + FIXED_USD, /// implies the fixedPrice is a constant multiple of the USD price (which is 1) + REPORTER /// implies the price is set by the reporter + } + + /// @dev Describe how the USD price should be determined for an asset. + /// There should be 1 TokenConfig object for each supported asset, passed in the constructor. struct TokenConfig { address cToken; address underlying; @@ -20,7 +28,11 @@ contract UniswapConfig { bool isUniswapReversed; } + /// @notice The max number of tokens this contract is hardcoded to support + /// @dev Do not change this variable without updating all the fields throughout the contract. uint public constant maxTokens = 30; + + /// @notice The number of tokens this contract actually supports uint public immutable numTokens; address internal immutable cToken00; @@ -271,6 +283,10 @@ contract UniswapConfig { bool internal immutable isUniswapReversed28; bool internal immutable isUniswapReversed29; + /** + * @notice Construct an immutable store of configs into the contract data + * @param configs The configs for the supported assets + */ constructor(TokenConfig[] memory configs) public { require(configs.length <= maxTokens, "too many configs"); numTokens = configs.length; @@ -644,6 +660,11 @@ contract UniswapConfig { return uint(-1); } + /** + * @notice Get the i-th config, according to the order they were passed in originally + * @param i The index of the config to get + * @return The config object + */ function getTokenConfig(uint i) public view returns (TokenConfig memory) { require(i < numTokens, "token config not found"); @@ -681,10 +702,20 @@ contract UniswapConfig { if (i == 29) return TokenConfig({cToken: cToken29, underlying: underlying29, symbolHash: symbolHash29, baseUnit: baseUnit29, priceSource: priceSource29, fixedPrice: fixedPrice29, uniswapMarket: uniswapMarket29, isUniswapReversed: isUniswapReversed29}); } + /** + * @notice Get the config for symbol + * @param symbol The symbol of the config to get + * @return The config object + */ function getTokenConfigBySymbol(string memory symbol) public view returns (TokenConfig memory) { return getTokenConfigBySymbolHash(keccak256(abi.encodePacked(symbol))); } + /** + * @notice Get the config for the symbolHash + * @param symbolHash The keccack256 of the symbol of the config to get + * @return The config object + */ function getTokenConfigBySymbolHash(bytes32 symbolHash) public view returns (TokenConfig memory) { uint index = getSymbolHashIndex(symbolHash); if (index != uint(-1)) { @@ -694,6 +725,12 @@ contract UniswapConfig { revert("token config not found"); } + /** + * @notice Get the config for the cToken + * @dev If a config for the cToken is not found, falls back to searching for the underlying. + * @param cToken The address of the cToken of the config to get + * @return The config object + */ function getTokenConfigByCToken(address cToken) public view returns (TokenConfig memory) { uint index = getCTokenIndex(cToken); if (index != uint(-1)) { @@ -703,6 +740,11 @@ contract UniswapConfig { return getTokenConfigByUnderlying(CErc20(cToken).underlying()); } + /** + * @notice Get the config for an underlying asset + * @param underlying The address of the underlying asset of the config to get + * @return The config object + */ function getTokenConfigByUnderlying(address underlying) public view returns (TokenConfig memory) { uint index = getUnderlyingIndex(underlying); if (index != uint(-1)) { @@ -711,4 +753,4 @@ contract UniswapConfig { revert("token config not found"); } -} \ No newline at end of file +} diff --git a/contracts/Uniswap/UniswapLib.sol b/contracts/Uniswap/UniswapLib.sol index 5a5abe47..4234f784 100644 --- a/contracts/Uniswap/UniswapLib.sol +++ b/contracts/Uniswap/UniswapLib.sol @@ -2,6 +2,8 @@ pragma solidity ^0.6.10; +// Based on code from https://github.com/Uniswap/uniswap-v2-periphery + // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) library FixedPoint { // range: [0, 2**112 - 1] @@ -24,7 +26,7 @@ library FixedPoint { // (x * 1e18) >> 112 // without risk of overflowing, e.g.: // (x) / 2 ** (112 - lg(1e18)) - return uint(self._x) / 5192296858534816; + return uint(self._x) / 5192296858534827; } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6ae90a64..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,78 +0,0 @@ -version: '3' -services: - ganache: - image: trufflesuite/ganache-cli:v6.6.0 - command: ganache -d - ports: - - "9999:8545" - - deployer: - build: . - command: "sh -c ' - apk add jq; - SADDLE_BUILD=/build npx saddle compile; - SADDLE_BUILD=/build npx saddle contracts; - GAS=6721975 SADDLE_BUILD=/build npx saddle deploy OpenOraclePriceData; - GAS=6721975 SADDLE_BUILD=/build npx saddle deploy DelFiPrice $$(cat /build/development.json | jq -r .OpenOraclePriceData) $$REPORTER_1_ADDRESS,$$REPORTER_2_ADDRESS:array $$ANCHOR_ADDRESS $$ANCHOR_MANTISSA_HEX - '" - environment: - - PROVIDER=http://ganache:8545 - env_file: - - test_keys.env - depends_on: - - ganache - volumes: - - ./.dockerbuild:/build - - reporter-1: - build: sdk/javascript - command: npm run start -- - --private_key $$REPORTER_1_PRIVATE_KEY - --script examples/fixed.js - ports: - - "3001:3000" - env_file: - - test_keys.env - - reporter-2: - build: sdk/javascript - command: npm run start -- - --private_key $$REPORTER_2_PRIVATE_KEY - --script examples/fixed.js - ports: - - "3002:3000" - env_file: - - test_keys.env - - anchor-reporter: - build: sdk/javascript - command: npm run start -- - --private_key $$ANCHOR_PRIVATE_KEY - --script examples/fixed.js - ports: - - "3003:3000" - env_file: - - test_keys.env - - poster: - build: poster - command: "sh -c ' - apk add jq; - while ! grep -q DelFiPrice /build/development.json; do echo waiting for /build/development.json; sleep 1; done; - cat /build/development.json; - npm run start -- - --poster_key $$POSTER_KEY - --sources http://anchor-reporter:3000/prices.json,http://reporter-1:3000/prices.json,http://reporter-2:3000/prices.json - --view_address $$(cat /build/development.json | jq -r .DelFiPrice) - --web3_provider http://ganache:8545 - '" - depends_on: - - deployer - - reporter-1 - - reporter-2 - - anchor-reporter - restart: "always" - volumes: - - ./.dockerbuild:/build - env_file: - - test_keys.env diff --git a/jest.config.js b/jest.config.js index 6397f6a8..9890e485 100644 --- a/jest.config.js +++ b/jest.config.js @@ -132,7 +132,7 @@ module.exports = { testEnvironment: "node", // Timeout of a test in milliseconds, default timeout is 5000 ms. - testTimeout: 10000, + testTimeout: 300000, // Options that will be passed to the testEnvironment // testEnvironmentOptions: {}, diff --git a/package.json b/package.json index a607c661..6e7d81dd 100644 --- a/package.json +++ b/package.json @@ -8,24 +8,26 @@ "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0", - "eth-saddle": "^0.1.17", + "eth-saddle": "0.1.19", "web3": "^1.2.4", "yargs": "^15.0.2" }, "devDependencies": { - "docker-compose": "^0.23.1", + "istanbul": "^0.4.5", "jest": "^24.9.0", "jest-cli": "^24.9.0", "jest-junit": "^10.0.0" }, "scripts": { - "test": "npx saddle test", + "test": "script/test", + "coverage": "script/coverage", "console": "npx -n --experimental-repl-await saddle console", "compile": "npx saddle compile", "deploy": "npx saddle deploy" }, "resolutions": { "**/ganache-core": "https://github.com/compound-finance/ganache-core.git#compound", - "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz" + "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz", + "**/sha3": "^2.1.2" } } diff --git a/poster/README.md b/poster/README.md index c86c3cf6..e553275b 100644 --- a/poster/README.md +++ b/poster/README.md @@ -1,50 +1,76 @@ -## The Open Oracle Poster +## The Open Price Feed Poster The poster is a simple application to pull prices from a set of source feeds and post them to the blockchain. The posters main job is to make sure that the requested source data is posted to the Ethereum blockchain, and thus is concerned about nonces, gas prices, etc. ## Installation -The DelFi poster can be run as a standalone process or as a module for configuration. To install as a global process: +The Open Price Feed poster can be run as a standalone process or as a module for configuration. To install as a global process: -``` +```sh yarn global add open-oracle-poster +# npm install -g open-oracle-poster ``` Or, if you plan on customizing the poster, you can install in a project: -``` +```sh yarn add open-oracle-poster +# npm install open-oracle-poster ``` -## Running +## Command-Line Interface + +The poster is a simple CLI to post prices. The following options are available: + +| Option | Description | +| ------ | ----------- | +| `--sources`, `-s` | sources to pull price messages from, a list of https endpoints created by open oracle reporters serving open oracle payloads as json. For complex sources, such as Coinbase, this can be JSON-encoded. Note: specify multiple times to specify multiple sources. | +| `--poster-key`, `-k` | Private key holding enough gas to post (try: `file:` or `env:`) | +| `--view-function`, `-f` | Function signature for the view (e.g. postPrices(bytes[],bytes[])) | +| `--web3-provider` | Web3 provider | +| `--view-address` | Address of open oracle view to post through | +| `--timeout`, `-t` | how many seconds to wait before retrying with more gas, defaults to 180 | +| `--asset`, `-a` | List of assets to post prices for. Pass multiple times to specify multiple assets. | + +### Sources + +A source can simply be a URL, e.g. `http://localhost:3000/prices.json` or you can pass a JSON-encoded structure for complex sources. Specifically, for the Coinbase API, you can use the following structure: + +```json +"{\"source\": \"coinbase\", \"endpoint\": \"https://api.pro.coinbase.com/oracle\", \"api_key_id\": \"\", \"api_secret\": \"\", \"api_passphrase\": \"\"}" +``` -The poster requires 5 arguments to run, with one optional argument. - --sources, -s sources to pull price messages from, a list of https endpoints created by open oracle reporters serving open oracle payloads as json - --poster_key, -k Private key holding enough gas to post (try: `file: or env:)` - --view_function, -f Function signature for the view (e.g. postPrices(bytes[],bytes[])) - --web3_provider Web 3 provider - --view_address address of open oracle view to post through - --timeout, -t how many seconds to wait before retrying with more gas, defaults to 180 +### Examples To run as standalone from this project's root, simply invoke the start script. + +```sh + yarn run start --view-address=0xViewAddress --poster-key=0xWalletWithGas --sources=http://localhost:3000/prices.json ``` - yarn run start --view_address=0xViewAddress --poster_key=0xWalletWithGas --sources=http://localhost:3000/prices.json + +Here's an example for the Kovan testnet: + +```sh + yarn prepare && yarn run start --web3-provider=https://kovan-eth.compound.finance/ --view-address=0x60F1FFB2FE2bFE6CFFA0A66e258B623f06E1949F --poster-key="$(cat ~/.ethereum/kovan)" --sources="{\"source\": \"coinbase\", \"endpoint\": \"https://api.pro.coinbase.com/oracle\", \"api_key_id\": \"$COINBASE_API_KEY\", \"api_secret\": \"$COINBASE_API_SECRET\", \"api_passphrase\": \"$COINBASE_API_PASSPHRASE\"}" ``` -Otherwise, you can include the DelFi poster in an app for configuration: +## Running in JavaScript + +You can include the Open Price Feed poster in an app for configuration: -```typescript -import poster from 'delfi-poster'; +```js +import poster from 'open-oracle-poster'; import Web3 from 'web3'; // sample arguments, fill these in with real data :) -// let sources = [list of sources]; -// let posterKey = ...a key to a wallet holding eth for gas; -// let viewAddress = "0xDelfiPriceView"; -// let viewFunction = ...view function signature e.g. 'postPrices(bytes[],bytes[],string[])'; -// let web3Provider = new Web3("web3Node.com", undefined, {transactionPollingTimeout: 180}); -await poster.main(sources, posterKey, viewAddress, viewFunction, web3Provider); +let sources: string[] = /* [list of sources, possibly JSON-encoded] */; +let posterKey: string = /* ...a key to a wallet holding eth for gas */; +let viewAddress: string = /* "0xDelfiPriceView" */; +let viewFunction: string = 'postPrices(bytes[],bytes[],string[])' /* ...view function signature */; +let provider = new Web3(); + +await poster.main(sources, posterKey, viewAddress, viewFunction, provider); ``` ## Testing @@ -60,3 +86,7 @@ To run a single test run: ``` yarn test tests/poster_test.ts ``` + +## Contributing + +For all contributions, please open an issue or pull request to discuss. Ensure that all test cases are passing and that top-level (integration) tests also pass (see the `open-oracle` root project). See top-level README for license notice and contribution agreement. diff --git a/poster/package.json b/poster/package.json index e56a04e2..a9e38444 100644 --- a/poster/package.json +++ b/poster/package.json @@ -1,7 +1,7 @@ { "name": "open-oracle-poster", - "version": "1.0.21", - "description": "A Customizable Poster for the Compound Open Oracle", + "version": "1.1.0", + "description": "A Customizable Poster for the Compound Open Price Feed", "main": ".tsbuilt/poster.js", "bin": ".tsbuilt/index.js", "repository": "https://compound.finance/open-oracle", @@ -21,14 +21,14 @@ "typescript": "^3.7.3" }, "dependencies": { - "@types/web3": "^1.2.2", "bignumber.js": "^9.0.0", "ganache-core": "github:compound-finance/ganache-core.git#compound", "node-fetch": "^2.6.0", - "web3": "1.2.4", + "web3": "1.2.9", "yargs": "^15.0.2" }, "resolutions": { - "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz" + "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz", + "**/sha3": "^2.1.2" } } diff --git a/poster/src/index.ts b/poster/src/index.ts index 20f07fd1..a14b4316 100644 --- a/poster/src/index.ts +++ b/poster/src/index.ts @@ -4,27 +4,60 @@ import Web3 from 'web3'; import yargs from 'yargs'; async function run() { - const argv = yargs + const parsed = yargs .env('POSTER') .option('sources', {alias: 's', description: 'Sources to pull price messages from, a list of https endpoints created by open oracle reporters serving open oracle payloads as json', type: 'string'}) - .option('poster_key', {alias: 'k', description: 'Private key holding enough gas to post (try: `file: or env:)`', type: 'string'}) - .option('view_address', {alias: 'a', description: 'Address of open oracle view to post through', type: 'string'}) - .option('view_function', {alias: 'f', description: 'Function signature for the view', type: 'string', default: 'postPrices(bytes[],bytes[],string[])'}) - .option('web3_provider', {description: 'Web 3 provider', type: 'string', default: 'http://127.0.0.1:8545'}) + .option('poster-key', {alias: 'k', description: 'Private key holding enough gas to post (try: `file: or env:)`', type: 'string'}) + .option('view-address', {alias: 'v', description: 'Address of open oracle view to post through', type: 'string'}) + .option('view-function', {alias: 'f', description: 'Function signature for the view', type: 'string', default: 'postPrices(bytes[],bytes[],string[])'}) + .option('web3-provider', {description: 'Web 3 provider', type: 'string', default: 'http://127.0.0.1:8545'}) .option('timeout', {alias: 't', description: 'how many seconds to wait before retrying with more gas', type: 'number', default: 180}) - .option('gas_limit', {alias: 'g', description: 'how much gas to send', type: 'number', default: 4000000}) - .option('price_delta', {alias: 'd', description: 'the min required difference between new and previous asset price for price update on blockchain', type: 'number', default: 1}) - .option('supported_assets', {alias: 'sa', description: 'A list of supported token names for posting prices', type: 'string', default: 'BTC,ETH,DAI,REP,ZRX,BAT,KNC,LINK,COMP'}) + .option('gas-limit', {alias: 'g', description: 'how much gas to send', type: 'number', default: 4000000}) + .option('gas-price', {alias: 'gp', description: 'gas price', type: 'number'}) + .option('price-delta', {alias: 'd', description: 'the min required difference between new and previous asset price for price update on blockchain', type: 'number', default: 1}) + .option('asset', {alias: 'a', description: 'A list of supported token names for posting prices', type: 'array', default: ['BTC', 'ETH', 'DAI', 'REP', 'ZRX', 'BAT', 'KNC', 'LINK', 'COMP']}) + .option('testnet-world', {alias: 'tw', description: 'An option to use mocked uniswap token pairs with data from mainnet', type: 'boolean', default: false}) + .option('testnet-uniswap-pairs', {alias: 'tup', description: 'A list of uniswap testnet pairs for all assets', type: 'array'}) + .option('mainnet-uniswap-pairs', {alias: 'mup', description: 'A list of uniswap mainnet pairs for all assets', type: 'array'}) + .help() .alias('help', 'h') - .demandOption(['poster_key', 'sources', 'view_function', 'web3_provider', 'view_address'], 'Provide all the arguments') + .demandOption(['poster-key', 'sources', 'view-function', 'web3-provider', 'view-address'], 'Provide all the arguments') .argv; + const sources = Array.isArray(parsed['sources']) ? parsed['sources'] : [ parsed['sources'] ]; + const poster_key = parsed['poster-key']; + const view_address = parsed['view-address']; + const view_function = parsed['view-function']; + const web3_provider = parsed['web3-provider']; + const timeout = parsed['timeout']; + const gas_limit = parsed['gas-limit']; + const gas_price = parsed['gas-price']; + const price_delta = parsed['price-delta']; + const assets = parsed['asset']; + + // parameters for testnets only + const mocked_world = parsed['testnet-world']; + const testnet_pairs = parsed['testnet-uniswap-pairs']; + const mainnet_pairs = parsed['mainnet-uniswap-pairs']; + const pairs = {testnet: {}, mainnet: {}}; + if (mocked_world) { + if (testnet_pairs.length != mainnet_pairs.length || testnet_pairs.length != assets.length) { + throw new TypeError("For each asset mainnet and testnet pairs should be provided, all lengths should match") + } + assets.forEach((asset, index) => { + pairs['testnet'][asset] = testnet_pairs[index]; + pairs['mainnet'][asset] = mainnet_pairs[index]; + }); + } + // posting promise will reject and retry once with higher gas after this timeout - const web3 = await new Web3(argv.web3_provider); - web3.eth.transactionPollingTimeout = argv.timeout; + const web3 = await new Web3(web3_provider); + web3.eth.transactionPollingTimeout = timeout; - if (argv.web3_provider.match(/.*:8545$/)) { + // TODO: We should probably just have a `network` var here, or + // pass this in as an option. + if (web3_provider.match(/.*:8545$/)) { // confirm immediately in dev web3.eth.transactionConfirmationBlocks = 1 } else { @@ -32,11 +65,11 @@ async function run() { } try { - await main(argv.sources, argv.poster_key, argv.view_address, argv.view_function, argv.gas_limit, argv.price_delta, argv.supported_assets, web3); - console.log("main completed") + await main(sources, poster_key, view_address, view_function, gas_limit, gas_price, price_delta, assets, mocked_world, pairs, web3); + console.log('Poster run completed successfully'); } catch (e) { console.error(`Poster failed to run`, e); } } -run().then(console.log); +run(); diff --git a/poster/src/interfaces.ts b/poster/src/interfaces.ts index 7f4d90c7..59647e68 100644 --- a/poster/src/interfaces.ts +++ b/poster/src/interfaces.ts @@ -1,20 +1,40 @@ -// --- open oracle interfaces --- +/* --- Open Price Feed Interfaces --- + * + * In the view, there is a function that will write message values to the + * the Open Price Feed data contract if the posted values are more valid + * ( e.g. later timestamp ) than what already is in the data contract. + * + * The view will also write to its own storage cache an aggregated value + * based on the state of data contract. + * + * A payload for an open price feed view comprises two fields: + * 1. ABI-encoded values to be written to the open price feed data contract + * 2. The attestor's signature on a hash of that message + */ -// In the view, there is a function that will write message values to the -// the open oracle data contract if the posted values are more valid -// ( e.g. later timestamp ) than what already is in the data contract. -// -// The view will also write to it's own storage cache an aggregated value -// based on the state of data contract. - -// A payload for an open oracle view comprises 2 fields: -// 1. Abi encoded values to be written to the open oracle data contract -// 2. The attestor's signature on a hash of that message -interface DelFiReporterPayload { - // ABI encoded values to be written to the open oracle data contract. - messages: string[], - // The signature of the attestor to these values. The values in 'message' +interface OpenPriceFeedPayload { + // ABI-encoded values to be written to the open oracle data contract. + messages: string[] + // The signature of the attestor to these values. The values in `message` // will be stored in a mapping under this signer's public address. - signatures: string[], + signatures: string[] prices: {[symbol: string]: string } }; + +interface DecodedMessage { + dataType: string + timestamp: number + symbol: string + price: number +} + +interface OpenPriceFeedItem { + message: string + signature: string + dataType: string + timestamp: number + symbol: string + price: number + source: string + prev: number +}; diff --git a/poster/src/mainnet_uniswap_mocker.ts b/poster/src/mainnet_uniswap_mocker.ts new file mode 100644 index 00000000..33036ad6 --- /dev/null +++ b/poster/src/mainnet_uniswap_mocker.ts @@ -0,0 +1,74 @@ + +import Web3 from 'web3'; +import { read, readMany, encode } from './util'; +import { postWithRetries } from './post_with_retries'; + +const mainnetWeb3 = new Web3(new Web3.providers.HttpProvider('https://mainnet-eth.compound.finance/')); + +async function getReserves(pair: string) { + return await readMany( + pair, + "getReserves()", + [], + ["uint112","uint112","uint32"], + mainnetWeb3 + ); +} + +async function getCumulativePrices(pair: string) { + const price0 = await read( + pair, + "price0CumulativeLast()", + [], + "uint256", + mainnetWeb3 + ); + const price1 = await read( + pair, + "price1CumulativeLast()", + [], + "uint256", + mainnetWeb3 + ); + + return [price0, price1]; +} + +function buildTrxData(reserve0, reserve1, blockTimestampLast, price0, price1, functionSig){ + return encode( + functionSig, + [reserve0, reserve1, blockTimestampLast, price0, price1] + ); +} + +async function mockUniswapTokenPair(symbol: string, senderKey: string, pairs, gas: number, gasPrice: number, web3: Web3) { + const testnetPair = pairs.testnet[symbol]; + const mainnetPair = pairs.mainnet[symbol]; + const reserves = await getReserves(mainnetPair); + const cumulatives = await getCumulativePrices(mainnetPair); + + const reserve0 = reserves[0]; + const reserve1 = reserves[1]; + const blockTimestampLast = reserves[2]; + const cumulativePrice0 = cumulatives[0]; + const cumulativePrice1 = cumulatives[1]; + + console.log(`Mocking uniswap token pair for ${symbol} with results--> ${reserve0} ${reserve1} ${blockTimestampLast} ${cumulativePrice0} ${cumulativePrice1}`); + + const functionSig = "update(uint256,uint256,uint256,uint256,uint256)"; + const trxData = buildTrxData(reserve0, reserve1, blockTimestampLast, cumulativePrice0, cumulativePrice1, functionSig); + const trx = { + data: trxData, + to: testnetPair, + gasPrice: gasPrice, + gas: gas + }; + + return await postWithRetries(trx, senderKey, web3); +} + +export async function mockUniswapTokenPairs(assets: string[], senderKey: string, pairs, gas: number, gasPrice: number, web3: Web3) { + for (const asset of assets) { + await mockUniswapTokenPair(asset.toUpperCase(), senderKey, pairs, gas, gasPrice, web3); + } +} \ No newline at end of file diff --git a/poster/src/post_with_retries.ts b/poster/src/post_with_retries.ts index 766cf830..85222490 100644 --- a/poster/src/post_with_retries.ts +++ b/poster/src/post_with_retries.ts @@ -1,43 +1,73 @@ import Web3 from 'web3'; +import Utils from 'web3-utils'; import { TransactionConfig, TransactionReceipt } from 'web3-core'; -async function postWithRetries(transaction: TransactionConfig, signerKey: string, web3: Web3, retries: number = 3) { - for (let i = 0; i < retries; i++) { - console.log(`Running Open Oracle Poster (attempt ${i})...`); - console.log(transaction) - if (!web3.utils.isHexStrict(signerKey)) { - signerKey = "0x" + signerKey; - if (!web3.utils.isHexStrict(signerKey)) { - throw "Invalid Private Key" - } +function ensureHex(val: string, type: string): string { + if (Utils.isHexStrict(val)) { + return val; + } + + const val0x = `0x${val}`; + if (Utils.isHexStrict(val0x)) { + return val0x; + } + + throw new Error(`Invalid hex for ${type}: got \`${val}\``); +} + +function isUnderpriced(e) { + return e.message === 'Returned error: replacement transaction underpriced'; +} + +function isTimeout(e) { + return /Error: Timeout exceeded during the transaction confirmation process. Be aware the transaction could still get confirmed!/.test(e.error); +} + +const SLEEP_DURATION = 3000; // 3s +const RETRIES = 3; +const GAS_ADJUSTMENT = 1.2; // Increase gas by this percentage each retry + +async function postWithRetries(transaction: TransactionConfig, signerKey: string, web3: Web3, retries: number = RETRIES, attempt: number = 0) { + console.log(`Running Open Price Feed Poster${attempt > 0 ? ` [attempt ${attempt}]` : ''}...`); + + signerKey = ensureHex(signerKey, 'private key'); + + let pubKey = web3.eth.accounts.privateKeyToAccount(signerKey) + + console.log(`Posting from account: ${pubKey.address}`); + + try { + return await signAndSend(transaction, signerKey, web3); + } catch (e) { + console.debug({transaction}); + console.warn('Failed to post Open Price Feed:'); + console.warn(e); + // If higher gas price will help, try again. Otherwise, really throw. + + if (isUnderpriced(e) || isTimeout(e)) { + transaction = { + ...transaction, + gasPrice: Math.floor(Number(transaction.gasPrice) * GAS_ADJUSTMENT) + }; } - let pubKey = web3.eth.accounts.privateKeyToAccount(signerKey) - console.log("posting from: ", pubKey.address) - let nonce = await web3.eth.getTransactionCount(pubKey.address) - transaction.nonce = nonce - console.log(transaction) - try { - return await signAndSend(transaction, signerKey, web3); - } catch (e) { - console.warn(e); - // If higher gas price will help, try again. Otherwise, really throw. - if (e.message === "Returned error: replacement transaction underpriced") { - transaction.gasPrice = Math.floor(Number(transaction.gasPrice) * 0.2); - } else if (/Error: Timeout exceeded during the transaction confirmation process. Be aware the transaction could still get confirmed!/.test(e.error)) { - transaction.gasPrice = Math.floor(Number(transaction.gasPrice) * 0.2); - } else { - await (new Promise(okay => setTimeout(okay, 3000))); - } + + if (retries > 0) { + // Sleep for some time before retrying + await (new Promise(okay => setTimeout(okay, SLEEP_DURATION))); + + let nonce = await web3.eth.getTransactionCount(pubKey.address) + transaction.nonce = nonce + + return postWithRetries(transaction, signerKey, web3, retries - 1, attempt + 1); + } else { + throw new Error(`Failed to run Open Price Feed poster after ${attempt} attempt(s): error=\`${e.toString()}\``); } } - console.error(`Failed to run Open Oracle Poster ${retries} times, giving up`); } async function signAndSend(transaction: TransactionConfig, signerKey: string, web3: Web3): Promise { - let signedTransaction = await web3 - .eth - .accounts - .signTransaction(transaction, signerKey); + let signedTransaction = + await web3.eth.accounts.signTransaction(transaction, signerKey); return web3.eth.sendSignedTransaction(signedTransaction.rawTransaction || ''); } diff --git a/poster/src/poster.ts b/poster/src/poster.ts index a09b4faa..fd8c8794 100644 --- a/poster/src/poster.ts +++ b/poster/src/poster.ts @@ -2,224 +2,186 @@ import { postWithRetries } from './post_with_retries'; import fetch from 'node-fetch'; import Web3 from 'web3'; import { TransactionConfig } from 'web3-core'; -import AbiCoder from 'web3-eth-abi'; -import helpers from './prev_price'; +import { + getDataAddress, + getPreviousPrice, + getSourceAddress +} from './prev_price'; import { BigNumber as BN } from 'bignumber.js'; +import { CoinbaseConfig, readCoinbasePayload } from './sources/coinbase'; +import { decodeMessage, encode, zip } from './util'; +import { mockUniswapTokenPairs } from './mainnet_uniswap_mocker'; + +const GAS_PRICE_API = 'https://api.compound.finance/api/gas_prices/get_gas_price'; +const DEFAULT_GAS_PRICE = 3_000_000_000; // use 3 gwei if api is unreachable for some reason + +export async function main( + sources: string[], + senderKey: string, + viewAddress: string, + functionSig: string, + gas: number, + gasPrice: number | undefined, + delta: number, + assets: string[], + mocked_world: boolean, + pairs, + web3: Web3) { + + const payloads = await fetchPayloads(sources); + const feedItems = await filterPayloads(payloads, viewAddress, assets, delta, web3); + + if (feedItems.length > 0) { + // If gas price was not defined, fetch average one from Compound API + if (!gasPrice) { + gasPrice = await fetchGasPrice(); + } -async function main(sources: string, - senderKey: string, - viewAddress: string, - functionSig: string, - gas: number, - delta: number, - assets: string, - web3: Web3) { - const payloads = await fetchPayloads(sources.split(",")); - const filteredPayloads = await filterPayloads(payloads, viewAddress, assets, delta, web3); - if (filteredPayloads.length != 0) { - const gasPrice = await fetchGasPrice(); - const trxData = buildTrxData(filteredPayloads, functionSig); + // mock uniswap mainnet pairs price + if (mocked_world) { + await mockUniswapTokenPairs(assets, senderKey, pairs, gas, gasPrice, web3); + } + const trxData = buildTrxData(feedItems, functionSig); const trx = { data: trxData, to: viewAddress, gasPrice: gasPrice, gas: gas - } + }; + + console.log(`Posting...`); + console.log(feedItems); return await postWithRetries(trx, senderKey, web3); } } -async function filterPayloads(payloads: DelFiReporterPayload[], - viewAddress: string, - assets: string, - delta: number, - web3: Web3): Promise { - const dataAddress = await helpers.getDataAddress(viewAddress, web3); - const supportedAssets = assets.split(","); - - await Promise.all(payloads.map(async payload => { - const sourceAddress = await helpers.getSourceAddress(dataAddress, payload.messages[0], payload.signatures[0], web3); - - const filteredPrices = {}; - const filteredMessages: string[] = []; - const filteredSignatures: string[] = []; - let index = 0; - for (const [asset, price] of Object.entries(payload.prices)) { - // Post only prices for supported assets, skip prices for unregistered assets - if (!supportedAssets.includes(asset.toUpperCase())) { - index++; - continue; - } - - const prev_price = await helpers.getPreviousPrice(sourceAddress, asset, dataAddress, web3); - console.log(`For asset ${asset}: prev price = ${prev_price}, new price = ${price}`); - - // Update price only if new price is different by more than delta % from the previous price - if (!inDeltaRange(delta, Number(price), prev_price)) { - console.log(`Not in delta range for asset ${asset}, price = ${Number(price)}, prev price = ${prev_price}`); - filteredPrices[asset] = price; - filteredMessages.push(payload.messages[index]); - filteredSignatures.push(payload.signatures[index]); - } - index++; - } - payload.prices = filteredPrices; - payload.messages = filteredMessages; - payload.signatures = filteredSignatures; - })) - - // Filter payloads with no updated prices - const filteredPayloads = payloads.filter(payload => payload.messages.length > 0); - return filteredPayloads; +export async function filterPayloads( + payloads: OpenPriceFeedPayload[], + viewAddress: string, + supportedAssets: string[], + delta: number, + web3: Web3): Promise { + + const dataAddress = await getDataAddress(viewAddress, web3); + + let filteredFeedItems = await Promise.all(payloads.map(async payload => { + return await Promise.all(zip(payload.messages, payload.signatures).map(([message, signature]) => { + const { + dataType, + timestamp, + symbol, + price + } = decodeMessage(message, web3) + + return { + message, + signature, + dataType, + timestamp, + symbol: symbol.toUpperCase(), + price: Number(price) + }; + }).filter(({message, signature, symbol}) => { + return supportedAssets.includes(symbol.toUpperCase()); + }).map(async (feedItem) => { + const source = await getSourceAddress(dataAddress, feedItem.message, feedItem.signature, web3); + const prev = await getPreviousPrice(source, feedItem.symbol, dataAddress, web3); + + return { + ...feedItem, + source, + prev: Number(prev) / 1e6 + }; + })).then((feedItems) => { + return feedItems.filter(({message, signature, symbol, price, prev}) => { + return !inDeltaRange(delta, price, prev); + }); + }); + })); + + let feedItems = filteredFeedItems.flat(); + + feedItems + .forEach(({source, symbol, price, prev}) => { + console.log(`Setting Price: source=${source}, symbol=${symbol}, price=${price}, prev_price=${prev}`); + }); + + return feedItems; } // Checks if new price is less than delta percent different form the old price -function inDeltaRange(delta: number, price: number, prev_price: number) { +// Note TODO: price here is uh... a number that needs to be scaled by 1e6? +export function inDeltaRange(delta: number, price: number, prevPrice: number) { // Always update prices if delta is set to 0 or delta is not within expected range [0..100]% - if (delta <= 0 || delta > 100) return false; - const minDifference = new BN(prev_price).multipliedBy(delta).dividedBy(100); - const difference = new BN(prev_price).minus(new BN(price).multipliedBy(1e6)).abs(); + if (delta <= 0 || delta > 100) { + return false + }; + + const minDifference = new BN(prevPrice).multipliedBy(delta).dividedBy(100); + const difference = new BN(prevPrice).minus(new BN(price)).abs(); + return difference.isLessThanOrEqualTo(minDifference); } -async function fetchPayloads(sources: string[], fetchFn = fetch): Promise { - let sourcePromises = sources.map(async (source) => { +export async function fetchPayloads(sources: string[], fetchFn=fetch): Promise { + function parse(json): object { + let result; try { - let response; - if(source == "https://api.pro.coinbase.com/oracle") { - const crypto = require('crypto'); - - const key_id = process.env.API_KEY_ID; - const secret = process.env.API_SECRET; - const passphrase = process.env.API_PASSPHRASE; - - - let timestamp = Date.now() / 1000; - - let method = 'GET'; - - // create the prehash string by concatenating required parts - let what = timestamp + method + "/oracle"; - - // decode the base64 secret - let key = Buffer.from(secret, 'base64'); - - // create a sha256 hmac with the secret - let hmac = crypto.createHmac('sha256', key); - - // sign the require message with the hmac - // and finally base64 encode the result - let signature = hmac.update(what).digest('base64'); - let headers = { - 'CB-ACCESS-KEY': key_id, - 'CB-ACCESS-SIGN': signature, - 'CB-ACCESS-TIMESTAMP': timestamp, - 'CB-ACCESS-PASSPHRASE': passphrase, - 'Content-Type': 'application/json' - } + result = JSON.parse(json); + } catch (e) { + console.error(`Error parsing source input: ${json}`); + throw e; + } + if (!result['source']) { + throw new Error(`Source must include \`source\` field for ${json}`); + } + return result; + } - response = await fetchFn(source, { - headers: headers - }) + return await Promise.all(sources.map(async (sourceRaw) => { + let source = sourceRaw.includes('{') ? parse(sourceRaw) : sourceRaw; + let response; - } else { + try { + if (typeof(source) === 'string') { response = await fetchFn(source); + } else if (source['source'] === 'coinbase') { + response = await readCoinbasePayload(source, fetchFn); } - return response.json(); + + return await response.json(); } catch (e) { + // This is now just for some extra debugging messages + console.error(`Error Fetching Payload for ${JSON.stringify(source)}`); + if (response) { + console.debug({response}); + } console.error(e); - return null; + throw e; } - }); - - return (await Promise.all(sourcePromises)).filter(x => x != null); + })); } -async function fetchGasPrice(fetchFn = fetch): Promise { +export async function fetchGasPrice(fetchFn = fetch): Promise { try { - let source = "https://api.compound.finance/api/gas_prices/get_gas_price"; - let response = await fetchFn(source); + let response = await fetchFn(GAS_PRICE_API); let prices = await response.json(); - let averagePrice = Number(prices["average"]["value"]); - return averagePrice; + return Number(prices["average"]["value"]); } catch (e) { - // use 3 gwei if api is unreachable for some reason console.warn(`Failed to fetch gas price`, e); - return 3_000_000_000; - } -} - -function buildTrxData(payloads: DelFiReporterPayload[], functionSig: string): string { - const types = findTypes(functionSig); - - let messages = payloads.reduce((a: string[], x) => a.concat(x.messages), []); - let signatures = payloads.reduce((a: string[], x) => a.concat(x.signatures), []); - let priceKeys = payloads.map(x => Object.keys(x.prices)); - let symbols = new Set(priceKeys.reduce((acc, val) => acc.concat(val))); - let upperCaseDeDuped = [...symbols].map((x) => x.toUpperCase()) - - // see https://github.com/ethereum/web3.js/blob/2.x/packages/web3-eth-abi/src/AbiCoder.js#L112 - return (AbiCoder).encodeFunctionSignature(functionSig) + - (AbiCoder) - .encodeParameters(types, [messages, signatures, [...upperCaseDeDuped]]) - .replace('0x', ''); -} - -// e.g. findTypes("postPrices(bytes[],bytes[],string[])")-> ["bytes[]","bytes[]","string[]"] -function findTypes(functionSig: string): string[] { - // this unexported function from ethereumjs-abi is copy pasted from source - // see https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L81 - let parseSignature = function (sig) { - var tmp = /^(\w+)\((.*)\)$/.exec(sig) || []; - - if (tmp.length !== 3) { - throw new Error('Invalid method signature') - } - - var args = /^(.+)\):\((.+)$/.exec(tmp[2]) - - if (args !== null && args.length === 3) { - return { - method: tmp[1], - args: args[1].split(','), - retargs: args[2].split(',') - } - } else { - var params = tmp[2].split(',') - if (params.length === 1 && params[0] === '') { - // Special-case (possibly naive) fixup for functions that take no arguments. - // TODO: special cases are always bad, but this makes the function return - // match what the calling functions expect - params = [] - } - return { - method: tmp[1], - args: params - } - } + return DEFAULT_GAS_PRICE; } - - return parseSignature(functionSig).args; } -function getEnvVar(name: string): string { - const result: string | undefined = process.env[name]; - - if (result) { - return result; - } else { - throw `Missing required env var: ${name}`; - } -} +export function buildTrxData(feedItems: OpenPriceFeedItem[], functionSig: string): string { + const messages = feedItems.map(({message}) => message); + const signatures = feedItems.map(({signature}) => signature); + const symbols = [...new Set(feedItems.map(({symbol}) => symbol.toUpperCase()))]; -export { - buildTrxData, - findTypes, - fetchGasPrice, - fetchPayloads, - main, - inDeltaRange, - filterPayloads + return encode( + functionSig, + [messages, signatures, symbols] + ); } diff --git a/poster/src/prev_price.ts b/poster/src/prev_price.ts index 2ec0c5c5..2d45d600 100644 --- a/poster/src/prev_price.ts +++ b/poster/src/prev_price.ts @@ -1,55 +1,32 @@ import Web3 from 'web3'; -import { TransactionConfig } from 'web3-core'; -import AbiCoder from 'web3-eth-abi'; -import { findTypes } from './poster'; +import { read } from './util'; -async function getPreviousPrice(sourceAddress: string, asset: string, dataAddress: string, web3 : Web3) { - const functionSig = 'getPrice(address,string)'; - const types = findTypes(functionSig); - - const callData = (AbiCoder).encodeFunctionSignature(functionSig) + - (AbiCoder) - .encodeParameters(types, [sourceAddress, asset.toUpperCase()]) - .replace('0x', ''); - const call = { - data: callData, - //Price open oracle data - to: dataAddress - } - const price = web3.utils.hexToNumber(await web3.eth.call(call)); - console.log(`Previous price of ${asset.toUpperCase()} for source ${sourceAddress} = ${price}`); - return price; - } - - async function getDataAddress(viewAddress: string, web3: Web3) { - const callData = (AbiCoder).encodeFunctionSignature('data()') - const call = { - data: callData, - to: viewAddress - } - return web3.eth.abi.decodeParameter('address', await web3.eth.call(call)).toString(); - } - - async function getSourceAddress(dataAddress: string, message: string, signature: string, web3 : Web3) { - const functionSig = 'source(bytes,bytes)'; - const types = findTypes(functionSig); - - const callData = (AbiCoder).encodeFunctionSignature(functionSig) + - (AbiCoder) - .encodeParameters(types, [message, signature]) - .replace('0x', ''); - const call = { - data: callData, - //Price open oracle data - to: dataAddress - } - return web3.eth.abi.decodeParameter('address', await web3.eth.call(call)).toString(); - } +export async function getPreviousPrice(sourceAddress: string, asset: string, dataAddress: string, web3: Web3) { + return await read( + dataAddress, + 'getPrice(address,string)', + [sourceAddress, asset.toUpperCase()], + 'uint256', + web3 + ); +} - const exportFunctions = { - getPreviousPrice, - getSourceAddress, - getDataAddress - }; - export default exportFunctions; - \ No newline at end of file +export async function getDataAddress(viewAddress: string, web3: Web3) { + return await read( + viewAddress, + 'priceData()', + [], + 'address', + web3 + ); +} + +export async function getSourceAddress(dataAddress: string, message: string, signature: string, web3 : Web3) { + return await read( + dataAddress, + 'source(bytes,bytes)', + [message, signature], + 'address', + web3 + ); +} diff --git a/poster/src/sources/coinbase.ts b/poster/src/sources/coinbase.ts new file mode 100644 index 00000000..b6b79d81 --- /dev/null +++ b/poster/src/sources/coinbase.ts @@ -0,0 +1,38 @@ +import crypto from 'crypto'; + +export interface CoinbaseConfig { + source: string + endpoint: string + api_key_id: string + api_secret: string + api_passphrase: string +} + +export async function readCoinbasePayload(config: CoinbaseConfig, fetchFn) { + let timestamp = Date.now() / 1000; + let method = 'GET'; + + // create the prehash string by concatenating required parts + let what = timestamp + method + "/oracle"; + + // decode the base64 secret + let key = Buffer.from(config.api_secret, 'base64'); + + // create a sha256 hmac with the secret + let hmac = crypto.createHmac('sha256', key); + + // sign the require message with the hmac + // and finally base64 encode the result + let signature = hmac.update(what).digest('base64'); + let headers = { + 'CB-ACCESS-KEY': config.api_key_id, + 'CB-ACCESS-SIGN': signature, + 'CB-ACCESS-TIMESTAMP': timestamp, + 'CB-ACCESS-PASSPHRASE': config.api_passphrase, + 'Content-Type': 'application/json' + }; + + return await fetchFn(config.endpoint, { + headers: headers + }); +} diff --git a/poster/src/util.ts b/poster/src/util.ts new file mode 100644 index 00000000..04a336ec --- /dev/null +++ b/poster/src/util.ts @@ -0,0 +1,136 @@ +import AbiCoder from 'web3-eth-abi'; +import Web3 from 'web3'; +import { TransactionConfig } from 'web3-core'; + +export function decodeMessage(message: string, web3: Web3): DecodedMessage { + // TODO: Consider using `decode` from `reporter.ts` + let { + '0': dataType, + '1': timestamp, + '2': symbol, + '3': price + } = web3.eth.abi.decodeParameters(['string', 'uint64', 'string', 'uint64'], message); + + return { + dataType, + timestamp, + symbol, + price: price / 1e6 + }; +} + +function encodeFull(sig: string, args: any[]): [string[], string] { + const types = findTypes(sig); + + const callData = + (AbiCoder).encodeFunctionSignature(sig) + + (AbiCoder).encodeParameters(types, args).slice(2); + + return [types, callData]; +} + +export function encode(sig: string, args: any[]): string { + let [types, callData] = encodeFull(sig, args); + + return callData; +} + +export async function read(address: string, sig: string, args: any[], returns: string, web3: Web3): Promise { + let [types, callData] = encodeFull(sig, args); + + const call = { + data: callData, + // Price open oracle data + to: address + }; + + try { + const result = await web3.eth.call(call); + + return (AbiCoder).decodeParameter(returns, result); + } catch (e) { + console.error(`Error reading ${sig}:${args} at ${address}: ${e.toString()}`); + throw e; + } +} + +export async function readMany(address: string, sig: string, args: any[], returns: string[], web3: Web3): Promise { + let [_, callData] = encodeFull(sig, args); + + const call = { + data: callData, + // Price open oracle data + to: address + }; + + try { + const result = await web3.eth.call(call); + + return (AbiCoder).decodeParameters(returns, result); + } catch (e) { + console.error(`Error reading ${sig}:${args} at ${address}: ${e.toString()}`); + throw e; + } +} + +// TODO: Swap with ether's own implementation of this +// e.g. findTypes("postPrices(bytes[],bytes[],string[])")-> ["bytes[]","bytes[]","string[]"] +export function findTypes(functionSig: string): string[] { + // this unexported function from ethereumjs-abi is copy pasted from source + // see https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L81 + let parseSignature = function (sig) { + var tmp = /^(\w+)\((.*)\)$/.exec(sig) || []; + + if (tmp.length !== 3) { + throw new Error('Invalid method signature') + } + + var args = /^(.+)\):\((.+)$/.exec(tmp[2]) + + if (args !== null && args.length === 3) { + return { + method: tmp[1], + args: args[1].split(','), + retargs: args[2].split(',') + } + } else { + var params = tmp[2].split(',') + if (params.length === 1 && params[0] === '') { + // Special-case (possibly naive) fixup for functions that take no arguments. + // TODO: special cases are always bad, but this makes the function return + // match what the calling functions expect + params = [] + } + return { + method: tmp[1], + args: params + } + } + } + + return parseSignature(functionSig).args; +} + +export function zip(arr1: T[], arr2: U[]): [T, U][] { + return arr1.map((k, i) => [k, arr2[i]]) +} + +export async function asyncFilter(arr: T[], f: ((T) => Promise)): Promise { + let tests: boolean[] = await Promise.all(arr.map(f)); + + return tests.reduce((acc, el, i) => { + if (el) { + return [...acc, arr[i]]; + } else { + return acc; + } + }, []); +} + +export async function allSuccesses(promises: Promise[]): Promise { + let settled = await Promise.allSettled(promises); + + return settled + .filter((promise) => promise.status === 'fulfilled') + .map((promise => (>promise).value)); +} diff --git a/poster/tests/post_with_retries_test.ts b/poster/tests/post_with_retries_test.ts index d7d5d472..17bd2b25 100644 --- a/poster/tests/post_with_retries_test.ts +++ b/poster/tests/post_with_retries_test.ts @@ -1,19 +1,19 @@ -import ganache from "ganache-core"; -import {postWithRetries, signAndSend} from '../src/post_with_retries'; +import Ganache from 'ganache-core'; import Web3 from 'web3'; import { PromiEvent } from 'web3-core'; import { TransactionReceipt } from 'web3-eth'; +import { postWithRetries, signAndSend } from '../src/post_with_retries'; describe('posting', () => { test('signs and sends', async () => { - const web3 = new Web3(ganache.provider()); + const web3 = new Web3(Ganache.provider()); web3.eth.transactionConfirmationBlocks = 1; - web3.eth.sendSignedTransaction = (signedTransactionData: string, callback?: ((error: Error, hash: string) => void) | undefined): PromiEvent => { + (web3.eth).sendSignedTransaction = (signedTransactionData: string, callback?: ((error: Error, hash: string) => void) | undefined): PromiEvent => { // TODO: Parse signed transaction data to ensure matches expect(signedTransactionData).toEqual("0xf901eb8083989680830186a0944fe3dd76d873caf6cbf56e442b2c808d3984df1d80b90184a59d56ad000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf1000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006004a78a7b3013f6939da19eac6fd1ad5c5a20c41bcc5d828557442aad6f07598d029ae684620bec13e13d018cba0da5096626e83cfd4d5356d808d7437a0a5076000000000000000000000000000000000000000000000000000000000000001c820a95a0122a39912374aaf6d64f0ea8d4de552237af6deda0038cc3ebf4e2f8b36daa2fa0408d15da1b8fa27a5f73b3c06a561eecf6e4e667d410c6f973da6a54abb78f4b"); - return >(Promise.resolve({ + return Promise.resolve({ "blockHash": "0x4dd036433dde8a2e04bf49f50f7543de20e8a66566bad42359fae6ec2a816ea3", "blockNumber": 1, "contractAddress": null, @@ -31,7 +31,7 @@ describe('posting', () => { "transactionHash": "0xc693cb5a9f8bea2fac46d003c5ebe63102704c66658b532e76a3f9a2f1272f8b", "transactionIndex": 0, "v": "0x9e" - })); + }); } let senderKey = "0x620622d7fbe43a4dccd3aef6ef90d20728508c563719380f289cf3f9460d0510"; diff --git a/poster/tests/poster_test.ts b/poster/tests/poster_test.ts index 4b7f32ba..8823d4ea 100644 --- a/poster/tests/poster_test.ts +++ b/poster/tests/poster_test.ts @@ -1,6 +1,13 @@ -import {buildTrxData, findTypes, fetchGasPrice, fetchPayloads, inDeltaRange, filterPayloads} from '../src/poster'; -import helpers from '../src/prev_price'; import Web3 from 'web3'; +import { + buildTrxData, + fetchGasPrice, + fetchPayloads, + inDeltaRange, + filterPayloads +} from '../src/poster'; +import * as prevPrice from '../src/prev_price'; +import * as util from '../src/util'; const endpointResponses = { "http://localhost:3000": { @@ -85,15 +92,20 @@ describe('loading poster arguments from environment and https', () => { describe('building a function call', () => { test('findTypes', () => { let typeString = "writePrices(bytes[],bytes[],string[])"; - expect(findTypes(typeString)).toEqual(["bytes[]", "bytes[]", "string[]"]); + expect(util.findTypes(typeString)).toEqual(["bytes[]", "bytes[]", "string[]"]); }); test('buildTrxData', () => { - let messages = ['0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10']; - let signatures = ['0x04a78a7b3013f6939da19eac6fd1ad5c5a20c41bcc5d828557442aad6f07598d029ae684620bec13e13d018cba0da5096626e83cfd4d5356d808d7437a0a5076000000000000000000000000000000000000000000000000000000000000001c']; - let prices = {"eth": "250", "zrx": "300"}; + let feedItems = [{ + message: '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10', + signature: '0x04a78a7b3013f6939da19eac6fd1ad5c5a20c41bcc5d828557442aad6f07598d029ae684620bec13e13d018cba0da5096626e83cfd4d5356d808d7437a0a5076000000000000000000000000000000000000000000000000000000000000001c', + price: 250.0, + symbol: 'eth' + }]; + let messages = feedItems.map(({message}) => message); + let signatures = feedItems.map(({signature}) => signature); - let data = buildTrxData([{messages, signatures, prices}], "writePrices(bytes[],bytes[],string[])"); + let data = buildTrxData(feedItems, "writePrices(bytes[],bytes[],string[])"); let assumedAbi = { "constant": false, @@ -119,7 +131,7 @@ describe('building a function call', () => { }; // @ts-ignore-start - let officialWeb3Encoding = new Web3().eth.abi.encodeFunctionCall(assumedAbi, [messages, signatures, ['ETH', 'ZRX']]); + let officialWeb3Encoding = new Web3().eth.abi.encodeFunctionCall(assumedAbi, [messages, signatures, ['ETH']]); // @ts-ignore-end expect(data).toEqual(officialWeb3Encoding); @@ -128,30 +140,56 @@ describe('building a function call', () => { describe('checking that numbers are within the specified delta range', () => { test('inDeltaRange', () => { - expect(inDeltaRange(0, 9687.654999, 9696640000)).toEqual(false); - expect(inDeltaRange(0.01, 9687.654999, 9696640000)).toEqual(false); - expect(inDeltaRange(0.1, 9687.654999, 9696640000)).toEqual(true); - expect(inDeltaRange(5, 9687.654999, 9696640000)).toEqual(true); + expect(inDeltaRange(0, 9687.654999, 9696.640000)).toEqual(false); + expect(inDeltaRange(0.01, 9687.654999, 9696.640000)).toEqual(false); + expect(inDeltaRange(0.1, 9687.654999, 9696.640000)).toEqual(true); + expect(inDeltaRange(5, 9687.654999, 9696.640000)).toEqual(true); - expect(inDeltaRange(0, 1, 1e6)).toEqual(false); - expect(inDeltaRange(-1, 1, 1e6)).toEqual(false); - expect(inDeltaRange(101, 1, 1e6)).toEqual(false); - expect(inDeltaRange(0.01, 1, 1e6)).toEqual(true); - expect(inDeltaRange(5, 1, 1e6)).toEqual(true); - expect(inDeltaRange(100, 1, 1e6)).toEqual(true); + expect(inDeltaRange(0, 1, 1)).toEqual(false); + expect(inDeltaRange(-1, 1, 1)).toEqual(false); + expect(inDeltaRange(101, 1, 1)).toEqual(false); + expect(inDeltaRange(0.01, 1, 1)).toEqual(true); + expect(inDeltaRange(5, 1, 1)).toEqual(true); + expect(inDeltaRange(100, 1, 1)).toEqual(true); }) }) describe('filtering payloads', () => { - let prevPrices = {}; - async function mockPreviosPrice(_sourceAddress, asset, _dataAddress, _web3) { - return prevPrices[asset]; + function mockPrevPrices(prevPrices={}) { + async function mockPreviousPrice(_sourceAddress, asset, _dataAddress, _web3) { + return prevPrices[asset]; + } + + const getSourceAddressSpy = jest.spyOn(prevPrice, 'getSourceAddress'); + getSourceAddressSpy.mockImplementation(() => Promise.resolve("")); + const getDataAddressSpy = jest.spyOn(prevPrice, 'getDataAddress'); + getDataAddressSpy.mockImplementation(() => Promise.resolve("")); + const getPreviousPriceSpy = jest.spyOn(prevPrice, 'getPreviousPrice'); + getPreviousPriceSpy.mockImplementation(mockPreviousPrice); + }; + + function mockMessages(messages: {[message: string]: DecodedMessage}) { + const decodeMessageSpy = jest.spyOn(util, 'decodeMessage'); + + decodeMessageSpy.mockImplementation((message, web3) => { + return messages[message]; + }); + }; + + function transformPayloads(payloads) { + return Object.fromEntries(payloads.map((payload) => { + return util.zip(Object.entries(payload.prices), payload.messages).map(([[symbol, price], message]) => { + return [message, { + dataType: 'type', + timestamp: 0, + symbol, + price + }]; + }) + }).flat()); } - test('Filtering payloads, BAT price is more than delta % different', async () => { - helpers.getSourceAddress = jest.fn(); - helpers.getDataAddress = jest.fn(); - helpers.getPreviousPrice = mockPreviosPrice; + test('Filtering payloads, BAT price is more than delta % different', async () => { const payloads = [ { timestamp: '1593209100', @@ -169,27 +207,27 @@ describe('filtering payloads', () => { LINK: '4.70819' } } - ] - prevPrices = { 'BTC': 9149090000, 'ETH': 229435000, 'DAI': 1003372, 'REP': 16884999, 'ZRX': 357704, 'BAT': 260992, 'KNC': 1156300, 'LINK': 4704680 } + ]; + mockMessages(transformPayloads(payloads)); + mockPrevPrices({ 'BTC': 9149090000, 'ETH': 229435000, 'DAI': 1003372, 'REP': 16884999, 'ZRX': 357704, 'BAT': 260992, 'KNC': 1156300, 'LINK': 4704680 }); - const filteredPayloads = await filterPayloads(payloads, '0x0', 'BTC,ETH,DAI,REP,ZRX,BAT,KNC,LINK,COMP', 1, new Web3()); - expect(filteredPayloads).toEqual([ + const feedItems = await filterPayloads(payloads, '0x0', ['BTC', 'ETH', 'DAI', 'REP', 'ZRX', 'BAT', 'KNC', 'LINK', 'COMP'], 1, new Web3()); + expect(feedItems).toEqual([ { - timestamp: '1593209100', - messages: ['0x7'], - signatures: ['0x7'], - prices: { BAT: '0.26466' } + dataType: "type", + message: "0x7", + prev: 0.260992, + price: 0.26466, + signature: "0x7", + source: "", + symbol: "BAT", + timestamp: 0, } - ] - ); + ]); }) test('Filtering payloads, ETH, BTC and ZRX prices are more than delta % different, ZRX, XTZ are not supported', async () => { - prevPrices = { 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 } - - helpers.getSourceAddress = jest.fn(); - helpers.getDataAddress = jest.fn(); - helpers.getPreviousPrice = mockPreviosPrice; + mockPrevPrices({ 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 }); const payloads = [ { @@ -208,25 +246,36 @@ describe('filtering payloads', () => { LINK: '5' } } - ] + ]; + mockMessages(transformPayloads(payloads)); - const filteredPayloads = await filterPayloads(payloads, '0x0', 'BTC,ETH,DAI,REP,BAT,KNC,LINK,COMP', 1, new Web3()); - expect(filteredPayloads).toEqual([ + const feedItems = await filterPayloads(payloads, '0x0', ['BTC', 'ETH', 'DAI', 'REP', 'BAT', 'KNC', 'LINK', 'COMP'], 1, new Web3()); + expect(feedItems).toEqual([ { - timestamp: '1593209100', - messages: [ '0x1', '0x2' ], - signatures: [ '0x1', '0x2' ], - prices: { BTC: '10101', ETH: '1011' } + message: '0x1', + signature: '0x1', + dataType: 'type', + timestamp: 0, + symbol: 'BTC', + price: 10101, + source: '', + prev: 10000 + }, + { + message: '0x2', + signature: '0x2', + dataType: 'type', + timestamp: 0, + symbol: 'ETH', + price: 1011, + source: '', + prev: 1000 } ]); }) test('Filtering payloads, ETH, BTC and ZRX prices are more than delta % different, no assets are supported', async () => { - prevPrices = { 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 } - - helpers.getSourceAddress = jest.fn(); - helpers.getDataAddress = jest.fn(); - helpers.getPreviousPrice = mockPreviosPrice; + mockPrevPrices({ 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 }); const payloads = [ { @@ -246,17 +295,14 @@ describe('filtering payloads', () => { } } ] + mockMessages(transformPayloads(payloads)); - const filteredPayloads = await filterPayloads(payloads, '0x0', '', 1, new Web3()); - expect(filteredPayloads).toEqual([]); + const feedItems = await filterPayloads(payloads, '0x0', [], 1, new Web3()); + expect(feedItems).toEqual([]); }) test('Filtering payloads, delta is 0% percent, all supported prices should be updated', async () => { - prevPrices = { 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 } - - helpers.getSourceAddress = jest.fn(); - helpers.getDataAddress = jest.fn(); - helpers.getPreviousPrice = mockPreviosPrice; + mockPrevPrices({ 'BTC': 10000000000, 'ETH': 1000000000, 'ZRX': 1011000, 'REP': 16000000, 'DAI': 1000000, 'BAT': 1000000, 'KNC': 2000000, 'LINK': 5000000 }); const payloads = [ { @@ -275,9 +321,91 @@ describe('filtering payloads', () => { LINK: '5' } } - ] + ]; + mockMessages(transformPayloads(payloads)); - const filteredPayloads = await filterPayloads(payloads, '0x0', 'BTC,ETH,DAI,REP,ZRX,BAT,KNC,LINK,COMP', 0, new Web3()); - expect(filteredPayloads).toEqual(payloads); + const feedItems = await filterPayloads(payloads, '0x0', ['BTC', 'ETH', 'DAI', 'REP', 'ZRX', 'BAT', 'KNC', 'LINK', 'COMP'], 0, new Web3()); + expect(feedItems).toEqual([ + { + message: '0x1', + signature: '0x1', + dataType: 'type', + timestamp: 0, + symbol: 'BTC', + price: 10101, + source: '', + prev: 10000 + }, + { + message: '0x2', + signature: '0x2', + dataType: 'type', + timestamp: 0, + symbol: 'ETH', + price: 1011, + source: '', + prev: 1000 + }, + { + message: '0x4', + signature: '0x4', + dataType: 'type', + timestamp: 0, + symbol: 'DAI', + price: 1, + source: '', + prev: 1 + }, + { + message: '0x5', + signature: '0x5', + dataType: 'type', + timestamp: 0, + symbol: 'REP', + price: 16, + source: '', + prev: 16 + }, + { + message: '0x6', + signature: '0x6', + dataType: 'type', + timestamp: 0, + symbol: 'ZRX', + price: 1.011, + source: '', + prev: 1.011 + }, + { + message: '0x7', + signature: '0x7', + dataType: 'type', + timestamp: 0, + symbol: 'BAT', + price: 1, + source: '', + prev: 1 + }, + { + message: '0x8', + signature: '0x8', + dataType: 'type', + timestamp: 0, + symbol: 'KNC', + price: 2, + source: '', + prev: 2 + }, + { + message: '0x9', + signature: '0x9', + dataType: 'type', + timestamp: 0, + symbol: 'LINK', + price: 5, + source: '', + prev: 5 + } + ]); }) -}) +}); diff --git a/poster/yarn.lock b/poster/yarn.lock index fcd2f2b4..83070d74 100644 --- a/poster/yarn.lock +++ b/poster/yarn.lock @@ -579,13 +579,6 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== -"@types/web3@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.2.2.tgz#d95a101547ce625c5ebd0470baa5dbd4b9f3c015" - integrity sha512-eFiYJKggNrOl0nsD+9cMh2MLk4zVBfXfGnVeRFbpiZzBE20eet4KLA3fXcjSuHaBn0RnQzwLAGdgzgzdet4C0A== - dependencies: - web3 "*" - "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -1863,7 +1856,7 @@ buffer-xor@^2.0.1: dependencies: safe-buffer "^5.1.1" -buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: +buffer@5.6.0, buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -5854,11 +5847,6 @@ mute-stdout@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== -nan@2.13.2: - version "2.13.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" - integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== - nan@^2.11.0, nan@^2.12.1, nan@^2.14.0, nan@^2.2.1: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" @@ -7264,12 +7252,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sha3@^1.2.2: - version "1.2.6" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.6.tgz#102aa3e47dc793e2357902c3cce8760822f9e905" - integrity sha512-KgLGmJGrmNB4JWVsAV11Yk6KbvsAiygWJc7t5IebWva/0NukNrjJqhtKhzy3Eiv2AKuGvhZZt7dt1mDo7HkoiQ== +sha3@^1.2.2, sha3@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.3.tgz#ab05b841b2bce347765db31f57fe2a3134b9fb48" + integrity sha512-Io53D4o9qOmf3Ow9p/DoGLQiQHhtuR0ulbyambvRSG+OX5yXExk2yYfvjHtb7AtOyk6K6+sPeK/qaowWc/E/GA== dependencies: - nan "2.13.2" + buffer "5.6.0" shebang-command@^1.2.0: version "1.2.0" @@ -8745,19 +8733,6 @@ web3-utils@1.2.9: underscore "1.9.1" utf8 "3.0.0" -web3@*: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.9.tgz#cbcf1c0fba5e213a6dfb1f2c1f4b37062e4ce337" - integrity sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA== - dependencies: - web3-bzz "1.2.9" - web3-core "1.2.9" - web3-eth "1.2.9" - web3-eth-personal "1.2.9" - web3-net "1.2.9" - web3-shh "1.2.9" - web3-utils "1.2.9" - web3@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.4.tgz#6e7ab799eefc9b4648c2dab63003f704a1d5e7d9" @@ -8772,6 +8747,19 @@ web3@1.2.4: web3-shh "1.2.4" web3-utils "1.2.4" +web3@1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.9.tgz#cbcf1c0fba5e213a6dfb1f2c1f4b37062e4ce337" + integrity sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA== + dependencies: + web3-bzz "1.2.9" + web3-core "1.2.9" + web3-eth "1.2.9" + web3-eth-personal "1.2.9" + web3-net "1.2.9" + web3-shh "1.2.9" + web3-utils "1.2.9" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" diff --git a/script/coverage b/script/coverage new file mode 100755 index 00000000..32cbe5d3 --- /dev/null +++ b/script/coverage @@ -0,0 +1,31 @@ +#!/bin/bash + +set -eo pipefail + +dir=`dirname $0` +reporter_sdk_dir=$(cd "$dir/../sdk/javascript" && pwd) +coverage_root="$dir/../coverage" + +if [ ! -f "$reporter_sdk_dir/.tsbuilt/reporter.js" ]; then + echo "Compiling Reporter SDK..." + ( + cd $reporter_sdk_dir && + yarn install --ignore-optional && + yarn prepare + ) && echo "Reporter Compiled" || (echo "Compilation failed" && exit 1) +fi + +echo "Compiling contracts with trace..." +npx saddle compile --trace + +rm -rf "$coverage_root" + +echo "Running coverage..." +npx saddle coverage $@ +coverage_code=$? + +npx istanbul report --root="$coverage_root" lcov json + +echo "Coverage generated. Report at $(cd $coverage_root && pwd)/lcov-report/index.html" + +exit $coverage_code diff --git a/script/test b/script/test new file mode 100755 index 00000000..647907f0 --- /dev/null +++ b/script/test @@ -0,0 +1,21 @@ +#!/bin/bash + +set -eo pipefail + +dir=`dirname $0` +reporter_sdk_dir=$(cd "$dir/../sdk/javascript" && pwd) + +if [ ! -f "$reporter_sdk_dir/.tsbuilt/reporter.js" ]; then + echo "Compiling Reporter SDK..." + ( + cd $reporter_sdk_dir && + yarn install --ignore-optional && + yarn prepare + ) && echo "Reporter Compiled" || (echo "Compilation failed" && exit 1) +fi + +echo "Compiling contracts..." +npx saddle compile + +echo "Running tests..." +npx saddle test $@ diff --git a/tests/AnchoredViewTest.js b/tests/AnchoredViewTest.js deleted file mode 100644 index 93d28954..00000000 --- a/tests/AnchoredViewTest.js +++ /dev/null @@ -1,482 +0,0 @@ -const { encode, sign, encodeRotationMessage } = require('../sdk/javascript/.tsbuilt/reporter'); -const { time, numToBigNum, numToHex, address, sendRPC } = require('./Helpers'); - -async function setup() { - const source = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); - - const underlyingAddresses = [...Array(9).keys()].map(address); - - const cTokensList = await Promise.all( - underlyingAddresses.map((x) => { - return deploy('MockCToken', [x]); - }) - ); - - const tokens = { - eth: underlyingAddresses[0], - usdc: underlyingAddresses[1], - dai: underlyingAddresses[2], - rep: underlyingAddresses[3], - wbtc: underlyingAddresses[4], - bat: underlyingAddresses[5], - zrx: underlyingAddresses[6], - sai: underlyingAddresses[7], - usdt: underlyingAddresses[8], - }; - - const cTokens = { - cEth: cTokensList[0], - cUsdc: cTokensList[1], - cDai: cTokensList[2], - cRep: cTokensList[3], - cWbtc: cTokensList[4], - cBat: cTokensList[5], - cZrx: cTokensList[6], - cSai: cTokensList[7], - cUsdt: cTokensList[8] -}; - - const anchorMantissa = numToHex(1e17); //1e17 equates to 10% tolerance for median to be above or below anchor - const priceData = await deploy('OpenOraclePriceData', []); - const anchorOracle = await deploy('MockAnchorOracle'); - const delfi = await deploy('AnchoredView', [ - priceData._address, - source.address, - anchorOracle._address, - anchorMantissa, - { - cEthAddress: cTokens.cEth._address, - cUsdcAddress: cTokens.cUsdc._address, - cDaiAddress: cTokens.cDai._address, - cRepAddress: cTokens.cRep._address, - cWbtcAddress: cTokens.cWbtc._address, - cBatAddress: cTokens.cBat._address, - cZrxAddress: cTokens.cZrx._address, - cSaiAddress: cTokens.cSai._address, - cUsdtAddress: cTokens.cUsdt._address - } - ]); - - async function postPrices(timestamp, prices2dArr, symbols, signer = source) { - const messages = [], - signatures = []; - - prices2dArr.forEach((prices, i) => { - const signed = sign( - encode( - 'prices', - timestamp, - prices - ), - signer.privateKey - ); - for (let { message, signature } of signed) { - messages.push(message); - signatures.push(signature); - } - }); - return send(delfi, 'postPrices', [messages, signatures, symbols]); - } - - async function getPrice(symbol) { - return call(delfi, 'prices', [symbol]); - } - - async function primeAnchor(usdRatio = "2000000000000000000000000000") { - // sets up anchor for $500 eth and 10k btc - await send(anchorOracle, 'setPrice', [tokens.usdc, numToHex(usdRatio)]); - - const theRatio = "200000000000000000000000000000"; - await send(anchorOracle, 'setPrice', [tokens.wbtc, numToHex(theRatio)]); - await send(anchorOracle, 'setPrice', [tokens.eth, numToHex("1000000000000000000")]); - } - - return { - source, - anchorMantissa, - priceData, - delfi, - anchorOracle, - cTokens, - tokens, - postPrices, - primeAnchor, - getPrice - }; -} - - -describe('AnchoredView', () => { - let source, - anchorMantissa, - priceData, - delfi, - anchorOracle, - cTokens, - tokens, - postPrices, - primeAnchor, - getPrice; - - const timestamp = time() - 5; - describe('Anchoring', () => { - beforeEach(async done => { - ({ - source, - anchorMantissa, - priceData, - delfi, - anchorOracle, - cTokens, - tokens, - postPrices, - primeAnchor, - getPrice - } = await setup()); - await primeAnchor(); - done(); - }); - - it('posting no ETH price should guard price', async () => { - const post1 = await postPrices(timestamp, [[['ETH', 91]]], ['ETH']); - - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(post1.events.PricePosted).toBe(undefined); - expect(await call(delfi, '_prices', ['ETH'])).numEquals(0); - }); - - it('posting within anchor should update stored value', async () => { - const post1 = await postPrices(timestamp, [[['ETH', 492]]], ['ETH']); - - expect(post1.gasUsed).toBeLessThan(253000); - expect(post1.events.PriceUpdated.returnValues.symbol).toBe('ETH'); - expect(post1.events.PriceUpdated.returnValues.price).numEquals(492e6); - expect(await call(delfi, 'prices', ['ETH'])).numEquals(492e6); - - // double the dollar ratio - await send(anchorOracle, 'setPrice', [tokens.usdc, "4000000000000000000000000000"]); - const post2 = await postPrices(timestamp + 1, [[['ETH', 250]]], ['ETH']); - expect(post2.gasUsed).toBeLessThan(252000); - expect(post2.events.PriceUpdated.returnValues.symbol).toBe('ETH'); - expect(post2.events.PriceUpdated.returnValues.price).numEquals(250e6); - expect(await getPrice('ETH')).numEquals(250e6); - }); - - it('should not update source price if anchor is much lower, returns anchor price', async () => { - const post1 = await postPrices(timestamp, [[['ETH', 1000]]], ['ETH']); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(await call(delfi, '_prices', ['ETH'])).numEquals(0); - }); - - it('should not update source price if anchor is much higher, returns anchor price', async () => { - const post1 = await postPrices(timestamp, [[['ETH', 116]]], ['ETH']); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(await call(delfi, '_prices', ['ETH'])).numEquals(0); - }); - - it('should updates source price if anchor is cut, even if price outside of anchor', async () => { - await sendRPC(web3, 'evm_mineBlockNumber', 5760); - await send(delfi, 'cutAnchor', []); - expect(await call(delfi, 'anchorBreaker')).toEqual(true); - - const post1 = await postPrices(timestamp, [[['ETH', 1000]]], ['ETH']); - expect(post1.events.PriceUpdated).not.toBe(undefined); - expect(post1.events.PriceGuarded).toBe(undefined); - expect(await call(delfi, '_prices', ['ETH'])).numEquals(1000e6); - }); - - - it('posting all source for two assets should update stored values', async () => { - const post1 = await postPrices(timestamp, - [[['ETH', 510], ['BTC', 11000]]], - ['ETH', 'BTC']); - expect(post1.gasUsed).toBeLessThan(650000); - expect(post1.events.PriceUpdated[0].returnValues.symbol).toBe('ETH'); - expect(post1.events.PriceUpdated[0].returnValues.price).numEquals(510e6); - expect(await getPrice('ETH')).numEquals(510e6); - - expect(post1.events.PriceUpdated[1].returnValues.symbol).toBe('BTC'); - expect(post1.events.PriceUpdated[1].returnValues.price).numEquals(11000e6); - expect(await getPrice('BTC')).numEquals(11000e6); - }); - - it('view should use most recent post from source', async () => { - await postPrices( - timestamp, - [ - [['ETH', 510], ['BTC', 11000]], - ], - ['ETH', 'BTC'], - source - ); - const post2 = await postPrices( - timestamp + 1, - [ - [['ETH', 502], ['BTC', 10008]], - ], - ['ETH', 'BTC'], - source - ); - - expect(post2.events.PriceUpdated[0].returnValues.price).numEquals(502e6); - expect(await getPrice('ETH')).numEquals(502e6); - - expect(post2.events.PriceUpdated[1].returnValues.price).numEquals(10008e6); - expect(await getPrice('BTC')).numEquals(10008e6); - }); - - it('should revert on posting invalid message', async () => { - await expect( - send(delfi, 'postPrices', [['0xabc'], ['0x123'], []], { gas: 5000000 }) - ).rejects.toRevert(); - }); - - it('allows writing unconfigured tokens to storage, but not view', async () => { - await postPrices(timestamp, [[['BLETH', 500]]], ['BLETH']); - - expect(await getPrice('BLETH')).numEquals(0); - expect(await call(priceData, 'getPrice', [source.address, 'BLETH'])).numEquals(500e6); - }); - - - it('posting from non-source should not change median or emit event', async () => { - await postPrices(timestamp, [[['ETH', 500]]], ['ETH']); - - let nonSource = web3.eth.accounts.privateKeyToAccount('0x666ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); - - const post1 = await postPrices(timestamp + 1, - [[['ETH', 595]]], - ['ETH'], - nonSource); - - expect(post1.events.PriceGuarded).toBe(undefined); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(await getPrice('ETH')).numEquals(500e6); - }); - - it('guards when posts usdc or usdt, but reads fixed price always', async () => { - let post1 = await postPrices(timestamp, [[['USDC', 1.01], ['USDT', 0.99] ]], ['USDC', 'USDT']); - - expect(post1.events.PriceGuarded).toBe(undefined); - expect(post1.events.PriceUpdated[0].returnValues.symbol).toEqual('USDC'); - expect(post1.events.PriceUpdated[0].returnValues.price).numEquals(1.01e6); - expect(post1.events.PriceUpdated[1].returnValues.symbol).toEqual('USDT'); - expect(post1.events.PriceUpdated[1].returnValues.price).numEquals(0.99e6); - expect(await getPrice('USDC')).numEquals(1e6); - expect(await getPrice('USDT')).numEquals(1e6); - }); - }); - - describe("getAnchorInUsd", () => { - beforeEach(async () => { - ({delfi, anchorOracle, cTokens} = await setup()); - }); - - it("returns one with 6 decimals when given usd or usdt", async () => { - - let usdcPrice = "5812601720530109000000000000"; - const converted_usdc_price = await call(delfi, 'getAnchorInUsd', [cTokens.cUsdc._address, usdcPrice]); - const converted_usdc_price_p = await send(delfi, 'getAnchorInUsd', [cTokens.cUsdc._address, usdcPrice]); - expect(converted_usdc_price).toEqual(1e6.toString()); - - const converted_usdt_price = await call(delfi, 'getAnchorInUsd', [cTokens.cUsdt._address, usdcPrice]); - expect(converted_usdt_price).toEqual(1e6.toString()); - - }); - - // [open oracle symbol, proxy price in ether, open oracle price in usd] - [ - ["ETH", 1e18, 172.04e6], - ["SAI", 5285551943761727, 0.909326e6], // sai priced at 189, so drops to 90 cents here - ["DAI", 5905879257418508, 1.016047e6], - - ["BAT", 931592500000000, 0.160271e6], - ["REP", 56128970000000000, 9.656427e6], - ["ZRX", 985525000000000, 0.169549e6], - ["BTC", "399920015996800660000000000000", 6880.223955e6] // 8 decimals underlying -> 10 extra decimals on proxy - ].forEach(([openOracleKey, proxyPrice, expectedOpenOraclePrice]) => { - it(`converts ${openOracleKey} price through proxy usdc, with 6 decimals`, async () => { - // TODO: fix sai price test after SAI Global Settlement - let tokenAddress = await call(delfi, 'getCTokenAddress', [openOracleKey]); - await send(anchorOracle, 'setUnderlyingPrice', [tokenAddress, numToHex(proxyPrice)]); - // ~ $172 eth - let usdcPrice = "5812601720530109000000000000"; - const converted_price = await call(delfi, 'getAnchorInUsd', [tokenAddress, usdcPrice]); - expect(converted_price).toEqual(expectedOpenOraclePrice.toString()); - }); - }); - - }); - - describe("getUnderlyingPrice", () => { - beforeEach(async () => { - ( { - delfi, - anchorOracle, - postPrices, - primeAnchor, - cTokens, - source - } = await setup() ); - - // // ~ $172 eth - await primeAnchor("5812601720530109000000000000"); - await postPrices( - timestamp, - [[['ETH', 172.00]]], - ['ETH']); - }); - - - ["USDC", "USDT"].forEach((openOracleKey) => { - it(`returns 1 with 36 - 6 for ${openOracleKey}`, async () => { - let tokenAddress = await call(delfi, 'getCTokenAddress', [openOracleKey]); - const underlying_price = await call(delfi, 'getUnderlyingPrice', [tokenAddress]); - - expect(underlying_price).toEqual("1000000000000000000000000000000"); - }); - }); - - - it("returns source price with 36 - 8 decimals for BTC ", async () => { - await postPrices( - timestamp, - [[['BTC', 3443.00]]], - ['BTC']); - let tokenAddress = await call(delfi, 'getCTokenAddress', ["BTC"]); - const underlying_price = await call(delfi, 'getUnderlyingPrice', [tokenAddress]); - - const actualOpenOraclePrice = await call(delfi, 'prices', ["BTC"]); - - expect(underlying_price).numEquals("34430000000000000000000000000000"); - }); - - [ - ["ETH", 1e18, 173.04], - ["DAI", 5905879257418508, 1.01604], - ["BAT", 931592500000000, 0.16027], - ["REP", 56128970000000000, 9.6564], - ["ZRX", 985525000000000, 0.169549] - ].forEach(([openOracleKey, anchorPrice, openOraclePrice]) => { - it(`returns source price with 36 - 18 decimals for ${openOracleKey}`, async () => { - let tokenAddress = await call(delfi, 'getCTokenAddress', [openOracleKey]); - if (openOracleKey == "ETH") { - await send(anchorOracle, 'setPrice', [tokenAddress, numToHex(anchorPrice)]); - } else { - await send(anchorOracle, 'setUnderlyingPrice', [tokenAddress, numToHex(anchorPrice)]); - } - const post1 = await postPrices( - time() - 5, - [[[openOracleKey, openOraclePrice]]], - [openOracleKey], - source - ); - - const underlying_price = await call(delfi, 'getUnderlyingPrice', [tokenAddress]); - const actualOpenOraclePrice = openOraclePrice * 1e6; - - expect(underlying_price).toEqual(numToBigNum(actualOpenOraclePrice).mul(numToBigNum("1000000000000")).toString(10)); - }); - }); - - [ - // all current tokens have a reporter price - ].forEach(([openOracleKey, anchorPrice, openOraclePrice]) => { - it(`returns anchor price converted to dollars with 18 decimals for ${openOracleKey} converted through open oracle eth price`, async () => { - - let tokenAddress = await call(delfi, 'getCTokenAddress', [openOracleKey]); - await send(anchorOracle, 'setUnderlyingPrice', [tokenAddress, numToHex(anchorPrice)]); - const post1 = await postPrices( - time() - 5, - [[['ETH', 172.04]]], - ['ETH'], - source - ); - - const underlying_price = await call(delfi, 'getUnderlyingPrice', [tokenAddress]); - - expect(underlying_price).toEqual(openOraclePrice); - }); - }); - - [ - ["ETH", 1e18], - ["SAI", 5285551943761727], - ["DAI", 5905879257418508], - ["USDT", "5905879257418508000000000000"], - ["USDC", "5905879257418508000000000000"], - ["BAT", 931592500000000], - ["REP", 56128970000000000], - ["ZRX", 985525000000000], - ["BTC", "399920015996800660000000000000"] // 8 decimals underlying -> 10 extra decimals on proxy - ].forEach(([openOracleKey, proxyPrice]) => { - it(`returns anchor price if reporterBreaker is set for ${openOracleKey}`, async () => { - let tokenAddress = await call(delfi, 'getCTokenAddress', [openOracleKey]); - if (openOracleKey == "USDT") { - await send(anchorOracle, 'setUnderlyingPrice', [cTokens.cUsdc._address, numToHex(proxyPrice)]); - } else { - await send(anchorOracle, 'setUnderlyingPrice', [tokenAddress, numToHex(proxyPrice)]); - } - - const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; - let encoded = encodeRotationMessage(rotationTarget); - const [ signed ] = sign(encoded, source.privateKey); - - expect(await call(delfi, 'reporterBreaker', [])).toEqual(false); - - await send(delfi, 'invalidate', [encoded, signed.signature]); - expect(await call(delfi, 'reporterBreaker', [])).toEqual(true); - - expect(await call(delfi, 'getUnderlyingPrice', [tokenAddress])).numEquals(proxyPrice); - }); - }); - }); - - describe("invalidate", () => { - beforeEach(async () => { - ({ - source, - priceData, - delfi, - anchorOracle, - cTokens, - postPrices - } = await setup()); - }); - - it("reverts if given wrong message", async () => { - const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; - let encoded = web3.eth.abi.encodeParameters(['string', 'address'], ['stay still', rotationTarget]); - const [ signed ] = sign(encoded, source.privateKey); - - await expect( - send(delfi, 'invalidate', [encoded, signed.signature]) - ).rejects.toRevert("revert invalidation message must be 'rotate'"); - }); - - it("reverts if given wrong signature", async () => { - const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; - let encoded = encodeRotationMessage(rotationTarget); - // sign rotation message with wrong key - const [ signed ] = sign(encoded, '0x666ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); - - await expect( - send(delfi, 'invalidate', [encoded, signed.signature + "1"]) - ).rejects.toRevert("revert invalidation message must come from the reporter"); - - }); - - it("sets fallback flag to true if passes", async () => { - const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; - let encoded = encodeRotationMessage(rotationTarget); - // sign rotation message with wrong key - const [ signed ] = sign(encoded, source.privateKey); - expect(await call(delfi, 'reporterBreaker', [])).toEqual(false); - await send(delfi, 'invalidate', [encoded, signed.signature]); - - expect(await call(delfi, 'reporterBreaker', [])).toEqual(true); - }); - }); -}); diff --git a/tests/DelFiPriceTest.js b/tests/DelFiPriceTest.js deleted file mode 100644 index 644627f9..00000000 --- a/tests/DelFiPriceTest.js +++ /dev/null @@ -1,516 +0,0 @@ -const { encode, sign } = require('../sdk/javascript/.tsbuilt/reporter'); -const { time, numToHex } = require('./Helpers'); - -async function setup(N) { - const sources = [ - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf11', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf12', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf13', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf14', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf20', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf21', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf22', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf23', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf24' - ] - .slice(0, N) - .map(web3.eth.accounts.privateKeyToAccount.bind(web3.eth.accounts)); - - const nonSources = [ - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf15', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf16', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf17', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf18', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf19', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf25', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf26', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf27', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf28', - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf29' - ] - .slice(0, N) - .map(web3.eth.accounts.privateKeyToAccount.bind(web3.eth.accounts)); - - const anchor = web3.eth.accounts.privateKeyToAccount.bind(web3.eth.accounts)( - '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf40' - ); - - const anchorMantissa = numToHex(1e17); //1e17 equates to 10% tolerance for median to be above or below anchor - const priceData = await deploy('OpenOraclePriceData', []); - const delfi = await deploy('DelFiPrice', [ - priceData._address, - sources.map(a => a.address), - anchor.address, - anchorMantissa - ]); - - async function postPrices(timestamp, prices2dArr, symbols, signers) { - const messages = [], - signatures = []; - prices2dArr.forEach((prices, i) => { - const signed = sign( - encode( - 'prices', - timestamp, - prices.map(([symbol, price]) => [symbol, price]) - ), - signers[i].privateKey - ); - for (let { message, signature } of signed) { - messages.push(message); - signatures.push(signature); - } - }); - return send(delfi, 'postPrices', [messages, signatures, symbols], { - gas: 6000000 - }); - } - - async function getPrice(symbol) { - return call(delfi, 'prices', [symbol]); - } - - return { - sources, - nonSources, - anchor, - anchorMantissa, - priceData, - delfi, - postPrices, - getPrice - }; -} - -describe('DelFiPrice', () => { - let sources, - nonSources, - anchor, - anchorMantissa, - priceData, - delfi, - postPrices, - getPrice; - - const timestamp = time() - 5; - - describe('Deploy with an even # of sources', () => { - beforeEach(async done => { - // use 4 sources - ({ - sources, - nonSources, - anchor, - anchorMantissa, - priceData, - delfi, - postPrices, - getPrice - } = await setup(4)); - done(); - }); - - it('should sort even number of sources correctly', async () => { - // post prices for the anchor and 3 / 4 sources - const post1 = await postPrices( - timestamp, - [[['ETH', 498]], [['ETH', 501]], [['ETH', 502]], [['ETH', 503]]], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.gasUsed).toBeLessThan(250000); - expect(post1.events.PriceUpdated.returnValues.symbol).toBe('ETH'); - // last unused source is saved as 0 - expect(post1.events.PriceUpdated.returnValues.price).numEquals(501.5e6); - expect(await getPrice('ETH')).numEquals(501.5e6); - }); - - it('should sort even number of sources correctly with two assets', async () => { - // post prices for the anchor and 4 / 4 sources - const post1 = await postPrices( - timestamp, - [ - [['ETH', 498], ['BTC', 9900]], - [['ETH', 510], ['BTC', 11000]], - [['ETH', 499], ['BTC', 20000]], - [['ETH', 1], ['BTC', 100]], - [['ETH', 501], ['BTC', 9000]] - ], - ['ETH', 'BTC'], - [anchor, ...sources] - ); - expect(post1.gasUsed).toBeLessThan(550000); - - expect(post1.events.PriceUpdated[0].returnValues.symbol).toBe('ETH'); - // anchor: 498, sources: [1, 499, 501, 510], median = 500 - expect(post1.events.PriceUpdated[0].returnValues.price).numEquals(500e6); - expect(await getPrice('ETH')).numEquals(500e6); - - expect(post1.events.PriceUpdated[1].returnValues.symbol).toBe('BTC'); - // anchor: 9900, sources: [100, 9000, 11000, 20000], median = 10000 - expect(post1.events.PriceUpdated[1].returnValues.price).numEquals(10000e6); - expect(await getPrice('BTC')).numEquals(10000e6); - }); - }); - - describe('Deploy with an odd # of sources', () => { - beforeEach(async done => { - // use 5 sources - ({ - sources, - nonSources, - anchor, - anchorMantissa, - priceData, - delfi, - postPrices, - getPrice - } = await setup(5)); - done(); - }); - - it('posting single source should not record a median', async () => { - const post1 = await postPrices( - timestamp, - [[['ETH', 100]], [['ETH', 100]]], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.gasUsed).toBeLessThan(152000); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(await getPrice('ETH')).numEquals(0); - }); - - it('posting 0 anchor price should guard price and not revert', async () => { - const post1 = await postPrices( - timestamp, - [[['ETH', 0]], [['ETH', 91]], [['ETH', 110]], [['ETH', 110]]], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(post1.events.PricePosted).toBe(undefined); - expect(await getPrice('ETH')).numEquals(0); - }); - - it('posting some sources should yield correct median', async () => { - // post prices for 3 / 5 sources, and the anchor - const post1 = await postPrices( - timestamp, - [[['ETH', 100]], [['ETH', 91]], [['ETH', 110]], [['ETH', 110]]], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.gasUsed).toBeLessThan(253000); - expect(post1.events.PriceUpdated.returnValues.symbol).toBe('ETH'); - expect(post1.events.PriceUpdated.returnValues.price).numEquals(91e6); - expect(await getPrice('ETH')).numEquals(91e6); - - const post2 = await postPrices( - timestamp + 1, - [[['ETH', 200]], [['ETH', 218]], [['ETH', 220]], [['ETH', 230]]], - ['ETH'], - [anchor, ...sources] - ); - expect(post2.gasUsed).toBeLessThan(252000); - expect(post2.events.PriceUpdated.returnValues.symbol).toBe('ETH'); - expect(post2.events.PriceUpdated.returnValues.price).numEquals(218e6); - expect(await getPrice('ETH')).numEquals(218e6); - }); - - it('should not update median if anchor is much higher', async () => { - // median is 89. anchor is 100. at 10% tolerance, this should not update median - const post1 = await postPrices( - timestamp, - [ - [['ETH', 100]], //anchor - [['ETH', 80]], - [['ETH', 85]], - [['ETH', 89]], - [['ETH', 100]], - [['ETH', 110]] - ], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(await getPrice('ETH')).numEquals(0); - }); - - it('should not update median if anchor is much lower', async () => { - // median is 111. anchor is 100. at 10% tolerance, this should not update median - const post1 = await postPrices( - timestamp, - [ - [['ETH', 100]], //anchor - [['ETH', 100]], - [['ETH', 110]], - [['ETH', 111]], - [['ETH', 115]], - [['ETH', 116]] - ], - ['ETH'], - [anchor, ...sources] - ); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(post1.events.PriceGuarded).not.toBe(undefined); - expect(await getPrice('ETH')).numEquals(0); - }); - - it('posting all sources for two assets should sort correctly and yield correct median', async () => { - // post prices for the anchor and 4 / 4 sources - const post1 = await postPrices( - timestamp, - [ - [['ETH', 498], ['BTC', 9900]], //anchor - [['ETH', 510], ['BTC', 11000]], - [['ETH', 499], ['BTC', 20000]], - [['ETH', 1], ['BTC', 100]], - [['ETH', 501], ['BTC', 9000]], - [['ETH', 502], ['BTC', 10200]] - ], - ['ETH', 'BTC'], - [anchor, ...sources] - ); - expect(post1.gasUsed).toBeLessThan(650000); - - expect(post1.events.PriceUpdated[0].returnValues.symbol).toBe('ETH'); - // anchor: 498, sources: [1, 499, 501, 502, 510], median = 501 - expect(post1.events.PriceUpdated[0].returnValues.price).numEquals(501e6); - expect(await getPrice('ETH')).numEquals(501e6); - - expect(post1.events.PriceUpdated[1].returnValues.symbol).toBe('BTC'); - // anchor: 9900, sources: [100, 9000, 10200, 11000, 20000], median = 10000 - expect(post1.events.PriceUpdated[1].returnValues.price).numEquals(10200e6); - expect(await getPrice('BTC')).numEquals(10200e6); - }); - - it('view should use most recent post with two sources', async () => { - // post prices for 5 / 5 sources, and the anchor - const post1 = await postPrices( - timestamp, - [ - [['ETH', 498], ['BTC', 9900]], //anchor - [['ETH', 510], ['BTC', 11000]], - [['ETH', 499], ['BTC', 20000]], - [['ETH', 1], ['BTC', 100]], - [['ETH', 501], ['BTC', 9000]], - [['ETH', 502], ['BTC', 10200]] - ], - ['ETH', 'BTC'], - [anchor, ...sources] - ); - - // anchor: 498, sources: [1, 499, 501, 502, 510], median = 501 - // anchor: 9900, sources: [100, 9000, 10200, 11000, 20000], median = 10000 - - const post2 = await postPrices( - timestamp + 1, - [ - [['ETH', 498], ['BTC', 9900]], //anchor - [['ETH', 510], ['BTC', 11000]], - [['ETH', 499], ['BTC', 20000]], - [['ETH', 1], ['BTC', 100]], - [['ETH', 503], ['BTC', 9000]], - [['ETH', 502], ['BTC', 10500]] - ], - ['ETH', 'BTC'], - [anchor, ...sources] - ); - - // anchor: 498, sources: [1, 499, 502, 503, 510], median = 502 - expect(post2.events.PriceUpdated[0].returnValues.price).numEquals(502e6); - expect(await getPrice('ETH')).numEquals(502e6); - - // anchor: 99, sources: [100, 9000, 10500, 11000, 20000], median = 10500 - expect(post2.events.PriceUpdated[1].returnValues.price).numEquals(10500e6); - expect(await getPrice('BTC')).numEquals(10500e6); - }); - - it('should revert on posting invalid message', async () => { - await expect( - send(delfi, 'postPrices', [['0xabc'], ['0x123'], []], { gas: 5000000 }) - ).rejects.toRevert(); - }); - - it('posting from non-source should not change median or emit event', async () => { - // set some baseline numbers - await postPrices( - timestamp, - [ - [['ETH', 100]], //anchor - [['ETH', 100]], - [['ETH', 100]], - [['ETH', 100]], - [['ETH', 100]], - [['ETH', 100]] - ], - ['ETH'], - [anchor, ...sources] - ); - - const post1 = await postPrices( - timestamp + 1, - [[['ETH', 95]]], - ['ETH'], - [...nonSources] - ); - expect(post1.events.PriceGuarded).toBe(undefined); - expect(post1.events.PriceUpdated).toBe(undefined); - expect(await getPrice('ETH')).numEquals(100e6); - }); - }); - - describe.skip('Deploy with many sources', () => { - beforeEach(async done => { - ({ delfi, timestamp, postPrices, getPrice } = await setup(10)); - done(); - }); - - it('quantifies the amount of gas used for a substantial set of updates', async () => { - const big = [ - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.34], - ['BTC', 8500], - ['DAI', 1], - ['ETH', 256], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.35], - ['BTC', 8000], - ['DAI', 1], - ['ETH', 255], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.35], - ['BTC', 8000], - ['DAI', 1], - ['ETH', 255], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.35], - ['BTC', 8000], - ['DAI', 1], - ['ETH', 255], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ], - - [ - ['ABC', 0.35], - ['DEF', 8000], - ['GHI', 0.35], - ['JKL', 8000], - ['BAT', 0.33], - ['BTC', 9000], - ['DAI', 1], - ['ETH', 257], - ['REP', 0.34], - ['ZRX', 0.34] - ] - ]; - - const postA = await postPrices(timestamp, big, big[0].map(([k]) => k)); - expect(postA.gasUsed).toBeLessThan(5.4e6); - - const postB = await postPrices(timestamp + 1, big, big[0].map(([k]) => k)); - expect(postB.gasUsed).toBeLessThan(3.7e6); - - const postC = await postPrices(timestamp + 1, big, big[0].map(([k]) => k)); - expect(postC.gasUsed).toBeLessThan(2.8e6); - }, 120000); - }); -}); \ No newline at end of file diff --git a/tests/Helpers.js b/tests/Helpers.js index d47aab61..e1301d2b 100644 --- a/tests/Helpers.js +++ b/tests/Helpers.js @@ -1,7 +1,12 @@ const Web3 = require('web3'); +const BigNumber = require("bignumber.js"); const web3 = new Web3(); // no provider, since we won't make any calls +const fixed = num => { + return (new BigNumber(num).toFixed()); +}; + function uint(n) { return web3.utils.toBN(n).toString(); } @@ -34,13 +39,19 @@ function time(){ return Math.floor(new Date() / 1000); } -function sendRPC(web3, method, params) { +async function currentBlockTimestamp(web3_) { + const blockNumber = await sendRPC(web3_, "eth_blockNumber", []); + const block = await sendRPC(web3_, "eth_getBlockByNumber", [ blockNumber.result, false]); + return block.result.timestamp; +} + +function sendRPC(web3_, method, params) { return new Promise((resolve, reject) => { - if (!web3.currentProvider || typeof (web3.currentProvider) === 'string') { - return reject(`cannot send from currentProvider=${web3.currentProvider}`); + if (!web3_.currentProvider || typeof (web3_.currentProvider) === 'string') { + return reject(`cannot send from currentProvider=${web3_.currentProvider}`); } - web3.currentProvider.send( + web3_.currentProvider.send( { jsonrpc: '2.0', method: method, @@ -59,13 +70,15 @@ function sendRPC(web3, method, params) { } module.exports = { - sendRPC, - address, - bytes, - time, - numToBigNum, - numToHex, - uint256, - uint, - keccak256 + sendRPC, + address, + bytes, + time, + numToBigNum, + numToHex, + uint256, + uint, + keccak256, + currentBlockTimestamp, + fixed }; diff --git a/tests/IntegrationTest.js b/tests/IntegrationTest.js deleted file mode 100644 index 99ae1786..00000000 --- a/tests/IntegrationTest.js +++ /dev/null @@ -1,67 +0,0 @@ -const path = require('path'); -const Web3 = require('web3'); -const compose = require('docker-compose'); -const contract = require('eth-saddle/dist/contract'); -const { exec } = require('child_process'); -const util = require('util'); -const DockerProvider = require('./DockerProvider'); - -const execute = util.promisify(exec); - -const projectName = "open-oracle"; -const root = path.join(__dirname, '..'); -const composeOptions = {cwd: root, log: true, composeOptions: ["--project-name", projectName]}; - -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function waitForLogs(serviceLogPairs) { - let results = await Promise.all(Object.entries(serviceLogPairs).map(async ([service, log]) => { - const serviceLogs = await compose.logs([service], composeOptions); - - if (!serviceLogs.out.includes(log)) { - console.log(`Waiting for logs ${JSON.stringify(serviceLogPairs)}`); - console.log(serviceLogs.out); - return false; - } else { - return true; - } - })); - - let complete = results.every((x) => x === true); - - if (complete) { - return; - } else { - await sleep(5000); - await waitForLogs(serviceLogPairs); - } -} - -describe('Integration', () => { - it('deploys the contracts, starts reporters and posts the right prices', async () => { - try { - await execute(`rm -rf ".dockerbuild"`); - await execute(`rm -rf ".dockerbuild_cp"`); - const deployer = `${projectName}_deployer_1`; - const reporter = `${projectName}_reporter-1_1`; - - await compose.upAll(composeOptions); - await waitForLogs({deployer: "Deployed DelFiPrice", poster: "main completed", "reporter-1": "Reporter listening", ganache: "Listening on 0.0.0.0:8545"}); - - await execute(`docker cp "${deployer}:/build" ".dockerbuild_cp"`); - - const web3 = new Web3(new DockerProvider('http://ganache:8545', reporter)); - const accounts = await web3.eth.getAccounts(); - - const delfi = await contract.getContractAt(web3, 'DelFiPrice', {build_dir: '.dockerbuild_cp', trace: false, extra_build_files: []}, '0x5b1869D9A4C187F2EAa108f3062412ecf0526b24'); - - expect(await delfi.methods.prices('BTC').call({from: accounts[0]})).numEquals(0); - expect(await delfi.methods.prices('ETH').call({from: accounts[0]})).numEquals('260000000'); - expect(await delfi.methods.prices('ZRX').call({from: accounts[0]})).numEquals('580000'); - } finally { - await compose.down({cwd: root}); - } - }, 180000); -}); diff --git a/tests/Matchers.js b/tests/Matchers.js index ec3c2629..5454e4e7 100644 --- a/tests/Matchers.js +++ b/tests/Matchers.js @@ -1,9 +1,26 @@ +const BigNumber = require("bignumber.js"); expect.extend({ + addrEquals(actual, expected) { + return { + pass: actual.toLowerCase() == expected.toLowerCase(), + message: () => `expected (${actual}) == (${expected})` + } + }, + numEquals(actual, expected) { return { pass: actual.toString() == expected.toString(), - message: () => `expected ${JSON.stringify(actual)} (${actual.toString()}) == ${JSON.stringify(expected)} (${expected.toString()})` + message: () => `expected (${actual.toString()}) == (${expected.toString()})` + } + } +}); + +expect.extend({ + greaterThan(actual, expected) { + return { + pass: (new BigNumber(actual)).gt(new BigNumber(expected)), + message: () => `expected ${actual.toString()} to be greater than ${expected.toString()}` } } }); @@ -12,7 +29,26 @@ expect.extend({ toRevert(actual, msg='revert') { return { pass: !!actual['message'] && actual.message === `VM Exception while processing transaction: ${msg}`, - message: () => `expected revert, got: ${JSON.stringify(actual)}` + message: () => `expected revert, got: ${actual && actual.message ? actual : JSON.stringify(actual)}` } } }); + +expect.extend({ + toBeWithinRange(received, floor, ceiling) { + const pass = received >= floor && received <= ceiling; + if (pass) { + return { + message: () => + `expected ${received} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${received} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } + }, +}); diff --git a/tests/NonReporterPricesTest.js b/tests/NonReporterPricesTest.js index 32930414..6444c4d9 100644 --- a/tests/NonReporterPricesTest.js +++ b/tests/NonReporterPricesTest.js @@ -36,37 +36,44 @@ describe('UniswapAnchoredView', () => { }); it('reverts if ETH has no uniswap market', async () => { - const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: address(0), isUniswapReversed: true}; - const SAI = {cToken: address(5), underlying: address(6), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; - const priceData = await deploy("OpenOraclePriceData", []); - expect(deploy('UniswapAnchoredView', [priceData._address, address(0), 0, 0, [ETH, SAI]])).rejects.toRevert('revert reported prices must have an anchor'); + if (!coverage) { + // This test for some reason is breaking coverage in CI, skip for now + const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: address(0), isUniswapReversed: true}; + const SAI = {cToken: address(5), underlying: address(6), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; + const priceData = await deploy("OpenOraclePriceData", []); + expect(deploy('UniswapAnchoredView', [priceData._address, address(0), 0, 0, [ETH, SAI]])).rejects.toRevert('revert reported prices must have an anchor'); + } }); it('reverts if non-reporter has a uniswap market', async () => { - const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: 14, uniswapMarket: address(112), isUniswapReversed: true}; - const SAI = {cToken: address(5), underlying: address(6), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; - const priceData = await deploy("OpenOraclePriceData", []); - expect(deploy('UniswapAnchoredView', [priceData._address, address(0), 0, 0, [ETH, SAI]])).rejects.toRevert('revert only reported prices utilize an anchor'); + if (!coverage) { + const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: 14, uniswapMarket: address(112), isUniswapReversed: true}; + const SAI = {cToken: address(5), underlying: address(6), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; + const priceData = await deploy("OpenOraclePriceData", []); + expect(deploy('UniswapAnchoredView', [priceData._address, address(0), 0, 0, [ETH, SAI]])).rejects.toRevert('revert only reported prices utilize an anchor'); + } }); it('handles fixed_eth prices', async () => { - const usdc_eth_pair = await deploy("MockUniswapTokenPair", [ - "1865335786147", - "8202340665419053945756", - "1593755855", - "119785032308978310142960133641565753500432674230537", - "5820053774558372823476814618189", - ]); - const reporter = "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"; - const messages = ["0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000"]; - const signatures = ["0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b"]; - const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: usdc_eth_pair._address, isUniswapReversed: true}; - const SAI = {cToken: address(7), underlying: address(8), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; - const priceData = await deploy("OpenOraclePriceData", []); - const oracle = await deploy('UniswapAnchoredView', [priceData._address, reporter, uint(20e16), 60, [ETH, SAI]]); - await sendRPC(web3, 'evm_increaseTime', [30 * 60]); - await send(oracle, "postPrices", [messages, signatures, ['ETH']]); - expect(await call(oracle, 'price', ["ETH"])).numEquals(226815000); - expect(await call(oracle, 'price', ["SAI"])).numEquals(1198842); + if (!coverage) { + const usdc_eth_pair = await deploy("MockUniswapTokenPair", [ + "1865335786147", + "8202340665419053945756", + "1593755855", + "119785032308978310142960133641565753500432674230537", + "5820053774558372823476814618189", + ]); + const reporter = "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"; + const messages = ["0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000"]; + const signatures = ["0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b"]; + const ETH = {cToken: address(5), underlying: address(6), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: usdc_eth_pair._address, isUniswapReversed: true}; + const SAI = {cToken: address(7), underlying: address(8), symbolHash: keccak256("SAI"), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(5285551943761727), uniswapMarket: address(0), isUniswapReversed: false}; + const priceData = await deploy("OpenOraclePriceData", []); + const oracle = await deploy('UniswapAnchoredView', [priceData._address, reporter, uint(20e16), 60, [ETH, SAI]]); + await sendRPC(web3, 'evm_increaseTime', [30 * 60]); + await send(oracle, "postPrices", [messages, signatures, ['ETH']]); + expect(await call(oracle, 'price', ["ETH"])).numEquals(226815000); + expect(await call(oracle, 'price', ["SAI"])).numEquals(1198842); + } }); }); \ No newline at end of file diff --git a/tests/PostRealWorldPricesTest.js b/tests/PostRealWorldPricesTest.js index 5676cfad..ee6a8deb 100644 --- a/tests/PostRealWorldPricesTest.js +++ b/tests/PostRealWorldPricesTest.js @@ -1,23 +1,11 @@ // @notice UniswapAnchoredView `postPrices` test // Based on data from Coinbase oracle https://api.pro.coinbase.com/oracle and Uniswap token pairs at July 2nd 2020. -const { sendRPC } = require('./Helpers'); +const BN = require("bignumber.js"); +const { sendRPC, address, uint, keccak256, numToHex } = require('./Helpers'); -function address(n) { - return `0x${n.toString(16).padStart(40, "0")}`; -} - -function uint(n) { - return web3.utils.toBN(n).toString(); -} - -function keccak256(str) { - return web3.utils.keccak256(str); -} - -function numToHex(num) { - return web3.utils.numberToHex(num); -} +// Cut all digits after decimal point +BN.set({ DECIMAL_PLACES: 0, ROUNDING_MODE: 3 }) async function setupTokenPairs() { // Reversed market for ETH, read value of ETH in USDC @@ -29,7 +17,7 @@ async function setupTokenPairs() { "5820053774558372823476814618189", ]); - // Initialize DAI_ETH pair with values from mainnet + // Initialize DAI pair with values from mainnet const dai_eth_pair = await deploy("MockUniswapTokenPair", [ "3435618131150076101237553", "15407572689721099289685", @@ -38,7 +26,7 @@ async function setupTokenPairs() { "5069668089169215245120760905619375569156736", ]); - // Initialize REP_ETH pair with values from mainnet + // Initialize REP pair with values from mainnet const rep_eth_pair = await deploy("MockUniswapTokenPair", [ "40867690797665090689823", "3089126268851209725535", @@ -47,7 +35,7 @@ async function setupTokenPairs() { "315226499991023307900665225550194785606382", ]); - // Initialize BAT_ETH pair with values from mainnet + // Initialize BAT pair with values from mainnet const bat_eth_pair = await deploy("MockUniswapTokenPair", [ "2809215824116494014601294", "3000910749924336260251", @@ -56,7 +44,7 @@ async function setupTokenPairs() { "22353658718734403427774753736831427982055589" ]); - // Initialize ETH_ZRX pair with values from mainnet + // Initialize ZRX pair with values from mainnet // Reversed market const eth_zrx_pair = await deploy("MockUniswapTokenPair", [ "259245497861929182740", @@ -66,7 +54,7 @@ async function setupTokenPairs() { "30665287778536822167996154892216941694", ]); - // Initialize WBTC_ETH pair with values from mainnet + // Initialize BTC pair with values from mainnet const wbtc_eth_pair = await deploy("MockUniswapTokenPair", [ "4744946699", "1910114633221652017296", @@ -75,7 +63,7 @@ async function setupTokenPairs() { "49529064100184996951568929095", ]); - // Initialize COMP_ETH pair with values from mainnet + // Initialize COMP pair with values from mainnet const comp_eth_pair = await deploy("MockUniswapTokenPair", [ "2726069269242972517844", "2121223809443892142647", @@ -84,7 +72,7 @@ async function setupTokenPairs() { "10471832919000882624476515664573920963717" ]) - // Initialize LINK_ETH pair with values from mainnet + // Initialize LINK pair with values from mainnet const link_eth_pair = await deploy("MockUniswapTokenPair", [ "115522168522463195428450", "2448717634007234031730", @@ -93,7 +81,7 @@ async function setupTokenPairs() { "1098123734917468235191126600400328121343356", ]) - // Initialize ETH_KNC pair with values from mainnet + // Initialize KNC pair with values from mainnet // Reversed market const eth_knc_pair = await deploy("MockUniswapTokenPair", [ "2071741256888346573966", @@ -104,77 +92,172 @@ async function setupTokenPairs() { ]) return { - USDC_ETH: usdc_eth_pair._address, - DAI_ETH: dai_eth_pair._address, - REP_ETH: rep_eth_pair._address, - BAT_ETH: bat_eth_pair._address, - ETH_ZRX: eth_zrx_pair._address, - WBTC_ETH: wbtc_eth_pair._address, - COMP_ETH: comp_eth_pair._address, - ETH_KNC: eth_knc_pair._address, - LINK_ETH: link_eth_pair._address, + ETH: usdc_eth_pair, + DAI: dai_eth_pair, + REP: rep_eth_pair, + BAT: bat_eth_pair, + ZRX: eth_zrx_pair, + BTC: wbtc_eth_pair, + COMP: comp_eth_pair, + LINK: link_eth_pair, + KNC: eth_knc_pair, } } -async function setupUniswapAnchoredView() { +async function setupUniswapAnchoredView(pairs) { + const PriceSource = { + FIXED_ETH: 0, + FIXED_USD: 1, + REPORTER: 2 + }; + const reporter = "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"; const anchorMantissa = numToHex(1e17); //1e17 equates to 10% tolerance for source price to be above or below anchor const priceData = await deploy("OpenOraclePriceData", []); const anchorPeriod = 30 * 60; - const pairs = await setupTokenPairs(); const tokenConfigs = [ - {cToken: address(1), underlying: address(1), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.USDC_ETH, isUniswapReversed: true}, - {cToken: address(2), underlying: address(2), symbolHash: keccak256("DAI"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.DAI_ETH, isUniswapReversed: false}, - {cToken: address(3), underlying: address(3), symbolHash: keccak256("REP"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.REP_ETH, isUniswapReversed: false}, - {cToken: address(4), underlying: address(4), symbolHash: keccak256("BAT"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.BAT_ETH, isUniswapReversed: false}, - {cToken: address(5), underlying: address(5), symbolHash: keccak256("ZRX"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.ETH_ZRX, isUniswapReversed: true}, - {cToken: address(6), underlying: address(6), symbolHash: keccak256("BTC"), baseUnit: uint(1e8), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.WBTC_ETH, isUniswapReversed: false}, - {cToken: address(7), underlying: address(7), symbolHash: keccak256("COMP"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.COMP_ETH, isUniswapReversed: false}, - {cToken: address(8), underlying: address(8), symbolHash: keccak256("KNC"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.ETH_KNC, isUniswapReversed: true}, - {cToken: address(9), underlying: address(9), symbolHash: keccak256("LINK"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.LINK_ETH, isUniswapReversed: false}, + {cToken: address(1), underlying: address(1), symbolHash: keccak256("ETH"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.ETH._address, isUniswapReversed: true}, + {cToken: address(2), underlying: address(2), symbolHash: keccak256("DAI"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.DAI._address, isUniswapReversed: false}, + {cToken: address(3), underlying: address(3), symbolHash: keccak256("REP"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.REP._address, isUniswapReversed: false}, + {cToken: address(4), underlying: address(4), symbolHash: keccak256("BAT"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.BAT._address, isUniswapReversed: false}, + {cToken: address(5), underlying: address(5), symbolHash: keccak256("ZRX"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.ZRX._address, isUniswapReversed: true}, + {cToken: address(6), underlying: address(6), symbolHash: keccak256("BTC"), baseUnit: uint(1e8), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.BTC._address, isUniswapReversed: false}, + {cToken: address(7), underlying: address(7), symbolHash: keccak256("COMP"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.COMP._address, isUniswapReversed: false}, + {cToken: address(8), underlying: address(8), symbolHash: keccak256("KNC"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.KNC._address, isUniswapReversed: true}, + {cToken: address(9), underlying: address(9), symbolHash: keccak256("LINK"), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: pairs.LINK._address, isUniswapReversed: false}, ]; - const uniswapAnchoredView = await deploy("UniswapAnchoredView", [priceData._address, reporter, anchorMantissa, anchorPeriod, tokenConfigs]); - return [uniswapAnchoredView, pairs]; + return deploy("UniswapAnchoredView", [priceData._address, reporter, anchorMantissa, anchorPeriod, tokenConfigs]); } -const PriceSource = { - FIXED_ETH: 0, - FIXED_USD: 1, - REPORTER: 2 -}; +async function setup() { + const pairs = await setupTokenPairs(); + const uniswapAnchoredView = await setupUniswapAnchoredView(pairs); -describe("UniswapAnchoredView, postPrices test", () => { + function isReversedMarket(name) { + return name == "ETH" || name == "ZRX" || name == "KNC"; + } - it("basic scenario, use real world data", async () => { - const [uniswapAnchoredView, _] = await setupUniswapAnchoredView(); - await sendRPC(web3, "evm_increaseTime", [31 * 60]); + function fraction(numerator, denominator){ + return new BN(numerator).multipliedBy(new BN(2).pow(112)).mod(new BN(2).pow(224)).dividedBy(denominator).mod(new BN(2).pow(224)); + } - const messages = [ - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000021e69e1300000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe2000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000f81f90000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034441490000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000010798780000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035245500000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000005707f0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a52580000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000003b8920000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000018f18c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034b4e430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000049208c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044c494e4b00000000000000000000000000000000000000000000000000000000", - ]; + // helper function that returns the current block timestamp within the range of uint32, i.e. [0, 2**32 - 1] + async function currentBlockTimestamp() { + const blockNumber = await sendRPC(web3, "eth_blockNumber", []); + const block = await sendRPC(web3, "eth_getBlockByNumber", [ blockNumber.result, false]); + return block.result.timestamp; + } - const signatures = [ - "0xe64be3c6153c0f450062ebb3bb93b48d8e2bec11030dbe7b639ab6c78a5edf688f4fd9ba51c7589c15e384b952a2dc4943d402871163aca12197fd08568c4b9f000000000000000000000000000000000000000000000000000000000000001c", - "0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b", - "0xac0731a325943a92f3745be5853f54ae110d889a408805e076ad9b5bc0bb1f4c1a994aebfb27a09156234fd1b27abf0fc19f667ea48c27a0c2a5d58c0243b99b000000000000000000000000000000000000000000000000000000000000001c", - "0x77058eaa98c77df280e069fb3c751b95aff57827a730458b48d773721432c42293f3b5d21b475169a59140e31196d333ca0f2b5935d2d86563df7a4e5cbc1e24000000000000000000000000000000000000000000000000000000000000001c", - "0x4758081209589c08db900e84d32393b3a37d9041a8e722e977dc3fff8affe0847d1dcb5c80496ea2f9314b3d6fcde03f8e7a896ba28dde6e2a003972da9e2b25000000000000000000000000000000000000000000000000000000000000001b", - "0x8f69f85dc792d657238a9a766b3cdb6c17c789d24a2b4c0d59eec079602aecfaf3d79d87a4ed430ef6889c46e03c82b5c616f9b968c4b757a89c1792a02dee13000000000000000000000000000000000000000000000000000000000000001b", - "0x25005922d67f6f446667de7e3052a2e97cf6b74bd01be62b478e16e3d72a3ecc5582fb44a3501fa359c2d6b5794844713c584740938e4012a1b0e3371e61a8a6000000000000000000000000000000000000000000000000000000000000001b", - "0xe393df120a0d95b8dea2ab693e4c89b4faf867c66636305bb1199e53cff95f43a22889e148a5bd85105173a102c3167f7716d4eca6a89a0120bef6479e812011000000000000000000000000000000000000000000000000000000000000001b", - ]; + async function currentCumulativePrice(pair, isReversedMarket = false) { + const blockTimestamp = await currentBlockTimestamp(); + return [await getCumulativePrice(pair, blockTimestamp, isReversedMarket), blockTimestamp]; + } + + async function currentCumulativePriceDelta(pair, timeElapsed, isReversedMarket = false) { + const fractionDelta = await call(pair, "getReservesFraction", [isReversedMarket]); + return new BN(fractionDelta).multipliedBy(timeElapsed); + } + + async function getCumulativePrice(pair, timestamp, isReversedMarket = false) { + const blockTimestamp = new BN(timestamp).mod(new BN(2).pow(32)); + let priceCumulative = isReversedMarket ? await call(pair, "price1CumulativeLast", []): await call(pair, "price0CumulativeLast", []); + + const blockTimestampLast = await call(pair, "blockTimestampLast", []); + if (blockTimestampLast != blockTimestamp.toString()) { + const timeElapsed = blockTimestamp.minus(new BN(blockTimestampLast)); + + const fractionDelta = await call(pair, "getReservesFraction", [isReversedMarket]); + const priceDelta = new BN(fractionDelta).multipliedBy(timeElapsed); + priceCumulative = new BN(priceCumulative).plus(priceDelta); + } + + return priceCumulative; + } - // No data for COMP from Coinbase so far, it is not added to the oracle yet - const symbols = ["BTC", "ETH", "DAI", "REP", "ZRX", "BAT", "KNC", "LINK"]; + function decode(price) { + return price.multipliedBy(1e18).dividedBy(new BN(2).pow(112)) + } + + function calculateTWAP(priceCumulativeOld, priceCumulativeNew, timestampOld, timestampNew) { + const timeElapsed = new BN(timestampNew).minus(new BN(timestampOld)); + return decode(new BN(priceCumulativeNew).minus(new BN(priceCumulativeOld)).dividedBy(timeElapsed)); + } + + const messages = [ + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000021e69e1300000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe2000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000f81f90000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034441490000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000010798780000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035245500000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000005707f0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a52580000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000003b8920000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000018f18c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034b4e430000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000049208c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044c494e4b00000000000000000000000000000000000000000000000000000000", + ]; + + const signatures = [ + "0xe64be3c6153c0f450062ebb3bb93b48d8e2bec11030dbe7b639ab6c78a5edf688f4fd9ba51c7589c15e384b952a2dc4943d402871163aca12197fd08568c4b9f000000000000000000000000000000000000000000000000000000000000001c", + "0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b", + "0xac0731a325943a92f3745be5853f54ae110d889a408805e076ad9b5bc0bb1f4c1a994aebfb27a09156234fd1b27abf0fc19f667ea48c27a0c2a5d58c0243b99b000000000000000000000000000000000000000000000000000000000000001c", + "0x77058eaa98c77df280e069fb3c751b95aff57827a730458b48d773721432c42293f3b5d21b475169a59140e31196d333ca0f2b5935d2d86563df7a4e5cbc1e24000000000000000000000000000000000000000000000000000000000000001c", + "0x4758081209589c08db900e84d32393b3a37d9041a8e722e977dc3fff8affe0847d1dcb5c80496ea2f9314b3d6fcde03f8e7a896ba28dde6e2a003972da9e2b25000000000000000000000000000000000000000000000000000000000000001b", + "0x8f69f85dc792d657238a9a766b3cdb6c17c789d24a2b4c0d59eec079602aecfaf3d79d87a4ed430ef6889c46e03c82b5c616f9b968c4b757a89c1792a02dee13000000000000000000000000000000000000000000000000000000000000001b", + "0x25005922d67f6f446667de7e3052a2e97cf6b74bd01be62b478e16e3d72a3ecc5582fb44a3501fa359c2d6b5794844713c584740938e4012a1b0e3371e61a8a6000000000000000000000000000000000000000000000000000000000000001b", + "0xe393df120a0d95b8dea2ab693e4c89b4faf867c66636305bb1199e53cff95f43a22889e148a5bd85105173a102c3167f7716d4eca6a89a0120bef6479e812011000000000000000000000000000000000000000000000000000000000000001b", + ]; + + return { + uniswapAnchoredView, + pairs, + messages, + signatures, + isReversedMarket, + decode, + fraction, + currentCumulativePrice, + currentCumulativePriceDelta, + getCumulativePrice, + calculateTWAP + } +} + +describe("UniswapAnchoredView", () => { + // No data for COMP from Coinbase so far, it is not added to the oracle yet + const symbols = ["BTC", "ETH", "DAI", "REP", "ZRX", "BAT", "KNC", "LINK"]; + beforeEach(async done => { + ({ + uniswapAnchoredView, + pairs, + messages, + signatures, + isReversedMarket, + decode, + fraction, + currentBlockTimestamp, + currentCumulativePrice, + currentCumulativePriceDelta, + getCumulativePrice, + calculateTWAP + } = await setup()); + done(); + }); + + it("check initialization of cumulative prices", async () => { + await Promise.all(Object.keys(pairs).map(async (key) => { + const [price, timestamp] = await currentCumulativePrice(pairs[key], isReversedMarket(key)); + const oldObservation = await call(uniswapAnchoredView, "oldObservations", [keccak256(key)]); + const newObservation = await call(uniswapAnchoredView, "newObservations", [keccak256(key)]); + // Sometimes `timestamp` and observation.timestamp are different, adjust cumulative prices to reflect difference + const diff = await currentCumulativePriceDelta(pairs[key], new BN(timestamp).minus(oldObservation.timestamp).abs().toFixed(), isReversedMarket(key)); + expect(diff.plus(price).toFixed()).toBe(oldObservation.acc); + expect(diff.plus(price).toFixed()).toBe(newObservation.acc); + expect(oldObservation.timestamp).toBe(newObservation.timestamp); + })); + }); + + it("basic scenario, use real world data", async () => { + await sendRPC(web3, "evm_increaseTime", [31 * 60]); await send(uniswapAnchoredView, "postPrices", [ messages, @@ -207,42 +290,15 @@ describe("UniswapAnchoredView, postPrices test", () => { expect(link_price).toBe("4792460"); }); - it("test price events", async () => { - const [uniswapAnchoredView, pairs] = await setupUniswapAnchoredView(); + it("test price events - PriceUpdated, PriceGuarded", async () => { await sendRPC(web3, "evm_increaseTime", [31 * 60]); - const messages = [ - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000021e69e1300000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe2000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000f81f90000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034441490000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000010798780000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035245500000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000005707f0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a52580000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000003b8920000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000018f18c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034b4e430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000049208c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044c494e4b00000000000000000000000000000000000000000000000000000000", - ]; - - const signatures = [ - "0xe64be3c6153c0f450062ebb3bb93b48d8e2bec11030dbe7b639ab6c78a5edf688f4fd9ba51c7589c15e384b952a2dc4943d402871163aca12197fd08568c4b9f000000000000000000000000000000000000000000000000000000000000001c", - "0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b", - "0xac0731a325943a92f3745be5853f54ae110d889a408805e076ad9b5bc0bb1f4c1a994aebfb27a09156234fd1b27abf0fc19f667ea48c27a0c2a5d58c0243b99b000000000000000000000000000000000000000000000000000000000000001c", - "0x77058eaa98c77df280e069fb3c751b95aff57827a730458b48d773721432c42293f3b5d21b475169a59140e31196d333ca0f2b5935d2d86563df7a4e5cbc1e24000000000000000000000000000000000000000000000000000000000000001c", - "0x4758081209589c08db900e84d32393b3a37d9041a8e722e977dc3fff8affe0847d1dcb5c80496ea2f9314b3d6fcde03f8e7a896ba28dde6e2a003972da9e2b25000000000000000000000000000000000000000000000000000000000000001b", - "0x8f69f85dc792d657238a9a766b3cdb6c17c789d24a2b4c0d59eec079602aecfaf3d79d87a4ed430ef6889c46e03c82b5c616f9b968c4b757a89c1792a02dee13000000000000000000000000000000000000000000000000000000000000001b", - "0x25005922d67f6f446667de7e3052a2e97cf6b74bd01be62b478e16e3d72a3ecc5582fb44a3501fa359c2d6b5794844713c584740938e4012a1b0e3371e61a8a6000000000000000000000000000000000000000000000000000000000000001b", - "0xe393df120a0d95b8dea2ab693e4c89b4faf867c66636305bb1199e53cff95f43a22889e148a5bd85105173a102c3167f7716d4eca6a89a0120bef6479e812011000000000000000000000000000000000000000000000000000000000000001b", - ]; - - // No data for COMP from Coinbase so far, it is not added to the oracle yet - const symbols = ["BTC", "ETH", "DAI", "REP", "ZRX", "BAT", "KNC", "LINK"]; - const postRes = await send(uniswapAnchoredView, "postPrices", [ messages, signatures, symbols, ]); - const anchorEvents = postRes.events.AnchorPriceUpdate; const priceUpdatedEvents = postRes.events.PriceUpdated; const priceGuardedEvents = postRes.events.PriceGuarded; @@ -251,87 +307,132 @@ describe("UniswapAnchoredView, postPrices test", () => { // Check price updates priceUpdatedEvents.forEach((updateEvent) => { - if (updateEvent.returnValues.symbol == "BTC") { - expect(updateEvent.returnValues.price).toBe("9100190000"); - } - if (updateEvent.returnValues.symbol == "ETH") { - expect(updateEvent.returnValues.price).toBe("226815000"); - } - if (updateEvent.returnValues.symbol == "DAI") { - expect(updateEvent.returnValues.price).toBe("1016313"); - } - if (updateEvent.returnValues.symbol == "ZRX") { - expect(updateEvent.returnValues.price).toBe("356479"); - } - if (updateEvent.returnValues.symbol == "REP") { - expect(updateEvent.returnValues.price).toBe("17275000"); - } - if (updateEvent.returnValues.symbol == "BAT") { - expect(updateEvent.returnValues.price).toBe("243858"); - } - if (updateEvent.returnValues.symbol == "KNC") { - expect(updateEvent.returnValues.price).toBe("1634700"); - } - if (updateEvent.returnValues.symbol == "LINK") { - expect(updateEvent.returnValues.price).toBe("4792460"); + switch(updateEvent.returnValues.price) { + case "BTC": + expect(updateEvent.returnValues.price).toBe("9100190000"); + break; + case "ETH": + expect(updateEvent.returnValues.price).toBe("226815000"); + break; + case "DAI": + expect(updateEvent.returnValues.price).toBe("1016313"); + break; + case "ZRX": + expect(updateEvent.returnValues.price).toBe("356479"); + break; + case "REP": + expect(updateEvent.returnValues.price).toBe("17275000"); + break; + case "BAT": + expect(updateEvent.returnValues.price).toBe("243858"); + break; + case "KNC": + expect(updateEvent.returnValues.price).toBe("1634700"); + break; + case "LINK": + expect(updateEvent.returnValues.price).toBe("4792460"); } }); + }); + + it("test anchor price events - AnchorPriceUpdated", async () => { + await sendRPC(web3, "evm_increaseTime", [31 * 60]); + + const observations = {}; + await Promise.all(Object.keys(pairs).map(async (key) => { + const newObservation = await call(uniswapAnchoredView, "newObservations", [pairs[key]._address]); + observations[key] = {acc: newObservation.acc, timestamp: newObservation.timestamp}; + })); + + const postRes = await send(uniswapAnchoredView, "postPrices", [ + messages, + signatures, + symbols, + ]); + + const anchorEvents = postRes.events.AnchorPriceUpdated; // Check anchor prices - anchorEvents.forEach((anchorEvent) => { - if (anchorEvent.returnValues.uniswapMarket == pairs.USDC_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("227415058"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.DAI_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("1019878"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.REP_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("17189956"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.BAT_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("242933"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.ETH_ZRX) { - expect(anchorEvent.returnValues.anchorPrice).toBe("359004"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.WBTC_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("9154767327"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.ETH_KNC) { - expect(anchorEvent.returnValues.anchorPrice).toBe("1661588"); - } - if (anchorEvent.returnValues.uniswapMarket == pairs.LINK_ETH) { - expect(anchorEvent.returnValues.anchorPrice).toBe("4820505"); + const block = await sendRPC(web3, "eth_getBlockByNumber", [ anchorEvents[0].blockNumber, false]); + const blockTimestamp = block.result.timestamp; + const cumulativePrice_eth = await getCumulativePrice(pairs.ETH, blockTimestamp, true); + // Recalculate anchor price in JS code and compare to the contract result + const ethPrice = calculateTWAP(cumulativePrice_eth, observations["ETH"].acc, blockTimestamp, observations["ETH"].timestamp).toFixed(); + await Promise.all(anchorEvents.map(async (anchorEvent) => { + switch(anchorEvent.returnValues.uniswapMarket) { + case pairs.ETH._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("227415058"); + expect(anchorEvent.returnValues.anchorPrice).toBe(ethPrice); + break; + case pairs.DAI._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("1019878"); + + // Recalculate anchor price in JS code and compare to the contract result + const cumulativePrice_dai = await getCumulativePrice(pairs.DAI, blockTimestamp); + const daiTWAP = calculateTWAP(cumulativePrice_dai, observations["DAI"].acc, blockTimestamp, observations["DAI"].timestamp); + const daiPrice = daiTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(daiPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.REP._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("17189956"); + + // Recalculate anchor price in JS code and compare to the contract result + const cumulativePrice_rep = await getCumulativePrice(pairs.REP, blockTimestamp); + const repTWAP = calculateTWAP(cumulativePrice_rep, observations["REP"].acc, blockTimestamp, observations["REP"].timestamp); + const repPrice = repTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(repPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.BAT._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("242933"); + + // Recalculate anchor price in JS code and compare to the contract result + const cumulativePrice_bat = await getCumulativePrice(pairs.BAT, blockTimestamp); + const batTWAP = calculateTWAP(cumulativePrice_bat, observations["BAT"].acc, blockTimestamp, observations["BAT"].timestamp); + const batPrice = batTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(batPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.ZRX._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("359004"); + + // Recalculate anchor price in JS code and compare to the contract result + cumulativePrice_zrx = await getCumulativePrice(pairs.ZRX, blockTimestamp, true); + const zrxTWAP = calculateTWAP(cumulativePrice_zrx, observations["ZRX"].acc, blockTimestamp, observations["ZRX"].timestamp); + const zrxPrice = zrxTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(zrxPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.BTC._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("9154767327"); + + // Recalculate anchor price in JS code and compare to the contract result + const cumulativePrice_btc = await getCumulativePrice(pairs.BTC, blockTimestamp); + const btcTWAP = calculateTWAP(cumulativePrice_btc, observations["BTC"].acc, blockTimestamp, observations["BTC"].timestamp); + const btcPrice = btcTWAP.multipliedBy(ethPrice).dividedBy(1e18).dividedBy(1e10).toFixed() + expect(btcPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.KNC._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("1661588"); + + // Recalculate anchor price in JS code and compare to the contract result + cumulativePrice_knc = await getCumulativePrice(pairs.KNC, blockTimestamp, true); + const kncTWAP = calculateTWAP(cumulativePrice_knc, observations["KNC"].acc, blockTimestamp, observations["KNC"].timestamp); + const kncPrice = kncTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(kncPrice).toBe(anchorEvent.returnValues.anchorPrice); + break; + case pairs.LINK._address: + expect(anchorEvent.returnValues.anchorPrice).toBe("4820505"); + + // Recalculate anchor price in JS code and compare to the contract result + const cumulativePrice_link = await getCumulativePrice(pairs.LINK, blockTimestamp); + const linkTWAP = calculateTWAP(cumulativePrice_link, observations["LINK"].acc, blockTimestamp, observations["LINK"].timestamp); + const linkPrice = linkTWAP.multipliedBy(ethPrice).dividedBy(1e18).toFixed() + expect(linkPrice).toBe(anchorEvent.returnValues.anchorPrice); } - }); + })); }); it("test uniswap window events", async () => { - const [uniswapAnchoredView, _] = await setupUniswapAnchoredView(); await sendRPC(web3, "evm_increaseTime", [31 * 60]); - const messages1 = [ - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000021e69e1300000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d84ec180000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe2000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000f81f90000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034441490000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000010798780000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035245500000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000005707f0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a52580000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000003b8920000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000018f18c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034b4e430000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005efebe9800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000049208c0000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044c494e4b00000000000000000000000000000000000000000000000000000000", - ]; - - const signatures1 = [ - "0xe64be3c6153c0f450062ebb3bb93b48d8e2bec11030dbe7b639ab6c78a5edf688f4fd9ba51c7589c15e384b952a2dc4943d402871163aca12197fd08568c4b9f000000000000000000000000000000000000000000000000000000000000001c", - "0xb8ba87c37228468f9d107a97eeb92ebd49a50993669cab1737fea77e5b884f2591affbf4058bcfa29e38756021deeafaeeab7a5c4f5ce584c7d1e12346c88d4e000000000000000000000000000000000000000000000000000000000000001b", - "0xac0731a325943a92f3745be5853f54ae110d889a408805e076ad9b5bc0bb1f4c1a994aebfb27a09156234fd1b27abf0fc19f667ea48c27a0c2a5d58c0243b99b000000000000000000000000000000000000000000000000000000000000001c", - "0x77058eaa98c77df280e069fb3c751b95aff57827a730458b48d773721432c42293f3b5d21b475169a59140e31196d333ca0f2b5935d2d86563df7a4e5cbc1e24000000000000000000000000000000000000000000000000000000000000001c", - "0x4758081209589c08db900e84d32393b3a37d9041a8e722e977dc3fff8affe0847d1dcb5c80496ea2f9314b3d6fcde03f8e7a896ba28dde6e2a003972da9e2b25000000000000000000000000000000000000000000000000000000000000001b", - "0x8f69f85dc792d657238a9a766b3cdb6c17c789d24a2b4c0d59eec079602aecfaf3d79d87a4ed430ef6889c46e03c82b5c616f9b968c4b757a89c1792a02dee13000000000000000000000000000000000000000000000000000000000000001b", - "0x25005922d67f6f446667de7e3052a2e97cf6b74bd01be62b478e16e3d72a3ecc5582fb44a3501fa359c2d6b5794844713c584740938e4012a1b0e3371e61a8a6000000000000000000000000000000000000000000000000000000000000001b", - "0xe393df120a0d95b8dea2ab693e4c89b4faf867c66636305bb1199e53cff95f43a22889e148a5bd85105173a102c3167f7716d4eca6a89a0120bef6479e812011000000000000000000000000000000000000000000000000000000000000001b", - ]; - const messages2 = [ "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005effbf7800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000021cd92f100000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005effbf7800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000d6e56d80000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000", @@ -354,20 +455,20 @@ describe("UniswapAnchoredView, postPrices test", () => { "0x039f30fb49b2f2badad1e3c5df00f2c5c2124c2a1bd06da56467aea45ebf89a027525cc7bfa776452171cb5865e74ef0c04ea6ef18d6ca2e556a0686af658803000000000000000000000000000000000000000000000000000000000000001b", ]; - // No data for COMP from Coinbase so far, it is not added to the oracle yet - const symbols = ["BTC", "ETH", "DAI", "REP", "ZRX", "BAT", "KNC", "LINK"]; - const postRes1 = await send(uniswapAnchoredView, "postPrices", [ - messages1, - signatures1, + messages, + signatures, symbols, ]); - const uniswapWindowEvents1 = postRes1.events.UniswapWindowUpdate; + const uniswapWindowEvents1 = postRes1.events.UniswapWindowUpdated; + const tolSeconds = 30; uniswapWindowEvents1.forEach((windowUpdate) => { - expect(windowUpdate.returnValues.newTimestamp).toBe( - windowUpdate.returnValues.oldTimestamp - ); + const elapsedTime = + windowUpdate.returnValues.newTimestamp - + windowUpdate.returnValues.oldTimestamp; + // but time difference should be around 31 minutes + 0/1 second + expect(elapsedTime).toBeWithinRange(31 * 60, 31 * 60 + tolSeconds); }); await sendRPC(web3, "evm_increaseTime", [31 * 60]); @@ -376,14 +477,64 @@ describe("UniswapAnchoredView, postPrices test", () => { signatures2, symbols, ]); - const uniswapWindowEvents2 = postRes2.events.UniswapWindowUpdate; + const uniswapWindowEvents2 = postRes2.events.UniswapWindowUpdated; uniswapWindowEvents2.forEach((windowUpdate) => { const elapsedTime = windowUpdate.returnValues.newTimestamp - windowUpdate.returnValues.oldTimestamp; - // Give an extra 5 seconds safety delay, but time difference should be around 31 minutes + 0/1 second - expect(elapsedTime >= 31 * 60 && elapsedTime < 31 * 60 + 5).toBe(true); + // Give an extra 30 seconds safety delay, but time difference should be around 31 minutes + 0/1 second + expect(elapsedTime).toBeWithinRange(31 * 60, 31 * 60 + tolSeconds); }); }); + + it("test ETH pair while token reserves change", async() => { + // Emulate timeElapsed for ETH token pair, so that timestamps are set up correctly + // 1594232101 - 1593755855 = 476246 + await sendRPC(web3, "evm_increaseTime", [476246]); + + // Update reserves, last block timestamp and cumulative prices for uniswap token pair + await send(pairs.ETH, "update", ["2699846518724", "10900804290754780075806", "1594232101", "130440674219479413955332918569393260852443923640848", "6394369143386285784459187027043"]); + const messages1 = ["0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005f060cac00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000eb20df00000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000"]; + const signatures1 = ["0x3b5dd2e97c072df44a576f1599a1a7beecef194596c0924c6f696f05c46e7494637041f819e7c89c897327f5932dddc3e4c811793bf5378bcd2289e3c2bd6210000000000000000000000000000000000000000000000000000000000000001b"]; + const symbols1 = ["ETH"]; + const postRes1 = await send(uniswapAnchoredView, "postPrices", [ + messages1, + signatures1, + symbols1, + ]); + const oldObservation1 = await call(uniswapAnchoredView, "oldObservations", [keccak256('ETH')]); + const anchorEvent1 = postRes1.events.AnchorPriceUpdated; + const block1 = await sendRPC(web3, "eth_getBlockByNumber", [anchorEvent1.blockNumber, false]); + const blockTimestamp1 = block1.result.timestamp; + + const cumulativePrice_eth1 = await getCumulativePrice(pairs.ETH, blockTimestamp1, true); + const ethPrice1 = calculateTWAP(cumulativePrice_eth1, oldObservation1.acc, blockTimestamp1, oldObservation1.timestamp).toFixed(); + expect(anchorEvent1.returnValues.symbol).toBe("ETH"); + expect(anchorEvent1.returnValues.anchorPrice).toBe(ethPrice1); + + // Emulate timeElapsed for ETH token pair, so that timestamps are set up correctly + // 1594232585 - 1594232101 = 484 + await sendRPC(web3, "evm_increaseTime", [484]); + // Update reserves, last block timestamp and cumulative prices for uniswap token pair + await send(pairs.ETH, "update", ["2699481954534", "10928542275748114013210", "1594232585", "130450824938813990811384244472088515000814627335952", "6394991319166063175850559023838"]); + const messages2 = ["0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000005f060e8c00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000eb2aa300000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034554480000000000000000000000000000000000000000000000000000000000"]; + const signatures2 = ["0xa9f78f3b7b3f35b124b186fc30a49418cde2baf40b01f7e710239a5e1c4c68bc0e1ae1abd93d3a79c20d4a742983fffd63ab5b239d36d77051ee265e36819920000000000000000000000000000000000000000000000000000000000000001b"]; + const symbols2 = ["ETH"]; + const postRes2 = await send(uniswapAnchoredView, "postPrices", [ + messages2, + signatures2, + symbols2, + ]); + const oldObservation2 = await call(uniswapAnchoredView, "oldObservations", [keccak256('ETH')]); + const anchorEvent2 = postRes2.events.AnchorPriceUpdated; + const block2 = await sendRPC(web3, "eth_getBlockByNumber", [anchorEvent2.blockNumber, false]); + const blockTimestamp2 = block2.result.timestamp; + const cumulativePrice_eth2 = await getCumulativePrice(pairs.ETH, blockTimestamp2, true); + const ethPrice2 = calculateTWAP(cumulativePrice_eth2, oldObservation2.acc, blockTimestamp2, oldObservation2.timestamp).toFixed(); + + expect(anchorEvent2.returnValues.symbol).toBe("ETH"); + expect(anchorEvent2.returnValues.anchorPrice).toBe(ethPrice2); + }); + }); diff --git a/tests/UniswapAnchoredViewTest.js b/tests/UniswapAnchoredViewTest.js index 43dc9ecc..65c46228 100644 --- a/tests/UniswapAnchoredViewTest.js +++ b/tests/UniswapAnchoredViewTest.js @@ -1,34 +1,56 @@ -const { encode, sign } = require('../sdk/javascript/.tsbuilt/reporter'); -const { uint, keccak256, time, numToHex, address } = require('./Helpers'); +const { encode, sign, encodeRotationMessage } = require('../sdk/javascript/.tsbuilt/reporter'); +const { uint, keccak256, time, numToHex, address, sendRPC, currentBlockTimestamp, fixed } = require('./Helpers'); const BigNumber = require('bignumber.js'); -async function setup(opts) { - ({isMockedView} = opts); - const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); +const PriceSource = { + FIXED_ETH: 0, + FIXED_USD: 1, + REPORTER: 2 +}; +const reporterPrivateKey = '0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'; +const FIXED_ETH_AMOUNT = 0.005e18; + +async function setup({isMockedView, freeze}) { + const reporter = + web3.eth.accounts.privateKeyToAccount(reporterPrivateKey); const anchorMantissa = numToHex(1e17); const priceData = await deploy('OpenOraclePriceData', []); const anchorPeriod = 60; + const timestamp = 1600000000; + + + if (freeze) { + await sendRPC(web3, 'evm_freezeTime', [timestamp]); + } else { + await sendRPC(web3, 'evm_mine', [timestamp]); + } - const FIXED_ETH_AMOUNT = 0.005e18; + const mockPair = await deploy("MockUniswapTokenPair", [ + fixed(1.8e12), + fixed(8.2e21), + fixed(1.6e9), + fixed(1.19e50), + fixed(5.8e30), + ]); - const dummyPair = await deploy("MockUniswapTokenPair", [ - "157323115357569242624896", - "10627310410144389510631", - "1592425041", - "819360504021542874838907395123146706291", - "221435982014902761159721791828816348231935", + // Initialize REP pair with values from mainnet + const mockRepPair = await deploy("MockUniswapTokenPair", [ + fixed(4e22), + fixed(3e21), + fixed(1.6e9), + fixed(1.32e39), + fixed(3.15e41), ]); - const priceSource = {FIXED_ETH: 0, FIXED_USD: 1, REPORTER: 2}; const cToken = {ETH: address(1), DAI: address(2), REP: address(3), USDT: address(4), SAI: address(5), WBTC: address(6)}; const dummyAddress = address(0); const tokenConfigs = [ - {cToken: cToken.ETH, underlying: dummyAddress, symbolHash: keccak256('ETH'), baseUnit: uint(1e18), priceSource: priceSource.REPORTER, fixedPrice: 0, uniswapMarket: dummyPair._address, isUniswapReversed: true}, - {cToken: cToken.DAI, underlying: dummyAddress, symbolHash: keccak256('DAI'), baseUnit: uint(1e18), priceSource: priceSource.REPORTER, fixedPrice: 0, uniswapMarket: dummyPair._address, isUniswapReversed: false}, - {cToken: cToken.REP, underlying: dummyAddress, symbolHash: keccak256('REP'), baseUnit: uint(1e18), priceSource: priceSource.REPORTER, fixedPrice: 0, uniswapMarket: dummyPair._address, isUniswapReversed: false}, - {cToken: cToken.USDT, underlying: dummyAddress, symbolHash: keccak256('USDT'), baseUnit: uint(1e6), priceSource: priceSource.FIXED_USD, fixedPrice: uint(1e6), uniswapMarket: address(0), isUniswapReversed: false}, - {cToken: cToken.SAI, underlying: dummyAddress, symbolHash: keccak256('SAI'), baseUnit: uint(1e18), priceSource: priceSource.FIXED_ETH, fixedPrice: uint(FIXED_ETH_AMOUNT), uniswapMarket: address(0), isUniswapReversed: false}, - {cToken: cToken.WBTC, underlying: dummyAddress, symbolHash: keccak256('WBTC'), baseUnit: uint(1e8), priceSource: priceSource.REPORTER, fixedPrice: 0, uniswapMarket: dummyPair._address, isUniswapReversed: false}, + {cToken: cToken.ETH, underlying: dummyAddress, symbolHash: keccak256('ETH'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: true}, + {cToken: cToken.DAI, underlying: dummyAddress, symbolHash: keccak256('DAI'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: false}, + {cToken: cToken.REP, underlying: dummyAddress, symbolHash: keccak256('REP'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockRepPair._address, isUniswapReversed: false}, + {cToken: cToken.USDT, underlying: dummyAddress, symbolHash: keccak256('USDT'), baseUnit: uint(1e6), priceSource: PriceSource.FIXED_USD, fixedPrice: uint(1e6), uniswapMarket: address(0), isUniswapReversed: false}, + {cToken: cToken.SAI, underlying: dummyAddress, symbolHash: keccak256('SAI'), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: uint(FIXED_ETH_AMOUNT), uniswapMarket: address(0), isUniswapReversed: false}, + {cToken: cToken.WBTC, underlying: dummyAddress, symbolHash: keccak256('BTC'), baseUnit: uint(1e8), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: false}, ]; let uniswapAnchoredView; @@ -38,45 +60,78 @@ async function setup(opts) { uniswapAnchoredView = await deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa, anchorPeriod, tokenConfigs]); } - async function postPrices(timestamp, prices2dArr, symbols, signer = reporter) { - const messages = [], - signatures = []; - - prices2dArr.forEach((prices, i) => { - const signed = sign( - encode( - 'prices', - timestamp, - prices - ), - signer.privateKey - ); - for (let { message, signature } of signed) { - messages.push(message); - signatures.push(signature); - } - }); - return send(uniswapAnchoredView, 'postPrices', [messages, signatures, symbols]); + async function postPrices(timestamp, prices2dArr, symbols, signer=reporter) { + let { + messages, + signatures + } = prices2dArr.reduce(({messages, signatures}, prices, i) => { + const signedMessages = sign( + encode( + 'prices', + timestamp, + prices + ), + signer.privateKey + ); + + return signedMessages.reduce(({messages, signatures}, {message, signature}) => { + return { + messages: [...messages, message], + signatures: [...signatures, signature], + }; + }, {messages, signatures}); + }, { messages: [], signatures: [] }); + + return send(uniswapAnchoredView, 'postPrices', [messages, signatures, symbols]); } - return {reporter, anchorMantissa, priceData, anchorPeriod, uniswapAnchoredView, tokenConfigs, postPrices, cToken}; + + return { + anchorMantissa, + anchorPeriod, + cToken, + mockPair, + postPrices, + priceData, + reporter, + timestamp, + tokenConfigs, + uniswapAnchoredView, + }; } describe('UniswapAnchoredView', () => { - let cToken, reporter, anchorMantissa, priceData, anchorPeriod, uniswapAnchoredView, tokenConfigs, postPrices; + let cToken; + let reporter; + let anchorMantissa; + let priceData; + let anchorPeriod; + let uniswapAnchoredView; + let tokenConfigs; + let postPrices; + let mockPair; + let timestamp; - describe('postPrices Unit Test', () => { - beforeEach(async done => { - ({reporter, anchorMantissa, priceData, uniswapAnchoredView, postPrices} = await setup({isMockedView: true})); - done(); - }) + describe('postPrices', () => { + beforeEach(async () => { + ({ + anchorMantissa, + postPrices, + priceData, + reporter, + uniswapAnchoredView, + } = await setup({isMockedView: true})); + }); it('should not update view if sender is not reporter', async () => { const timestamp = time() - 5; const nonSource = web3.eth.accounts.privateKeyToAccount('0x666ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); - const tx = await postPrices(timestamp, [[['ETH', 91]]], ['ETH'], nonSource); + await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 91e6]); + await postPrices(timestamp, [[['ETH', 91]]], ['ETH'], reporter); + + const tx = await postPrices(timestamp, [[['ETH', 95]]], ['ETH'], nonSource); expect(tx.events.PriceGuarded).toBe(undefined); expect(tx.events.PricePosted).toBe(undefined); - expect(await call(uniswapAnchoredView, 'prices', [keccak256('ETH')])).numEquals(0); + expect(await call(uniswapAnchoredView, 'prices', [keccak256('ETH')])).numEquals(91e6); }); it('should update view if ETH price is within anchor bounds', async () => { @@ -85,48 +140,142 @@ describe('UniswapAnchoredView', () => { const tx = await postPrices(timestamp, [[['ETH', 91]]], ['ETH']); expect(tx.events.PriceGuarded).toBe(undefined); - expect(tx.events.PriceUpdated).not.toBe(undefined); + expect(tx.events.PriceUpdated.returnValues.price).numEquals(91e6); + expect(tx.events.PriceUpdated.returnValues.symbol).toBe('ETH'); expect(await call(uniswapAnchoredView, 'prices', [keccak256('ETH')])).numEquals(91e6); expect(await call(priceData, 'getPrice', [reporter.address, 'ETH'])).numEquals(91e6); }); + it('should update view if ERC20 price is within anchor bounds', async () => { + const timestamp = time() - 5; + await send(uniswapAnchoredView, 'setAnchorPrice', ['REP', 17e6]); + const tx = await postPrices(timestamp, [[['REP', 17]]], ['REP']); + + expect(tx.events.PriceGuarded).toBe(undefined); + expect(tx.events.PriceUpdated.returnValues.price).numEquals(17e6); + expect(tx.events.PriceUpdated.returnValues.symbol).toBe('REP'); + expect(await call(uniswapAnchoredView, 'prices', [keccak256('REP')])).numEquals(17e6); + expect(await call(priceData, 'getPrice', [reporter.address, 'REP'])).numEquals(17e6); + }); + it('should not update view if ETH price is below anchor bounds', async () => { // anchorMantissa is 1e17, so 10% tolerance const timestamp = time() - 5; await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 89.9e6]); const tx = await postPrices(timestamp, [[['ETH', 100]]], ['ETH']); - expect(tx.events.PriceGuarded).not.toBe(undefined); + expect(tx.events.PriceGuarded.returnValues.symbol).toBe('ETH'); + expect(tx.events.PriceGuarded.returnValues.reporter).numEquals(100e6); + expect(tx.events.PriceGuarded.returnValues.anchor).numEquals(89.9e6); expect(tx.events.PriceUpdated).toBe(undefined); expect(await call(uniswapAnchoredView, 'prices', [keccak256('ETH')])).numEquals(0); expect(await call(priceData, 'getPrice', [reporter.address, 'ETH'])).numEquals(100e6); }); + it('should not update view if ERC20 price is below anchor bounds', async () => { + const timestamp = time() - 5; + // anchorMantissa is 1e17, so 10% tolerance + await send(uniswapAnchoredView, 'setAnchorPrice', ['REP', 15e6]); + const tx = await postPrices(timestamp, [[['REP', 17]]], ['REP']); + + expect(tx.events.PriceGuarded.returnValues.reporter).numEquals(17e6); + expect(tx.events.PriceGuarded.returnValues.anchor).numEquals(15e6); + expect(await call(uniswapAnchoredView, 'prices', [keccak256('REP')])).numEquals(0); + expect(await call(priceData, 'getPrice', [reporter.address, 'REP'])).numEquals(17e6); + }); + it('should not update view if ETH price is above anchor bounds', async () => { // anchorMantissa is 1e17, so 10% tolerance const timestamp = time() - 5; await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 110.1e6]); const tx = await postPrices(timestamp, [[['ETH', 100]]], ['ETH']); - expect(tx.events.PriceGuarded).not.toBe(undefined); + expect(tx.events.PriceGuarded.returnValues.reporter).numEquals(100e6); + expect(tx.events.PriceGuarded.returnValues.anchor).numEquals(110.1e6); expect(tx.events.PriceUpdated).toBe(undefined); expect(await call(uniswapAnchoredView, 'prices', [keccak256('ETH')])).numEquals(0); expect(await call(priceData, 'getPrice', [reporter.address, 'ETH'])).numEquals(100e6); }); - it.todo('test anchor with non-eth prices') + it('should not update view if ERC20 price is above anchor bounds', async () => { + const timestamp = time() - 5; + // anchorMantissa is 1e17, so 10% tolerance + await send(uniswapAnchoredView, 'setAnchorPrice', ['REP', 19e6]); + const tx = await postPrices(timestamp, [[['REP', 17]]], ['REP']); - it.todo('should invalidate reporter'); + expect(tx.events.PriceGuarded.returnValues.reporter).numEquals(17e6); + expect(tx.events.PriceGuarded.returnValues.anchor).numEquals(19e6); + expect(await call(uniswapAnchoredView, 'prices', [keccak256('REP')])).numEquals(0); + expect(await call(priceData, 'getPrice', [reporter.address, 'REP'])).numEquals(17e6); + }); - }); + it('should revert on posting arrays of messages and signatures with different lengths', async () => { + await expect( + send(uniswapAnchoredView, 'postPrices', [['0xabc'], ['0x123', '0x123'], []]) + ).rejects.toRevert("revert messages and signatures must be 1:1"); + + await expect( + send(uniswapAnchoredView, 'postPrices', [['0xabc', '0xabc'], ['0x123'], []]) + ).rejects.toRevert("revert messages and signatures must be 1:1"); + }); + + it('should revert on posting arrays with invalid symbols', async () => { + const timestamp = time() - 5; + await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 91e6]); + + await expect( + postPrices(timestamp, [[['ETH', 91]]], ['HOHO']) + ).rejects.toRevert("revert token config not found"); + + await expect( + postPrices(timestamp, [[['HOHO', 91]]], ['HOHO']) + ).rejects.toRevert("revert token config not found"); + + await expect( + postPrices(timestamp, [[['ETH', 91], ['WBTC', 1000]]], ['ETH', 'HOHO']) + ).rejects.toRevert("revert token config not found"); + }); + + it('should revert on posting arrays with invalid symbols', async () => { + const timestamp = time() - 5; + await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 91e6]); + + await expect( + postPrices(timestamp, [[['ETH', 91]]], ['HOHO']) + ).rejects.toRevert("revert token config not found"); + + await expect( + postPrices(timestamp, [[['HOHO', 91]]], ['HOHO']) + ).rejects.toRevert("revert token config not found"); + + await expect( + postPrices(timestamp, [[['ETH', 91], ['WBTC', 1000]]], ['ETH', 'HOHO']) + ).rejects.toRevert("revert token config not found"); + }); + + it("should revert on posting FIXED_USD prices", async () => { + await expect( + postPrices(time() - 5, [[['USDT', 1]]], ['USDT']) + ).rejects.toRevert("revert only reporter prices get posted"); + }); + + it("should revert on posting FIXED_ETH prices", async () => { + await expect( + postPrices(time() - 5, [[['SAI', 1]]], ['SAI']) + ).rejects.toRevert("revert only reporter prices get posted"); + }); +}); describe('getUnderlyingPrice', () => { // everything must return 1e36 - underlying units - beforeEach(async done => { - ({cToken, uniswapAnchoredView, postPrices} = await setup({isMockedView: true})); - done(); - }) + beforeEach(async () => { + ({ + cToken, + postPrices, + uniswapAnchoredView, + } = await setup({isMockedView: true})); + }); it('should work correctly for USDT fixed USD price source', async () => { // 1 * (1e(36 - 6)) = 1e30 @@ -155,12 +304,12 @@ describe('UniswapAnchoredView', () => { it('should return reported WBTC price', async () => { const timestamp = time() - 5; await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 200e6]); - await send(uniswapAnchoredView, 'setAnchorPrice', ['WBTC', 10000e6]); + await send(uniswapAnchoredView, 'setAnchorPrice', ['BTC', 10000e6]); - const tx = await postPrices(timestamp, [[['ETH', 200], ['WBTC', 10000]]], ['ETH', 'WBTC']); - const wbtcPrice = await call(uniswapAnchoredView, 'prices', [keccak256('WBTC')]); + const tx = await postPrices(timestamp, [[['ETH', 200], ['BTC', 10000]]], ['ETH', 'BTC']); + const btcPrice = await call(uniswapAnchoredView, 'prices', [keccak256('BTC')]); - expect(wbtcPrice).numEquals(10000e6); + expect(btcPrice).numEquals(10000e6); // priceInternal: returns 10000e6 // getUnderlyingPrice: 1e30 * 10000e6 / 1e8 = 1e32 let expected = new BigNumber('1e32'); @@ -168,4 +317,242 @@ describe('UniswapAnchoredView', () => { }); }); + + describe('pokeWindowValues', () => { + beforeEach(async () => { + ({ + mockPair, + anchorPeriod, + uniswapAnchoredView, + postPrices, + tokenConfigs, + timestamp + } = await setup({isMockedView: false, freeze: true})); + }); + + it('should not update window values if not enough time elapsed', async () => { + await sendRPC(web3, 'evm_freezeTime', [timestamp + anchorPeriod - 5]); + const tx = await postPrices(timestamp, [[['ETH', 227]]], ['ETH']); + expect(tx.events.UniswapWindowUpdated).toBe(undefined); + }); + + it('should update window values if enough time elapsed', async () => { + const ethHash = keccak256('ETH'); + const mkt = mockPair._address;// ETH's mock market + const newObs1 = await call(uniswapAnchoredView, 'newObservations', [ethHash]); + const oldObs1 = await call(uniswapAnchoredView, 'oldObservations', [ethHash]); + + let timestampLater = timestamp + anchorPeriod; + await sendRPC(web3, 'evm_freezeTime', [timestampLater]); + + const tx1 = await postPrices(timestampLater, [[['ETH', 227]]], ['ETH']); + const updateEvent = tx1.events.AnchorPriceUpdated.returnValues; + expect(updateEvent.newTimestamp).greaterThan(updateEvent.oldTimestamp); + expect(tx1.events.PriceGuarded).toBe(undefined); + + // on the first update, we expect the new observation to change + const newObs2 = await call(uniswapAnchoredView, 'newObservations', [ethHash]); + const oldObs2 = await call(uniswapAnchoredView, 'oldObservations', [ethHash]); + expect(newObs2.acc).greaterThan(newObs1.acc); + expect(newObs2.timestamp).greaterThan(newObs1.timestamp); + expect(oldObs2.acc).numEquals(oldObs1.acc); + expect(oldObs2.timestamp).numEquals(oldObs1.timestamp); + + let timestampEvenLater = timestampLater + anchorPeriod; + await sendRPC(web3, 'evm_freezeTime', [timestampEvenLater]); + const tx2 = await postPrices(timestampEvenLater, [[['ETH', 201]]], ['ETH']); + + const windowUpdate = tx2.events.UniswapWindowUpdated.returnValues; + expect(windowUpdate.symbolHash).toEqual(ethHash); + expect(timestampEvenLater).greaterThan(windowUpdate.oldTimestamp); + expect(windowUpdate.newPrice).greaterThan(windowUpdate.oldPrice);// accumulator should always go up + + // this time, both should change + const newObs3 = await call(uniswapAnchoredView, 'newObservations', [ethHash]); + const oldObs3 = await call(uniswapAnchoredView, 'oldObservations', [ethHash]); + expect(newObs3.acc).greaterThan(newObs2.acc); + expect(newObs3.acc).greaterThan(newObs2.timestamp); + // old becomes last new + expect(oldObs3.acc).numEquals(newObs2.acc); + expect(oldObs3.timestamp).numEquals(newObs2.timestamp); + + const anchorPriceUpdated = tx2.events.AnchorPriceUpdated.returnValues; + expect(anchorPriceUpdated.symbol).toBe("ETH"); + expect(anchorPriceUpdated.newTimestamp).greaterThan(anchorPriceUpdated.oldTimestamp); + expect(oldObs3.timestamp).toBe(anchorPriceUpdated.oldTimestamp); + }); + }) + + describe('constructor', () => { + + it('should prevent bounds from under/overflow', async () => { + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + const priceData = await deploy('OpenOraclePriceData', []); + const anchorPeriod = 30, configs = []; + const UINT256_MAX = (1n<<256n) - 1n, exp = (a, b) => BigInt(a) * 10n**BigInt(b); + + const anchorMantissa1 = exp(100, 16); + const view1 = await deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa1, anchorPeriod, configs]); + expect(await call(view1, 'upperBoundAnchorRatio')).numEquals(2e18); + expect(await call(view1, 'lowerBoundAnchorRatio')).numEquals(1); + + const anchorMantissa2 = UINT256_MAX - exp(99, 16); + const view2 = await deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa2, anchorPeriod, configs]); + expect(await call(view2, 'upperBoundAnchorRatio')).numEquals(UINT256_MAX.toString()); + expect(await call(view2, 'lowerBoundAnchorRatio')).numEquals(1); + }); + + it('should fail if baseUnit == 0', async () => { + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + const priceData = await deploy('OpenOraclePriceData', []); + const anchorMantissa = numToHex(1e17); + + const dummyAddress = address(0); + const mockPair = await deploy("MockUniswapTokenPair", [ + fixed(1.8e12), + fixed(8.2e21), + fixed(1.6e9), + fixed(1.19e50), + fixed(5.8e30), + ]); + const tokenConfigs = [ + // Set dummy address as a uniswap market address + {cToken: address(1), underlying: dummyAddress, symbolHash: keccak256('ETH'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: true}, + {cToken: address(2), underlying: dummyAddress, symbolHash: keccak256('DAI'), baseUnit: 0, priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: false}, + {cToken: address(3), underlying: dummyAddress, symbolHash: keccak256('REP'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: mockPair._address, isUniswapReversed: false}]; + await expect( + deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa, 30, tokenConfigs]) + ).rejects.toRevert("revert baseUnit must be greater than zero"); + }); + + it('should fail if uniswap market is not defined', async () => { + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + const priceData = await deploy('OpenOraclePriceData', []); + const anchorMantissa = numToHex(1e17); + + const dummyAddress = address(0); + const tokenConfigs = [ + // Set dummy address as a uniswap market address + {cToken: address(1), underlying: dummyAddress, symbolHash: keccak256('ETH'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: dummyAddress, isUniswapReversed: true}, + {cToken: address(2), underlying: dummyAddress, symbolHash: keccak256('DAI'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: address(4), isUniswapReversed: false}, + {cToken: address(3), underlying: dummyAddress, symbolHash: keccak256('REP'), baseUnit: uint(1e18), priceSource: PriceSource.REPORTER, fixedPrice: 0, uniswapMarket: address(5), isUniswapReversed: false}]; + await expect( + deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa, 30, tokenConfigs]) + ).rejects.toRevert("revert reported prices must have an anchor"); + }); + + it('should fail if non-reporter price utilizes an anchor', async () => { + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + const priceData = await deploy('OpenOraclePriceData', []); + const anchorMantissa = numToHex(1e17); + + const dummyAddress = address(0); + const tokenConfigs1 = [ + {cToken: address(2), underlying: dummyAddress, symbolHash: keccak256('USDT'), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_USD, fixedPrice: 0, uniswapMarket: address(5), isUniswapReversed: false}]; + await expect( + deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa, 30, tokenConfigs1]) + ).rejects.toRevert("revert only reported prices utilize an anchor"); + + const tokenConfigs2 = [ + {cToken: address(2), underlying: dummyAddress, symbolHash: keccak256('USDT'), baseUnit: uint(1e18), priceSource: PriceSource.FIXED_ETH, fixedPrice: 0, uniswapMarket: address(5), isUniswapReversed: false}]; + await expect( + deploy('UniswapAnchoredView', [priceData._address, reporter.address, anchorMantissa, 30, tokenConfigs2]) + ).rejects.toRevert("revert only reported prices utilize an anchor"); + }); + + it('basic scenario, successfully initialize observations initial state', async () => { + ({reporter, anchorMantissa, priceData, anchorPeriod, uniswapAnchoredView, tokenConfigs, postPrices, cToken, mockPair} = await setup({isMockedView: true})); + expect(await call(uniswapAnchoredView, 'reporter')).toBe(reporter.address); + expect(await call(uniswapAnchoredView, 'anchorPeriod')).numEquals(anchorPeriod); + expect(await call(uniswapAnchoredView, 'upperBoundAnchorRatio')).numEquals(new BigNumber(anchorMantissa).plus(1e18)); + expect(await call(uniswapAnchoredView, 'lowerBoundAnchorRatio')).numEquals(new BigNumber(1e18).minus(anchorMantissa)); + + await Promise.all(tokenConfigs.map(async config => { + const oldObservation = await call(uniswapAnchoredView, 'oldObservations', [config.uniswapMarket]); + const newObservation = await call(uniswapAnchoredView, 'newObservations', [config.uniswapMarket]); + expect(oldObservation.timestamp).numEquals(newObservation.timestamp); + expect(oldObservation.acc).numEquals(newObservation.acc); + if (config.priceSource != PriceSource.REPORTER) { + expect(oldObservation.acc).numEquals(0); + expect(newObservation.acc).numEquals(0); + expect(oldObservation.timestamp).numEquals(0); + expect(newObservation.timestamp).numEquals(0); + } + })) + }); + }) + + describe('invalidateReporter', () => { + + beforeEach(async done => { + ({uniswapAnchoredView, postPrices} = await setup({isMockedView: true})); + done(); + }) + + it("reverts if given wrong message", async () => { + const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + let encoded = web3.eth.abi.encodeParameters(['string', 'address'], ['stay still', rotationTarget]); + const [ signed ] = sign(encoded, reporter.privateKey); + + await expect( + send(uniswapAnchoredView, 'invalidateReporter', [encoded, signed.signature]) + ).rejects.toRevert("revert invalid message must be 'rotate'"); + }); + + it("reverts if given wrong signature", async () => { + const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; + let encoded = encodeRotationMessage(rotationTarget); + // sign rotation message with wrong key + const [ signed ] = sign(encoded, '0x666ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + + await expect( + send(uniswapAnchoredView, 'invalidateReporter', [encoded, signed.signature]) + ).rejects.toRevert("revert invalidation message must come from the reporter"); + }); + + it("basic scenario, sets reporterInvalidated and emits ReporterInvalidated event", async () => { + const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + let encoded = web3.eth.abi.encodeParameters(['string', 'address'], ['rotate', rotationTarget]); + const [ signed ] = sign(encoded, reporter.privateKey); + + // Check that reporterInvalidated variable is properly set + expect(await call(uniswapAnchoredView, 'reporterInvalidated')).toBe(false); + const tx = await send(uniswapAnchoredView, 'invalidateReporter', [encoded, signed.signature]); + expect(await call(uniswapAnchoredView, 'reporterInvalidated')).toBe(true); + + // Check that event is emitted + expect(tx.events.ReporterInvalidated).not.toBe(undefined); + expect(tx.events.ReporterInvalidated.returnValues.reporter).toBe(reporter.address); + }); + + it("basic scenario, return anchor price after reporter is invalidated", async () => { + const timestamp = time() - 5; + await send(uniswapAnchoredView, 'setAnchorPrice', ['ETH', 200e6]); + await send(uniswapAnchoredView, 'setAnchorPrice', ['BTC', 10000e6]); + + await postPrices(timestamp, [[['ETH', 201], ['BTC', 10001]]], ['ETH', 'BTC']); + + // Check that prices = posted prices + const wbtcPrice1 = await call(uniswapAnchoredView, 'prices', [keccak256('BTC')]); + const ethPrice1 = await call(uniswapAnchoredView, 'prices', [keccak256('ETH')]); + expect(wbtcPrice1).numEquals(10001e6); + expect(ethPrice1).numEquals(201e6); + + const rotationTarget = '0xAbcdef0123456789000000000000000000000005'; + const reporter = web3.eth.accounts.privateKeyToAccount('0x177ee777e72b8c042e05ef41d1db0f17f1fcb0e8150b37cfad6993e4373bdf10'); + let encoded = web3.eth.abi.encodeParameters(['string', 'address'], ['rotate', rotationTarget]); + const [ signed ] = sign(encoded, reporter.privateKey); + + await send(uniswapAnchoredView, 'invalidateReporter', [encoded, signed.signature]); + await postPrices(timestamp, [[['ETH', 201], ['BTC', 10001]]], ['ETH', 'BTC']); + + // Check that prices = anchor prices + const wbtcPrice2 = await call(uniswapAnchoredView, 'prices', [keccak256('BTC')]); + const ethPrice2 = await call(uniswapAnchoredView, 'prices', [keccak256('ETH')]); + expect(wbtcPrice2).numEquals(10000e6); + expect(ethPrice2).numEquals(200e6); + }); + }) }); diff --git a/tests/UniswapConfigTest.js b/tests/UniswapConfigTest.js index d51834ba..d7a4c4f7 100644 --- a/tests/UniswapConfigTest.js +++ b/tests/UniswapConfigTest.js @@ -12,26 +12,33 @@ function uint(n) { describe('UniswapConfig', () => { it('basically works', async () => { + const unlistedButUnderlying = await deploy('MockCToken', [address(4)]) + const unlistedNorUnderlying = await deploy('MockCToken', [address(5)]) const contract = await deploy('UniswapConfig', [[ {cToken: address(1), underlying: address(0), symbolHash: keccak256('ETH'), baseUnit: uint(1e18), priceSource: 0, fixedPrice: 0, uniswapMarket: address(6), isUniswapReversed: false}, - {cToken: address(2), underlying: address(3), symbolHash: keccak256('BTC'), baseUnit: uint(1e18), priceSource: 1, fixedPrice: 1, uniswapMarket: address(7), isUniswapReversed: true} + {cToken: address(2), underlying: address(3), symbolHash: keccak256('BTC'), baseUnit: uint(1e18), priceSource: 1, fixedPrice: 1, uniswapMarket: address(7), isUniswapReversed: true}, + {cToken: unlistedButUnderlying._address, underlying: address(4), symbolHash: keccak256('REP'), baseUnit: uint(1e18), priceSource: 1, fixedPrice: 1, uniswapMarket: address(7), isUniswapReversed: true} ]]); const cfg0 = await call(contract, 'getTokenConfig', [0]); const cfg1 = await call(contract, 'getTokenConfig', [1]); + const cfg2 = await call(contract, 'getTokenConfig', [2]); const cfgETH = await call(contract, 'getTokenConfigBySymbol', ['ETH']); const cfgBTC = await call(contract, 'getTokenConfigBySymbol', ['BTC']); const cfgCT0 = await call(contract, 'getTokenConfigByCToken', [address(1)]); const cfgCT1 = await call(contract, 'getTokenConfigByCToken', [address(2)]); + const cfgU2 = await call(contract, 'getTokenConfigByCToken', [unlistedButUnderlying._address]) expect(cfg0).toEqual(cfgETH); expect(cfgETH).toEqual(cfgCT0); expect(cfg1).toEqual(cfgBTC); expect(cfgBTC).toEqual(cfgCT1); expect(cfg0).not.toEqual(cfg1); + expect(cfgU2).toEqual(cfg2); - await expect(call(contract, 'getTokenConfig', [2])).rejects.toRevert('revert token config not found'); + await expect(call(contract, 'getTokenConfig', [3])).rejects.toRevert('revert token config not found'); await expect(call(contract, 'getTokenConfigBySymbol', ['COMP'])).rejects.toRevert('revert token config not found'); await expect(call(contract, 'getTokenConfigByCToken', [address(3)])).rejects.toRevert('revert'); // not a ctoken + await expect(call(contract, 'getTokenConfigByCToken', [unlistedNorUnderlying._address])).rejects.toRevert('revert token config not found'); }); it('returns configs exactly as specified', async () => { @@ -45,6 +52,7 @@ describe('UniswapConfig', () => { const cfgByIndex = await call(contract, 'getTokenConfig', [i]); const cfgBySymbol = await call(contract, 'getTokenConfigBySymbol', [symbols[i]]); const cfgByCToken = await call(contract, 'getTokenConfigByCToken', [address(i + 1)]); + const cfgByUnderlying = await call(contract, 'getTokenConfigByUnderlying', [address(i)]); expect({ cToken: cfgByIndex.cToken.toLowerCase(), underlying: cfgByIndex.underlying.toLowerCase(), @@ -66,34 +74,51 @@ describe('UniswapConfig', () => { }); expect(cfgByIndex).toEqual(cfgBySymbol); expect(cfgBySymbol).toEqual(cfgByCToken); + expect(cfgByUnderlying).toEqual(cfgBySymbol); })); }); it('checks gas', async () => { const configs = Array(26).fill(0).map((_, i) => { const symbol = String.fromCharCode('a'.charCodeAt(0) + i); - return {cToken: address(i + 1), underlying: address(i), symbolHash: keccak256(symbol), baseUnit: uint(1e6), priceSource: 0, fixedPrice: 1, uniswapMarket: address(i + 50), isUniswapReversed: i % 2 == 0} + return { + cToken: address(i), + underlying: address(i + 1), + symbolHash: keccak256(symbol), + baseUnit: uint(1e6), + priceSource: 0, + fixedPrice: 1, + uniswapMarket: address(i + 50), + isUniswapReversed: i % 2 == 0} }); const contract = await deploy('UniswapConfig', [configs]); const cfg9 = await call(contract, 'getTokenConfig', [9]); const tx9 = await send(contract, 'getTokenConfig', [9]); - expect(cfg9.underlying).toEqual(address(9)); + expect(cfg9.underlying).addrEquals(address(10)); expect(tx9.gasUsed).toEqual(22619); const cfg25 = await call(contract, 'getTokenConfig', [25]); const tx25 = await send(contract, 'getTokenConfig', [25]); - expect(cfg25.underlying).toEqual(address(25)); + expect(cfg25.underlying).addrEquals(address(26)); expect(tx25.gasUsed).toEqual(23035); const cfgZ = await call(contract, 'getTokenConfigBySymbol', ['z']); const txZ = await send(contract, 'getTokenConfigBySymbol', ['z']); - expect(cfgZ.underlying).toEqual(address(25)); + expect(cfgZ.cToken).addrEquals(address(25)); + expect(cfgZ.underlying).addrEquals(address(26)); expect(txZ.gasUsed).toEqual(25273); - const cfgCT26 = await call(contract, 'getTokenConfigByCToken', [address(26)]); - const txCT26 = await send(contract, 'getTokenConfigByCToken', [address(26)]); - expect(cfgCT26.underlying).toEqual(address(25)); + const cfgCT26 = await call(contract, 'getTokenConfigByCToken', [address(25)]); + const txCT26 = await send(contract, 'getTokenConfigByCToken', [address(25)]); + expect(cfgCT26.cToken).addrEquals(address(25)); + expect(cfgCT26.underlying).addrEquals(address(26)); expect(txCT26.gasUsed).toEqual(25136); + + const cfgU26 = await call(contract, 'getTokenConfigByUnderlying', [address(26)]); + const txU26 = await send(contract, 'getTokenConfigByUnderlying', [address(26)]); + expect(cfgU26.cToken).addrEquals(address(25)); + expect(cfgU26.underlying).addrEquals(address(26)); + expect(txU26.gasUsed).toEqual(25137); }); }); \ No newline at end of file diff --git a/tests/contracts/MockUniswapAnchoredView.sol b/tests/contracts/MockUniswapAnchoredView.sol index 43d4127c..5530fef6 100644 --- a/tests/contracts/MockUniswapAnchoredView.sol +++ b/tests/contracts/MockUniswapAnchoredView.sol @@ -18,7 +18,8 @@ contract MockUniswapAnchoredView is UniswapAnchoredView { anchorPrices[keccak256(abi.encodePacked(symbol))] = price; } - function fetchAnchorPrice(TokenConfig memory config, uint _conversionFactor) internal override returns (uint) { + function fetchAnchorPrice(string memory _symbol, TokenConfig memory config, uint _conversionFactor) internal override returns (uint) { + _symbol; // Shh _conversionFactor; // Shh return anchorPrices[config.symbolHash]; } diff --git a/tests/contracts/MockUniswapTokenPair.sol b/tests/contracts/MockUniswapTokenPair.sol index e66fc094..b8af1e4f 100644 --- a/tests/contracts/MockUniswapTokenPair.sol +++ b/tests/contracts/MockUniswapTokenPair.sol @@ -41,4 +41,14 @@ contract MockUniswapTokenPair { function getReserves() external view returns(uint112, uint112, uint32) { return (reserve0, reserve1, blockTimestampLast); } + + function getReservesFraction(bool reversedMarket) external view returns (uint224) { + require(reserve0 > 0, "Reserve is equal to 0"); + require(reserve1 > 0, "Reserve is equal to 0"); + if (reversedMarket) { + return (uint224(reserve0) << 112) / reserve1; + } else { + return (uint224(reserve1) << 112) / reserve0; + } + } } diff --git a/yarn.lock b/yarn.lock index 849170c5..834ba9fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,62 +2,62 @@ # yarn lockfile v1 -"@0x/assert@^3.0.7": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-3.0.7.tgz#fb616533ed00480bd642f8419d28e88b547c30df" - integrity sha512-HYdVvIgj/wVb20MVveazLQwICxZGelNuyu/U09ZSMzRy1NrDgrBiMjrU4WrcpW2GTPZjl+7R4U4/7h/C8I/egQ== - dependencies: - "@0x/json-schemas" "^5.0.7" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" +"@0x/assert@^3.0.9": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-3.0.9.tgz#4bc750b786c1f02ea0b7ceb701ebe2e46d30c113" + integrity sha512-ywQuG8feXtIpRn/3tekPZeXLjKfDJPooA5QJEDxHDv1E5ZjSrKSK6B3wdkVW6NJvNPECw0eKjM50uF2784tbfA== + dependencies: + "@0x/json-schemas" "^5.1.0" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" lodash "^4.17.11" valid-url "^1.0.9" -"@0x/dev-utils@^3.0.0", "@0x/dev-utils@^3.2.1": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-3.2.1.tgz#778b89a6ce47af143b526934cab0518f083eb831" - integrity sha512-+7YVtXTMkEGgnf7HYEO9uqUmgowP+NaIOaNzzzooTJo6SrnwF1EBc0evBQeS/NBzyFoEskLs2w/jcGYP92J0ag== - dependencies: - "@0x/subproviders" "^6.0.8" - "@0x/types" "^3.1.2" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" - "@0x/web3-wrapper" "^7.0.7" +"@0x/dev-utils@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-3.3.0.tgz#ff56021d13b9630bb7f42d102e30a18f2b9aa7ff" + integrity sha512-/SPF/dVrGeYgZMo4vhHjG/YPxEsbnSi3sKA3+R6hySF7AKOH0u+78cUZ1NwrVOhGHToNWmehhci1Ic2BOXxQ9g== + dependencies: + "@0x/subproviders" "^6.1.1" + "@0x/types" "^3.2.0" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" + "@0x/web3-wrapper" "^7.2.0" "@types/web3-provider-engine" "^14.0.0" chai "^4.0.1" chai-as-promised "^7.1.0" chai-bignumber "^3.0.0" dirty-chai "^2.0.1" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" lodash "^4.17.11" web3-provider-engine "14.0.6" -"@0x/json-schemas@^5.0.7": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-5.0.7.tgz#4e68074b7ab06984f680df742de825b678a253d3" - integrity sha512-/7gbLno+qQRNjLWRkulbPVOnRoP7uN5CnNP+VKydpKArBgU/E38rUVzSl/Y6FaPbhwC/Rs0d0BmP2Y5DMozjyA== +"@0x/json-schemas@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-5.1.0.tgz#3f30529049ea5aa4cfc4aed2c2124c7482d632f1" + integrity sha512-qDiCz94AR140puQ0MHT6XyDbmDrvWjw+Zyysmf4tD9PfM8sD+MOhF9dfvaYPNlS51M1CIlOTWZYqo5OUCIBEXQ== dependencies: - "@0x/typescript-typings" "^5.0.2" + "@0x/typescript-typings" "^5.1.1" "@types/node" "*" jsonschema "^1.2.0" lodash.values "^4.3.0" -"@0x/sol-compiler@^4.0.0", "@0x/sol-compiler@^4.0.8": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-4.0.8.tgz#e60634423d47c4241a404e48edbdfff99098ab5f" - integrity sha512-azwMQbPDcMmp81j/wHtZj9paaoq+Eo8SWkouVbygwUDxB+FSaBN7N+IURRzhKuTpKxY2w/vh2FBrJUE9ii6wog== - dependencies: - "@0x/assert" "^3.0.7" - "@0x/json-schemas" "^5.0.7" - "@0x/sol-resolver" "^3.0.3" - "@0x/types" "^3.1.2" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" - "@0x/web3-wrapper" "^7.0.7" +"@0x/sol-compiler@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-4.1.1.tgz#dfab22e2370c03ef8dcfd910d66ced0d46ef5579" + integrity sha512-ktcTBz1m0cRn34t1ZkCn1BzssgLEI3ZLB6+aLq1OZzb3hGha9RW/yzl8UC7K/G/GPAK0rb3ip4t3TYHzIH/3lg== + dependencies: + "@0x/assert" "^3.0.9" + "@0x/json-schemas" "^5.1.0" + "@0x/sol-resolver" "^3.1.0" + "@0x/types" "^3.2.0" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" + "@0x/web3-wrapper" "^7.2.0" "@types/yargs" "^11.0.0" chalk "^2.3.0" chokidar "^3.0.2" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" ethereumjs-util "^5.1.1" lodash "^4.17.11" mkdirp "^0.5.1" @@ -69,62 +69,35 @@ web3-eth-abi "^1.0.0-beta.24" yargs "^10.0.3" -"@0x/sol-resolver@^3.0.0", "@0x/sol-resolver@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-3.0.3.tgz#da969e04d5512f8cc88c117dd377bdb04f3f4410" - integrity sha512-8Pn53YZd8y8RCjWjZDcZlp39fcqZIuEJo7o4q1P2umfatIfl4ihXgfgzw7iNxA64jTY2CWtltLpRHLhyOcvqxw== +"@0x/sol-resolver@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-3.1.0.tgz#86dbbc6641011df446f5a0e4545cd3360a3af52b" + integrity sha512-fyOngwc1qzN/rH+lrsGAACnXd8UuTVHkzj423+s3p5I2qhjDrQcxTfJpxQ1Yuc74fb9R0cXFRQto3A9LevjKxA== dependencies: - "@0x/types" "^3.1.2" - "@0x/typescript-typings" "^5.0.2" + "@0x/types" "^3.2.0" + "@0x/typescript-typings" "^5.1.1" lodash "^4.17.11" -"@0x/sol-tracing-utils@^7.0.3": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-7.0.8.tgz#f1c2e1bf064d126d988d5574fbb6d45037064e8e" - integrity sha512-dFZ/CVl4hWJ2rDbcjsnIRif8gd/Eu4lT+5iEDlD427ns1tKaSg/sVPr7Qz0UubipxHDozFY0f69vGdZmNw7Uew== - dependencies: - "@0x/dev-utils" "^3.2.1" - "@0x/sol-compiler" "^4.0.8" - "@0x/sol-resolver" "^3.0.3" - "@0x/subproviders" "^6.0.8" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" - "@0x/web3-wrapper" "^7.0.7" - "@types/solidity-parser-antlr" "^0.2.3" - chalk "^2.3.0" - ethereum-types "^3.1.0" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - glob "^7.1.2" - istanbul "^0.4.5" - lodash "^4.17.11" - loglevel "^1.6.1" - mkdirp "^0.5.1" - rimraf "^2.6.2" - semaphore-async-await "^1.5.1" - solc "^0.5.5" - solidity-parser-antlr "^0.4.2" - -"@0x/subproviders@^6.0.0", "@0x/subproviders@^6.0.8": - version "6.0.8" - resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-6.0.8.tgz#5fd7461201d434785f231e9d5f2b1b1015e8cc1b" - integrity sha512-OEFZ6qdkHfAvkkcvXSLiCJd2V87NGiZvNcZZkNeupCK77mlDnO0k/IpCe3hr9bUu8PV8vOBvrQaE973vjyCfgQ== - dependencies: - "@0x/assert" "^3.0.7" - "@0x/types" "^3.1.2" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" - "@0x/web3-wrapper" "^7.0.7" +"@0x/subproviders@^6.0.0", "@0x/subproviders@^6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-6.1.1.tgz#f0d523055cb889652a7e53263cbe0b9d93bb1d4b" + integrity sha512-5rXmQbokPAlq6Am+O/C2QV6VlxKJGREncJ50ymLp0z8Bsyjt864Mgb1sB1ym19Qg6EJEhamQiJzVrrkN4ApbTQ== + dependencies: + "@0x/assert" "^3.0.9" + "@0x/types" "^3.2.0" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" + "@0x/web3-wrapper" "^7.2.0" "@ledgerhq/hw-app-eth" "^4.3.0" "@ledgerhq/hw-transport-u2f" "4.24.0" "@types/hdkey" "^0.7.0" "@types/web3-provider-engine" "^14.0.0" bip39 "^2.5.0" bn.js "^4.11.8" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" ethereumjs-tx "^1.3.5" ethereumjs-util "^5.1.1" - ganache-core "^2.9.0-istanbul.0" + ganache-core "^2.10.2" hdkey "^0.7.1" json-rpc-error "2.0.0" lodash "^4.17.11" @@ -133,205 +106,204 @@ optionalDependencies: "@ledgerhq/hw-transport-node-hid" "^4.3.0" -"@0x/types@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@0x/types/-/types-3.1.2.tgz#85fb2e6de4b459bcb162a6065dcd7dc8bdb233ce" - integrity sha512-jweDayth9SSmvhx2Z5cARqQAdB9luzDm+GCzmpqQXYpdPPUzUMXQWjepGouLUgLEoBEq7Xm7DkY+qcTq3ekrSQ== +"@0x/types@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@0x/types/-/types-3.2.0.tgz#490627e9440c3ff4e4660b0c10960ccd02d7105f" + integrity sha512-HcYXmz7gYtibJmZQjrQCnzV2FjRoo5b/Ul1QoTeQFAZ2M2ggt1wHspQ7vOkEAZEK/7TE4TKA4MlRwRLa4isL1Q== dependencies: "@types/node" "*" bignumber.js "~9.0.0" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" -"@0x/typescript-typings@^5.0.0", "@0x/typescript-typings@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-5.0.2.tgz#5589ee8721165d0aa2e5290e28372c560970ab9d" - integrity sha512-syOJE/cN8lg4Homh/TGZPiS493NBmKBFjlaeOpnrjjzGcCG2SqY4nbj+VjGrvhJdw9/KM/J1nWqsCV+KSDORhg== +"@0x/typescript-typings@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-5.1.1.tgz#e5216f33e5859d48cd441a059da9c56c2b6df36a" + integrity sha512-a8/uKPNStHORFQzI/DDEq4ixb4IricPogAJ3P17YnNYr/yF3HwXEu6+cFxs4qi1fr0zGoPe7D3XtqtR+dX/ajQ== dependencies: "@types/bn.js" "^4.11.0" "@types/react" "*" bignumber.js "~9.0.0" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" popper.js "1.14.3" -"@0x/utils@^5.0.0", "@0x/utils@^5.4.1": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-5.4.1.tgz#5ec5f0d08dad38543b6ff8199bdf8f3d0e63488e" - integrity sha512-zmolzXYt1FZlF3nGI9I1FaMdPp3kQAO/ZmtObFjN2KHG35dl+BBk/lSlWtmaqlVLaSLBQzLREKgjswKEcyT+xA== +"@0x/utils@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-5.5.1.tgz#5f131755e1692e7c7b74d03d224d94808d475dd7" + integrity sha512-UreVm7R/qEv1v4dVccb4Y6Oz13o8ko2vB10sdNrPAGbstqx3NfdEBkylNxD7I8Bkta/BIwHrLbtCN3jni8CSeg== dependencies: - "@0x/types" "^3.1.2" - "@0x/typescript-typings" "^5.0.2" + "@0x/types" "^3.2.0" + "@0x/typescript-typings" "^5.1.1" "@types/node" "*" abortcontroller-polyfill "^1.1.9" bignumber.js "~9.0.0" chalk "^2.3.0" detect-node "2.0.3" - ethereum-types "^3.1.0" + ethereum-types "^3.2.0" ethereumjs-util "^5.1.1" ethers "~4.0.4" isomorphic-fetch "2.2.1" js-sha3 "^0.7.0" lodash "^4.17.11" -"@0x/web3-wrapper@^7.0.0", "@0x/web3-wrapper@^7.0.7": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-7.0.7.tgz#c88b46b1b79e1775e3581d3cbd3e8f26ce220bd1" - integrity sha512-4dqtJW14W2E/hFBL0mTBd6w7QYp3l+OxAsJwhbmB9xnhZj8DfLrQA2RC+cFZnUarhjUeXlUh2B1Zr6YXLv3lEQ== +"@0x/web3-wrapper@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-7.2.0.tgz#079f59276a7ea4e2920881645c51f71b1c0251e3" + integrity sha512-5jRr5Xl/co5VZB2sCFiokuRwuPc2BENeSVuXll/+YNmytP5+C+7oDvVt6GrGP3j5921GIr4EhusZMbvOFw1oKQ== dependencies: - "@0x/assert" "^3.0.7" - "@0x/json-schemas" "^5.0.7" - "@0x/typescript-typings" "^5.0.2" - "@0x/utils" "^5.4.1" - ethereum-types "^3.1.0" + "@0x/assert" "^3.0.9" + "@0x/json-schemas" "^5.1.0" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" + ethereum-types "^3.2.0" ethereumjs-util "^5.1.1" ethers "~4.0.4" lodash "^4.17.11" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" - integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - "@babel/highlight" "^7.10.3" + "@babel/highlight" "^7.10.4" "@babel/core@^7.1.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e" - integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" + integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.5" + "@babel/types" "^7.10.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" json5 "^2.1.2" - lodash "^4.17.13" + lodash "^4.17.19" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.10.3", "@babel/generator@^7.4.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5" - integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA== +"@babel/generator@^7.10.5", "@babel/generator@^7.4.0": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" + integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== dependencies: - "@babel/types" "^7.10.3" + "@babel/types" "^7.10.5" jsesc "^2.5.1" - lodash "^4.17.13" source-map "^0.5.0" -"@babel/helper-function-name@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197" - integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw== - dependencies: - "@babel/helper-get-function-arity" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-get-function-arity@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e" - integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-member-expression-to-functions@^7.10.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6" - integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a" - integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-module-transforms@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" - integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-simple-access" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.10.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530" - integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.3", "@babel/helper-plugin-utils@^7.8.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz#aac45cccf8bc1873b99a85f34bceef3beb5d3244" - integrity sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g== - -"@babel/helper-replace-supers@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" - integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-simple-access@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e" - integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw== - dependencies: - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-split-export-declaration@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" - integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-validator-identifier@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15" - integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw== - -"@babel/helpers@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973" - integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw== - dependencies: - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/highlight@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d" - integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee" + integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA== + dependencies: + "@babel/types" "^7.10.5" + +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-transforms@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" + integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-split-export-declaration@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" + integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.4.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" - integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.4.3": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== "@babel/plugin-syntax-object-rest-spread@^7.0.0": version "7.8.3" @@ -341,53 +313,53 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-transform-runtime@^7.5.5": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.3.tgz#3b287b06acc534a7cb6e6c71d6b1d88b1922dd6c" - integrity sha512-b5OzMD1Hi8BBzgQdRHyVVaYrk9zG0wset1it2o3BgonkPadXfOv0aXRqd7864DeOIu3FGKP/h6lr15FE5mahVw== + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.5.tgz#3b39b7b24830e0c2d8ff7a4489fe5cf99fbace86" + integrity sha512-tV4V/FjElJ9lQtyjr5xD2IFFbgY46r7EeVu5a8CpEKT5laheHKSlFeHjpkPppW3PqzGLAuv5k2qZX5LgVZIX5w== dependencies: - "@babel/helper-module-imports" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" resolve "^1.8.1" semver "^5.5.1" "@babel/runtime@^7.5.5": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" - integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.1", "@babel/template@^7.10.3", "@babel/template@^7.4.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8" - integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.10.3", "@babel/traverse@^7.4.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e" - integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" +"@babel/template@^7.10.4", "@babel/template@^7.4.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.4.3": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" + lodash "^4.17.19" -"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.10.3", "@babel/types@^7.3.0", "@babel/types@^7.4.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e" - integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA== +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.3.0", "@babel/types@^7.4.0": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== dependencies: - "@babel/helper-validator-identifier" "^7.10.3" - lodash "^4.17.13" + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" to-fast-properties "^2.0.0" "@cnakazawa/watch@^1.0.3": @@ -398,35 +370,35 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@compound-finance/sol-coverage@^4.0.0-r1": - version "4.0.0-r1" - resolved "https://registry.yarnpkg.com/@compound-finance/sol-coverage/-/sol-coverage-4.0.0-r1.tgz#d887f875f6ad0a8675fccf30357546a7bc17b2aa" - integrity sha512-vs7dEx6AaufsqhRECf0CLCCsR5xsKKbJKlM/RQzksBSAo9EpC6+7aEquhTfCIsTz176IjzTQmPhdDLn7/nEHsg== +"@compound-finance/sol-coverage@^4.0.10-alpha.7": + version "4.0.10-alpha.7" + resolved "https://registry.yarnpkg.com/@compound-finance/sol-coverage/-/sol-coverage-4.0.10-alpha.7.tgz#7459f23a9934f987e865bc1717a33da137dae696" + integrity sha512-UtBAc50sdv2KEnG3y5d2Vm/WvN5UvnhhZZ73BSYut67DNu4l22kOvlgivgO+Gd4cfTHRNhoy7cy0mZuUBomDfQ== dependencies: - "@0x/subproviders" "^6.0.0" - "@0x/typescript-typings" "^5.0.0" - "@compound-finance/sol-tracing-utils" "^7.0.0-r1" + "@0x/subproviders" "^6.1.1" + "@0x/typescript-typings" "^5.1.1" + "@compound-finance/sol-tracing-utils" "^7.1.0-alpha.7" "@types/minimatch" "^3.0.3" - ethereum-types "^3.0.0" + ethereum-types "^3.2.0" lodash "^4.17.11" minimatch "^3.0.4" web3-provider-engine "14.0.6" -"@compound-finance/sol-tracing-utils@^7.0.0-r1": - version "7.0.0-r1" - resolved "https://registry.yarnpkg.com/@compound-finance/sol-tracing-utils/-/sol-tracing-utils-7.0.0-r1.tgz#61c16f2a967cd4e51feba2d1f5efd2c9a2dc1df8" - integrity sha512-NJu62xUMU64tsNUavZjtyMnIjRfdm39JHpbiDfz3I+F8UoDwnLr5qbMZlDZXqgukuGkRS7aVJ3PXZqYnjtP1Ag== - dependencies: - "@0x/dev-utils" "^3.0.0" - "@0x/sol-compiler" "^4.0.0" - "@0x/sol-resolver" "^3.0.0" - "@0x/subproviders" "^6.0.0" - "@0x/typescript-typings" "^5.0.0" - "@0x/utils" "^5.0.0" - "@0x/web3-wrapper" "^7.0.0" +"@compound-finance/sol-tracing-utils@7.1.0-alpha.7", "@compound-finance/sol-tracing-utils@^7.1.0-alpha.7": + version "7.1.0-alpha.7" + resolved "https://registry.yarnpkg.com/@compound-finance/sol-tracing-utils/-/sol-tracing-utils-7.1.0-alpha.7.tgz#ad4b06320fb2db115d3266ca8fdfdd68e6d57080" + integrity sha512-7yrJsjg7U6zwrDu09FTRN2ziQAv140dBZnQEasiXGy2UPhv17hToKow/bXtrR233ULQ3xHNR9hlpm2jatE5xVQ== + dependencies: + "@0x/dev-utils" "^3.3.0" + "@0x/sol-compiler" "^4.1.1" + "@0x/sol-resolver" "^3.1.0" + "@0x/subproviders" "^6.1.1" + "@0x/typescript-typings" "^5.1.1" + "@0x/utils" "^5.5.1" + "@0x/web3-wrapper" "^7.2.0" "@types/solidity-parser-antlr" "^0.2.3" chalk "^2.3.0" - ethereum-types "^3.0.0" + ethereum-types "^3.2.0" ethereumjs-util "^5.1.1" ethers "~4.0.4" glob "^7.1.2" @@ -437,7 +409,7 @@ rimraf "^2.6.2" semaphore-async-await "^1.5.1" solc "^0.5.5" - solidity-parser-antlr "^0.4.2" + solidity-parser-antlr "https://github.com/solidity-parser/parser" "@ethersproject/abi@5.0.0-beta.153": version "5.0.0-beta.153" @@ -455,9 +427,9 @@ "@ethersproject/strings" ">=5.0.0-beta.130" "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.1.tgz#882424fbbec1111abc1aa3482e647a72683c5377" - integrity sha512-kfQtXpBP2pI2TfoRRAYv8grHGiYw8U0c1KbMsC58/W33TIBy7gFSf/oAzOd94lNzdIUenKU0OuSzrHQfVcDDDA== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a" + integrity sha512-+rz26RKj7ujGfQynys4V9VJRbR+wpC6eL8F22q3raWMH3152Ha31GwJPWzxE/bEA+43M/zTNVwY0R53gn53L2Q== dependencies: "@ethersproject/bignumber" "^5.0.0" "@ethersproject/bytes" "^5.0.0" @@ -467,33 +439,32 @@ bn.js "^4.4.0" "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.1.tgz#85edb1026310633ef566cc379e3e0026697f6e6e" - integrity sha512-srGDO7ksT0avdDw5pBtj6F81psv5xiJMInwSSatfIKplitubFb6yVwoHGObGRd0Pp3TvrkIDfJkuskoSMj4OHQ== + version "5.0.5" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b" + integrity sha512-24ln7PV0g8ZzjcVZiLW9Wod0i+XCmK6zKkAaxw5enraTIT1p7gVOcSXFSzNQ9WYAwtiFQPvvA+TIO2oEITZNJA== dependencies: "@ethersproject/bytes" "^5.0.0" "@ethersproject/logger" "^5.0.0" - "@ethersproject/properties" "^5.0.0" bn.js "^4.4.0" "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.1.tgz#da8cd9b3e3b2800be9b46fda7036fc441b334680" - integrity sha512-Y198536UW9Jb9RBXuqmCsCa9mYJUsxJn+5aGr2XjNMpLBc6vEn/44GHnbQXYgRCzh4rnWtJ9bTgSwDjme9Hgnw== + version "5.0.3" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.3.tgz#b3769963ae0188a35713d343890a903bda20af9c" + integrity sha512-AyPMAlY+Amaw4Zfp8OAivm1xYPI8mqiUYmEnSUk1CnS2NrQGHEMmFJFiOJdS3gDDpgSOFhWIjZwxKq2VZpqNTA== dependencies: "@ethersproject/logger" "^5.0.0" "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.1.tgz#51426b1d673661e905418ddeefca1f634866860d" - integrity sha512-Xec07hFCPN4wfC3WDiRay7KipkApl2msiKTrBHCuAwNMOM8M92+mlQp8tgfEL51DPwCZkmdk1f02kArc6caVSw== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1" + integrity sha512-nNoVlNP6bgpog7pQ2EyD1xjlaXcy1Cl4kK5v1KoskHj58EtB6TK8M8AFGi3GgHTdMldfT4eN3OsoQ/CdOTVNFA== dependencies: "@ethersproject/bignumber" "^5.0.0" "@ethersproject/hash@>=5.0.0-beta.128": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.1.tgz#8190240d250b9442dd25f1e8ec2d66e7d0d38237" - integrity sha512-1ByUXYvkszrSSks07xctBtZfpFnIVmftxWlAAnguxh6Q65vKECd/EPi5uI5xVOvnrYMH9Vb8MK1SofPX/6fArQ== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7" + integrity sha512-dWGvNwmVRX2bxoQQ3ciMw46Vzl1nqfL+5R8+2ZxsRXD3Cjgw1dL2mdjJF7xMMWPvPdrlhKXWSK0gb8VLwHZ8Cw== dependencies: "@ethersproject/bytes" "^5.0.0" "@ethersproject/keccak256" "^5.0.0" @@ -501,56 +472,56 @@ "@ethersproject/strings" "^5.0.0" "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.1.tgz#be91c11a8bdf4e94c8b900502d2a46b223fbdeb3" - integrity sha512-AtFm/4qHRQUvZcG3WYmaT7zV79dz72+N01w0XphcIBaD/7UZXyW85Uf08sirVlckHmh9fvc4UDWyHiroKsBT6Q== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.2.tgz#7ed4a95bb45ee502cf4532223833740a83602797" + integrity sha512-MbroXutc0gPNYIrUjS4Aw0lDuXabdzI7+l7elRWr1G6G+W0v00e/3gbikWkCReGtt2Jnt4lQSgnflhDwQGcIhA== dependencies: "@ethersproject/bytes" "^5.0.0" js-sha3 "0.5.7" "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.0": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.2.tgz#f24aa14a738a428d711c1828b44d50114a461b8b" - integrity sha512-NQe3O1/Nwkcp6bto6hsTvrcCeR/cOGK+RhOMn0Zi2FND6gdWsf1g+5ie8gQ1REqDX4MTGP/Y131dZas985ls/g== + version "5.0.4" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4" + integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw== "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.1.tgz#e1fecbfcb24f23bf3b64a2ac74f2751113d116e0" - integrity sha512-b3VZ/NpYIf64/hFXeWNxVCbY1xoMPIYM3n6Qnu6Ayr3bLt1olFPQfAaaRB0aOsLz7tMtmkT3DrA1KG/IrOgBRw== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.2.tgz#2facb62d2f2d968c7b3d0befa5bcc884cc565d3b" + integrity sha512-FxAisPGAOACQjMJzewl9OJG6lsGCPTm5vpUMtfeoxzAlAb2lv+kHzQPUh9h4jfAILzE8AR1jgXMzRmlhwyra1Q== dependencies: "@ethersproject/logger" "^5.0.0" "@ethersproject/rlp@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.1.tgz#3407b0cb78f82a1a219aecff57578c0558ae26c8" - integrity sha512-3F8XE1zS4w8w4xiK1hMtFuVs6UnhQlmrEHLT85GanqK8vG5wGi81IQmkukL9tQIu2a5jykoO46ibja+6N1fpFg== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.2.tgz#d6b550a2ac5e484f15f0f63337e522004d2e78cd" + integrity sha512-oE0M5jqQ67fi2SuMcrpoewOpEuoXaD8M9JeR9md1bXRMvDYgKXUtDHs22oevpEOdnO2DPIRabp6MVHa4aDuWmw== dependencies: "@ethersproject/bytes" "^5.0.0" "@ethersproject/logger" "^5.0.0" "@ethersproject/signing-key@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.1.tgz#90957e42c69e857dc741c8fbeeff0f36bcee9cf7" - integrity sha512-Z3yMPFFf4KkWltndDNi/tpese7qZh6ZWKbGu3DHd8xOX0PJqbScdAs6gCfFeMatO06qyX307Y52soc/Ayf8ZSg== + version "5.0.3" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.3.tgz#adb84360e147bfd336cb2fe114100120732dc10a" + integrity sha512-5QPZaBRGCLzfVMbFb3LcVjNR0UbTXnwDHASnQYfbzwUOnFYHKxHsrcbl/5ONGoppgi8yXgOocKqlPCFycJJVWQ== dependencies: "@ethersproject/bytes" "^5.0.0" "@ethersproject/logger" "^5.0.0" "@ethersproject/properties" "^5.0.0" - elliptic "6.5.2" + elliptic "6.5.3" "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.1.tgz#a93aafeede100c4aad7f48e25aad1ddc42eeccc7" - integrity sha512-N8LxdHGBT7GZdogkEOV5xKXYTz5PNHuNzcxLNPYfH3kpvWSyXshZBgAz8YE1a8sMZagGj+Ic6d3mHijdCTSkGA== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619" + integrity sha512-oNa+xvSqsFU96ndzog0IBTtsRFGOqGpzrXJ7shXLBT7juVeSEyZA/sYs0DMZB5mJ9FEjHdZKxR/rTyBY91vuXg== dependencies: "@ethersproject/bytes" "^5.0.0" "@ethersproject/constants" "^5.0.0" "@ethersproject/logger" "^5.0.0" "@ethersproject/transactions@^5.0.0-beta.135": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.1.tgz#61480dc600f4a49eb99627778e3b46b381f8bdd9" - integrity sha512-IGc6/5hri3PrqR/ZCj89osDiq3Lt0CSrycn6vlRl8SjpBKYDdcT+Ru5xkeC7YcsnqcdBmTL+jyR3SLudU+x2Kw== + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.2.tgz#590ede71fc87b45be7bd46002e18ae52246a2347" + integrity sha512-jZp0ZbbJlq4JLZY6qoMzNtp2HQsX6USQposi3ns0MPUdn3OdZJBDtrcO15r/2VS5t/K1e1GE5MI1HmMKlcTbbQ== dependencies: "@ethersproject/address" "^5.0.0" "@ethersproject/bignumber" "^5.0.0" @@ -849,9 +820,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.12.tgz#22f49a028e69465390f87bb103ebd61bd086b8f5" - integrity sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA== + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.13.tgz#1874914be974a492e1b4cb00585cabb274e8ba18" + integrity sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ== dependencies: "@babel/types" "^7.3.0" @@ -862,7 +833,7 @@ dependencies: bignumber.js "*" -"@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4": +"@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4", "@types/bn.js@^4.11.5": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== @@ -921,19 +892,26 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*": - version "14.0.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" - integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== + version "14.0.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== "@types/node@^10.12.18", "@types/node@^10.3.2": - version "10.17.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" - integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== + version "10.17.28" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.28.tgz#0e36d718a29355ee51cec83b42d921299200f6d9" + integrity sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ== + +"@types/node@^12.12.6", "@types/node@^12.6.1": + version "12.12.53" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.53.tgz#be0d375933c3d15ef2380dafb3b0350ea7021129" + integrity sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ== -"@types/node@^12.6.1": - version "12.12.47" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.47.tgz#5007b8866a2f9150de82335ca7e24dd1d59bdfb5" - integrity sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A== +"@types/pbkdf2@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + dependencies: + "@types/node" "*" "@types/prop-types@*": version "15.7.3" @@ -941,13 +919,20 @@ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== "@types/react@*": - version "16.9.38" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.38.tgz#868405dace93a4095d3e054f4c4a1de7a1ac0680" - integrity sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA== + version "16.9.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.43.tgz#c287f23f6189666ee3bebc2eb8d0f84bcb6cdb6b" + integrity sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg== dependencies: "@types/prop-types" "*" csstype "^2.2.0" +"@types/secp256k1@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.1.tgz#fb3aa61a1848ad97d7425ff9dcba784549fca5a4" + integrity sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog== + dependencies: + "@types/node" "*" + "@types/solidity-parser-antlr@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" @@ -1098,9 +1083,9 @@ aes-js@^3.1.1: integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== ajv@^6.5.5: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + version "6.12.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" + integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -2053,9 +2038,9 @@ binary-extensions@^1.0.0: integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== bindings@^1.2.1, bindings@^1.4.0, bindings@^1.5.0: version "1.5.0" @@ -2110,6 +2095,11 @@ bl@^4.0.1: inherits "^2.0.4" readable-stream "^3.4.0" +blakejs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" + integrity sha1-ad+S75U6qIylGjLfarHFShVfx6U= + bluebird@^3.5.0: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -2125,7 +2115,7 @@ bn.js@4.11.8: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.4.0, bn.js@^4.8.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0, bn.js@^4.8.0: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== @@ -2199,7 +2189,7 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: +browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6, browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -2238,14 +2228,6 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: bn.js "^4.1.0" randombytes "^2.0.1" -browserify-sha3@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/browserify-sha3/-/browserify-sha3-0.0.4.tgz#086c47b8c82316c9d47022c26185954576dd8e26" - integrity sha1-CGxHuMgjFsnUcCLCYYWVRXbdjiY= - dependencies: - js-sha3 "^0.6.1" - safe-buffer "^5.1.1" - browserify-sign@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" @@ -2359,7 +2341,7 @@ buffer-xor@^2.0.1: dependencies: safe-buffer "^5.1.1" -buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: +buffer@5.6.0, buffer@^5.0.5, buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -2444,9 +2426,9 @@ camelcase@^5.0.0, camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-lite@^1.0.30000844: - version "1.0.30001085" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001085.tgz#bed28bd51ff7425d33ee23e730c7f3b703711db6" - integrity sha512-x0YRFRE0pmOD90z+9Xk7jwO58p4feVNXP+U8kWV+Uo/HADyrgESlepzIkUqPgaXkpyceZU6siM1gsK7sHgplqA== + version "1.0.30001107" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a" + integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ== capture-exit@^2.0.0: version "2.0.0" @@ -2544,9 +2526,9 @@ chokidar@^2.0.0: fsevents "^1.2.7" chokidar@^3.0.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== + version "3.4.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" + integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -2950,9 +2932,9 @@ cssstyle@^1.0.0: cssom "0.3.x" csstype@^2.2.0: - version "2.6.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" - integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== + version "2.6.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.11.tgz#452f4d024149ecf260a852b025e36562a253ffc5" + integrity sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw== d@1, d@^1.0.1: version "1.0.1" @@ -3250,11 +3232,6 @@ dirty-chai@^2.0.1: resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" integrity sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w== -docker-compose@^0.23.1: - version "0.23.4" - resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.4.tgz#43bcabcde55a6ba2873b52fe0ccd99dd8fdceba8" - integrity sha512-yWdXby9uQ8o4syOfvoSJ9ZlTnLipvUmDn59uaYY5VGIUSUAfMPPGqE1DE3pOCnfSg9Tl9UOOFO0PCSAzuIHmuA== - dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -3320,9 +3297,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.47: - version "1.3.480" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.480.tgz#190ae45074578349a4c4f336fba29e76b20e9ef5" - integrity sha512-wnuUfQCBMAdzu5Xe+F4FjaRK+6ToG6WvwG72s8k/3E6b+hoGVYGiQE7JD1NhiCMcqF3+wV+c2vAnaLGRSSWVqA== + version "1.3.509" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.509.tgz#830fcb89cd66dc2984d18d794973b99e3f00584c" + integrity sha512-cN4lkjNRuTG8rtAqTOVgwpecEC2kbKA04PG6YijcKGHK/kD0xLjiqExcAOmLUwtXZRF8cBeam2I0VZcih919Ug== elliptic@6.3.3: version "6.3.3" @@ -3347,7 +3324,7 @@ elliptic@6.5.2: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.5.2: +elliptic@6.5.3, elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== @@ -3387,11 +3364,11 @@ encoding-down@5.0.4, encoding-down@~5.0.0: xtend "^4.0.1" encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: - iconv-lite "~0.4.13" + iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" @@ -3499,9 +3476,9 @@ escodegen@1.8.x: source-map "~0.2.0" escodegen@^1.9.1: - version "1.14.2" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84" - integrity sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A== + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== dependencies: esprima "^4.0.1" estraverse "^4.2.0" @@ -3667,6 +3644,15 @@ eth-lib@0.2.7: elliptic "^6.4.0" xhr-request-promise "^0.1.2" +eth-lib@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" + integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + eth-lib@^0.1.26: version "0.1.29" resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" @@ -3679,15 +3665,6 @@ eth-lib@^0.1.26: ws "^3.0.0" xhr-request-promise "^0.1.2" -eth-lib@^0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" - integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - xhr-request-promise "^0.1.2" - eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/eth-query/-/eth-query-2.1.2.tgz#d6741d9000106b51510c72db92d6365456a6da5e" @@ -3696,26 +3673,26 @@ eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: json-rpc-random-id "^1.0.0" xtend "^4.0.1" -eth-saddle@^0.1.17: - version "0.1.17" - resolved "https://registry.yarnpkg.com/eth-saddle/-/eth-saddle-0.1.17.tgz#54c7bf89970739f0154b382cfbdda9ccdae314f5" - integrity sha512-o+ZhnasyFmlBtmeAI8MV/pnCPtjxnkJ058qQ9H0k27Ew6f+Aj7k/k0HuEElCMkczELTa57UJIv0nZqjCVNARxw== +eth-saddle@0.1.19: + version "0.1.19" + resolved "https://registry.yarnpkg.com/eth-saddle/-/eth-saddle-0.1.19.tgz#0822206f92d6063ce3f44a70b4d0a26fcfb1b740" + integrity sha512-ntTFkNuN21gZEK0mGTS1uMJgRnI0Q/HpNAguL0ybfgojqzyvFSgRwg1M1lrCTAsLrR52UQZT5BQDEKX6cftW/g== dependencies: - "@0x/sol-tracing-utils" "^7.0.3" "@0x/subproviders" "^6.0.0" - "@compound-finance/sol-coverage" "^4.0.0-r1" + "@compound-finance/sol-coverage" "^4.0.10-alpha.7" + "@compound-finance/sol-tracing-utils" "7.1.0-alpha.7" "@types/jest" "^24.0.15" ethereumjs-tx "2.1.2" ethereumjs-util "5.2.0" fast-glob "^3.2.2" - ganache-core "^2.9.1" + ganache-core "^2.10.2" jest "^24.9.0" jest-cli "^24.9.0" jest-diff "^25.3.0" jest-junit "^6.4.0" ts-jest "^24.0.2" typescript "^3.5.1" - web3 "^1.2.4" + web3 "^1.2.11" web3-provider-engine "^15.0.4" yargs "^13.2.4" @@ -3782,10 +3759,31 @@ ethereum-common@^0.0.18: resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= -ethereum-types@^3.0.0, ethereum-types@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.1.0.tgz#76de33b398b5b5375e08e647c92ac00e87a647d9" - integrity sha512-E4lBfxzRSFjd2OM+BATc0b3nN+xWVpAQhBCwSNS9kbsXVPV2LNhIduwCR8Poc8seQukFfuXp0QoVXOlQbUS2pQ== +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereum-types@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.2.0.tgz#5bd27cadc3c1b3c2e2bf94654fa2bc3618a4520f" + integrity sha512-osxikvWF2CuHauo2jiBpGalLXbCj5xWm2WcNr+Z4sNTk7z6DArPNXwsgANu2bA+aAsqSSF4NgsNx8JS1d3xdOQ== dependencies: "@types/node" "*" bignumber.js "~9.0.0" @@ -3854,15 +3852,15 @@ ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0: merkle-patricia-tree "^2.1.2" ethereumjs-blockchain@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.3.tgz#e013034633a30ad2006728e8e2b21956b267b773" - integrity sha512-0nJWbyA+Gu0ZKZr/cywMtB/77aS/4lOVsIKbgUN2sFQYscXO5rPbUfrEe7G2Zhjp86/a0VqLllemDSTHvx3vZA== + version "4.0.4" + resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.4.tgz#30f2228dc35f6dcf94423692a6902604ae34960f" + integrity sha512-zCxaRMUOzzjvX78DTGiKjA+4h2/sF0OYL1QuPux0DHpyq8XiNoF5GYHtb++GUxVlMsMfZV7AVyzbtgcRdIcEPQ== dependencies: async "^2.6.1" ethashjs "~0.0.7" ethereumjs-block "~2.2.2" ethereumjs-common "^1.5.0" - ethereumjs-util "~6.1.0" + ethereumjs-util "^6.1.0" flow-stoplight "^1.0.0" level-mem "^3.0.1" lru-cache "^5.1.1" @@ -3895,7 +3893,7 @@ ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@ ethereum-common "^0.0.18" ethereumjs-util "^5.0.0" -ethereumjs-util@5.2.0, ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5: +ethereumjs-util@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642" integrity sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA== @@ -3908,7 +3906,7 @@ ethereumjs-util@5.2.0, ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumj safe-buffer "^5.1.1" secp256k1 "^3.0.1" -ethereumjs-util@6.2.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.0: +ethereumjs-util@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.0.tgz#23ec79b2488a7d041242f01e25f24e5ad0357960" integrity sha512-vb0XN9J2QGdZGIEKG2vXM+kUdEivUfU6Wmi5y0cg+LRhDYKnXIZ/Lz7XjFbHRR9VIKq2lVGLzGBkA++y2nOdOQ== @@ -3922,41 +3920,53 @@ ethereumjs-util@6.2.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.0: secp256k1 "^3.0.1" ethereumjs-util@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6" - integrity sha1-PpQosxfuvaPXJg2FT93alUsfG8Y= + version "4.5.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz#f4bf9b3b515a484e3cc8781d61d9d980f7c83bd0" + integrity sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w== dependencies: bn.js "^4.8.0" create-hash "^1.1.2" - keccakjs "^0.2.0" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" rlp "^2.0.0" - secp256k1 "^3.0.1" -ethereumjs-util@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.2.tgz#7e0d9fcd225ece6e49ee4ea65609d124715f1d15" - integrity sha512-ATAP02eJLpAlWGfiKQddNrRfZpwXiTFhRN2EM/yLXMCdBW/xjKYblNKcx8GLzzrjXg0ymotck+lam1nuV90arQ== +ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz#a833f0e5fca7e5b361384dc76301a721f537bf65" + integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ== + dependencies: + bn.js "^4.11.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + ethjs-util "^0.1.3" + rlp "^2.0.0" + safe-buffer "^5.1.1" + +ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" + integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw== dependencies: "@types/bn.js" "^4.11.3" - bn.js "^5.1.2" + bn.js "^4.11.0" create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" ethjs-util "0.1.6" - keccak "^3.0.0" - rlp "^2.2.4" - secp256k1 "^4.0.1" + rlp "^2.2.3" -ethereumjs-util@~6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" - integrity sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q== +ethereumjs-util@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.3.tgz#d055fc7f852d79065d2e689002a789b1323cf3fd" + integrity sha512-uLQsGPOwsRxe50WV1Dybh5N8zXDz4ev7wP49LKX9kr28I5TmcDILPgpKK/BFe5zYSfRGEeo+hPT7W3tjghYLuA== dependencies: - bn.js "^4.11.0" + "@types/bn.js" "^4.11.3" + bn.js "^5.1.2" create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" ethjs-util "0.1.6" - keccak "^1.0.2" - rlp "^2.0.0" - safe-buffer "^5.1.1" - secp256k1 "^3.0.1" + rlp "^2.2.4" ethereumjs-vm@4.1.3: version "4.1.3" @@ -4063,15 +4073,15 @@ eventemitter3@3.1.2: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== -eventemitter3@^4.0.0: +eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -4565,7 +4575,7 @@ functional-red-black-tree@^1.0.1, functional-red-black-tree@~1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -ganache-core@^2.9.0-istanbul.0, ganache-core@^2.9.1, "ganache-core@https://github.com/compound-finance/ganache-core.git#compound": +ganache-core@^2.10.2, "ganache-core@https://github.com/compound-finance/ganache-core.git#compound": version "2.10.2" resolved "https://github.com/compound-finance/ganache-core.git#f779cc61864176c8432caed0cc7f698d58113947" dependencies: @@ -4708,15 +4718,16 @@ glob-stream@^6.1.0: unique-stream "^2.0.2" glob-watcher@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" - integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== + version "5.0.5" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.5.tgz#aa6bce648332924d9a8489be41e3e5c52d4186dc" + integrity sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw== dependencies: anymatch "^2.0.0" async-done "^1.2.0" chokidar "^2.0.0" is-negated-glob "^1.0.0" just-debounce "^1.0.0" + normalize-path "^3.0.0" object.defaults "^1.1.0" glob@^5.0.15: @@ -5004,7 +5015,7 @@ hash.js@1.1.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -5116,13 +5127,20 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + idna-uts46-hx@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" @@ -5827,9 +5845,9 @@ jest-mock@^24.9.0: "@jest/types" "^24.9.0" jest-pnp-resolver@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" - integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: version "24.9.0" @@ -6003,11 +6021,6 @@ js-sha3@0.8.0, js-sha3@^0.8.0: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== -js-sha3@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" - integrity sha1-W4n3enR3Z5h39YxKB1JAk0sflcA= - js-sha3@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" @@ -6226,21 +6239,13 @@ keccak@^2.0.0: safe-buffer "^5.2.0" keccak@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.0.tgz#420d1de4a38a04f33ff8401f0535fb93756861d4" - integrity sha512-/4h4FIfFEpTEuySXi/nVFM5rqSKPnnhI7cL4K3MFSwoI3VyM7AhPSq3SsysARtnEBEeIKMBUWD8cTh9nHE8AkA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== dependencies: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -keccakjs@^0.2.0: - version "0.2.3" - resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.3.tgz#5e4e969ce39689a3861f445d7752ee3477f9fe72" - integrity sha512-BjLkNDcfaZ6l8HBG9tH0tpmDv3sS2mA7FNQxFHpCdzP3Gb2MVruXBSuoM66SnVxKJpAr5dKGdkHD+bDokt8fTg== - dependencies: - browserify-sha3 "^0.0.4" - sha3 "^1.2.2" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -6319,9 +6324,11 @@ left-pad@^1.3.0: integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== level-codec@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.1.tgz#042f4aa85e56d4328ace368c950811ba802b7247" - integrity sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q== + version "9.0.2" + resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" + integrity sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ== + dependencies: + buffer "^5.6.0" level-codec@~7.0.0: version "7.0.1" @@ -6543,10 +6550,10 @@ lodash@4.17.14: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== -lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== loglevel@^1.6.1: version "1.6.8" @@ -6939,9 +6946,9 @@ multicodec@^0.5.5: varint "^5.0.0" multicodec@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.2.tgz#370dd2654cd58f3a93bde22a081d7b735e414779" - integrity sha512-IcTBw34qiRGHsEDKlWp2yLQDVZKzRZWjAfUeCYZSqHWszyCAM1o5R9YLLLV1SQVPAa9AVnXKfAA6sjyYZC/2LQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f" + integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg== dependencies: buffer "^5.6.0" varint "^5.0.0" @@ -7008,9 +7015,9 @@ negotiator@0.6.2: integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== next-tick@~1.0.0: version "1.0.0" @@ -7030,9 +7037,9 @@ node-abi@^2.7.0: semver "^5.4.1" node-addon-api@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.1.tgz#4fd0931bf6d7e48b219ff3e6abc73cbb0252b7a3" - integrity sha512-2WVfwRfIr1AVn3dRq4yRc2Hn35ND+mPJH6inC6bjpYCZVrpXPB4j3T6i//OGVfqVsR1t/X/axRulDsheq4F0LQ== + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== node-fetch@2.1.2: version "2.1.2" @@ -7048,9 +7055,9 @@ node-fetch@^1.0.1, node-fetch@~1.7.1: is-stream "^1.0.1" node-gyp-build@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.2.tgz#3f44b65adaafd42fb6c3d81afd630e45c847eb66" - integrity sha512-Lqh7mrByWCM8Cf9UPqpeoVBBo5Ugx+RKu885GAzmLBVYjeywScxHXPGLa4JfYNZmcNGwzR0Glu5/9GaQZMFqyA== + version "4.2.3" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" + integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== node-hid@^0.7.9: version "0.7.9" @@ -7554,7 +7561,7 @@ pathval@^1.1.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= -pbkdf2@^3.0.3, pbkdf2@^3.0.9: +pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.0.9: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== @@ -7642,9 +7649,9 @@ posix-character-classes@^0.1.0: integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= prebuild-install@^5.3.0, prebuild-install@^5.3.3: - version "5.3.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.4.tgz#6982d10084269d364c1856550b7d090ea31fa293" - integrity sha512-AkKN+pf4fSEihjapLEEj8n85YIw/tN6BQqkhzbDc0RvEZGdkpJBGMUYx66AAMcPG2KzmPQS7Cm16an4HVBRRMA== + version "5.3.5" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.5.tgz#e7e71e425298785ea9d22d4f958dbaccf8bb0e1b" + integrity sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -8037,9 +8044,9 @@ regenerator-runtime@^0.11.0: integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== regenerator-transform@^0.10.0: version "0.10.1" @@ -8140,19 +8147,19 @@ replace-homedir@^1.0.0: is-absolute "^1.0.0" remove-trailing-separator "^1.1.0" -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.17.15" + lodash "^4.17.19" request-promise-native@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: - request-promise-core "1.1.3" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" @@ -8286,9 +8293,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rlp@^2.0.0, rlp@^2.2.1, rlp@^2.2.2, rlp@^2.2.3, rlp@^2.2.4: - version "2.2.5" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.5.tgz#b0577b763e909f21a9dea31b4b235b2393f15ef1" - integrity sha512-y1QxTQOp0OZnjn19FxBmped4p+BSKPHwGndaqrESseyd2xXZtcgR3yuTIosh8CaMaOii9SKIYerBXnV/CpJ3qw== + version "2.2.6" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.6.tgz#c80ba6266ac7a483ef1e69e8e2f056656de2fb2c" + integrity sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg== dependencies: bn.js "^4.11.1" @@ -8308,9 +8315,9 @@ rustbn.js@~0.2.0: integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== rxjs@^6.5.3: - version "6.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + version "6.6.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" + integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== dependencies: tslib "^1.9.0" @@ -8338,7 +8345,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -8373,7 +8380,7 @@ scrypt-js@2.0.4: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -scrypt-js@^3.0.1: +scrypt-js@^3.0.0, scrypt-js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== @@ -8411,9 +8418,9 @@ secp256k1@^3.0.1: safe-buffer "^5.1.2" secp256k1@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.1.tgz#b9570ca26ace9e74c3171512bba253da9c0b6d60" - integrity sha512-iGRjbGAKfXMqhtdkkuNxsgJQfJO8Oo78Rm7DAvsG3XKngq+nJIOGqrCSXcQqIVsmCj0wFanE5uTKFxV3T9j2wg== + version "4.0.2" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" + integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== dependencies: elliptic "^6.5.2" node-addon-api "^2.0.0" @@ -8551,12 +8558,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sha3@^1.2.2: - version "1.2.6" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.6.tgz#102aa3e47dc793e2357902c3cce8760822f9e905" - integrity sha512-KgLGmJGrmNB4JWVsAV11Yk6KbvsAiygWJc7t5IebWva/0NukNrjJqhtKhzy3Eiv2AKuGvhZZt7dt1mDo7HkoiQ== +sha3@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.3.tgz#ab05b841b2bce347765db31f57fe2a3134b9fb48" + integrity sha512-Io53D4o9qOmf3Ow9p/DoGLQiQHhtuR0ulbyambvRSG+OX5yXExk2yYfvjHtb7AtOyk6K6+sPeK/qaowWc/E/GA== dependencies: - nan "2.13.2" + buffer "5.6.0" shebang-command@^1.2.0: version "1.2.0" @@ -8581,9 +8588,9 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== simple-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" - integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== simple-get@^2.7.0: version "2.8.1" @@ -8662,10 +8669,9 @@ solc@^0.5.5: semver "^5.5.0" tmp "0.0.33" -solidity-parser-antlr@^0.4.2: - version "0.4.11" - resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.4.11.tgz#af43e1f13b3b88309a875455f5d6e565b05ee5f1" - integrity sha512-4jtxasNGmyC0midtjH/lTFPZYvTTUMy6agYcF+HoMnzW8+cqo3piFrINb4ZCzpPW+7tTVFCGa5ubP34zOzeuMg== +"solidity-parser-antlr@https://github.com/solidity-parser/parser": + version "0.6.2" + resolved "https://github.com/solidity-parser/parser#f41eef8845e6f672144c98efea25adb3e2d06cfc" source-map-resolve@^0.5.0: version "0.5.3" @@ -9109,9 +9115,9 @@ tar-stream@^1.5.2: xtend "^4.0.0" tar-stream@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" - integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" + integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== dependencies: bl "^4.0.1" end-of-stream "^1.4.1" @@ -9375,9 +9381,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^3.5.1: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" @@ -9691,6 +9697,16 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +web3-bzz@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f" + integrity sha512-XGpWUEElGypBjeFyUhTkiPXFbDVD6Nr/S5jznE3t8cWUA0FxRf1n3n/NuIZeb0H9RkN2Ctd/jNma/k8XGa3YKg== + dependencies: + "@types/node" "^12.12.6" + got "9.6.0" + swarm-js "^0.1.40" + underscore "1.9.1" + web3-bzz@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.4.tgz#a4adb7a8cba3d260de649bdb1f14ed359bfb3821" @@ -9701,15 +9717,14 @@ web3-bzz@1.2.4: swarm-js "0.1.39" underscore "1.9.1" -web3-bzz@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.9.tgz#25f8a373bc2dd019f47bf80523546f98b93c8790" - integrity sha512-ogVQr9jHodu9HobARtvUSmWG22cv2EUQzlPeejGWZ7j5h20HX40EDuWyomGY5VclIj5DdLY76Tmq88RTf/6nxA== +web3-core-helpers@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.11.tgz#84c681ed0b942c0203f3b324a245a127e8c67a99" + integrity sha512-PEPoAoZd5ME7UfbnCZBdzIerpe74GEvlwT4AjOmHeCVZoIFk7EqvOZDejJHt+feJA6kMVTdd0xzRNN295UhC1A== dependencies: - "@types/node" "^10.12.18" - got "9.6.0" - swarm-js "^0.1.40" underscore "1.9.1" + web3-eth-iban "1.2.11" + web3-utils "1.2.11" web3-core-helpers@1.2.4: version "1.2.4" @@ -9720,14 +9735,17 @@ web3-core-helpers@1.2.4: web3-eth-iban "1.2.4" web3-utils "1.2.4" -web3-core-helpers@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.9.tgz#6381077c3e01c127018cb9e9e3d1422697123315" - integrity sha512-t0WAG3orLCE3lqi77ZoSRNFok3VQWZXTniZigDQjyOJYMAX7BU3F3js8HKbjVnAxlX3tiKoDxI0KBk9F3AxYuw== +web3-core-method@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.11.tgz#f880137d1507a0124912bf052534f168b8d8fbb6" + integrity sha512-ff0q76Cde94HAxLDZ6DbdmKniYCQVtvuaYh+rtOUMB6kssa5FX0q3vPmixi7NPooFnbKmmZCM6NvXg4IreTPIw== dependencies: + "@ethersproject/transactions" "^5.0.0-beta.135" underscore "1.9.1" - web3-eth-iban "1.2.9" - web3-utils "1.2.9" + web3-core-helpers "1.2.11" + web3-core-promievent "1.2.11" + web3-core-subscriptions "1.2.11" + web3-utils "1.2.11" web3-core-method@1.2.4: version "1.2.4" @@ -9740,17 +9758,12 @@ web3-core-method@1.2.4: web3-core-subscriptions "1.2.4" web3-utils "1.2.4" -web3-core-method@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.9.tgz#3fb538751029bea570e4f86731e2fa5e4945e462" - integrity sha512-bjsIoqP3gs7A/gP8+QeLUCyOKJ8bopteCSNbCX36Pxk6TYfYWNuC6hP+2GzUuqdP3xaZNe+XEElQFUNpR3oyAg== +web3-core-promievent@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.11.tgz#51fe97ca0ddec2f99bf8c3306a7a8e4b094ea3cf" + integrity sha512-il4McoDa/Ox9Agh4kyfQ8Ak/9ABYpnF8poBLL33R/EnxLsJOGQG2nZhkJa3I067hocrPSjEdlPt/0bHXsln4qA== dependencies: - "@ethersproject/transactions" "^5.0.0-beta.135" - underscore "1.9.1" - web3-core-helpers "1.2.9" - web3-core-promievent "1.2.9" - web3-core-subscriptions "1.2.9" - web3-utils "1.2.9" + eventemitter3 "4.0.4" web3-core-promievent@1.2.4: version "1.2.4" @@ -9760,12 +9773,16 @@ web3-core-promievent@1.2.4: any-promise "1.3.0" eventemitter3 "3.1.2" -web3-core-promievent@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.9.tgz#bb1c56aa6fac2f4b3c598510f06554d25c11c553" - integrity sha512-0eAUA2zjgXTleSrnc1wdoKQPPIHU6KHf4fAscu4W9kKrR+mqP1KsjYrxY9wUyjNnXxfQ+5M29ipvbiaK8OqdOw== +web3-core-requestmanager@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.11.tgz#fe6eb603fbaee18530293a91f8cf26d8ae28c45a" + integrity sha512-oFhBtLfOiIbmfl6T6gYjjj9igOvtyxJ+fjS+byRxiwFJyJ5BQOz4/9/17gWR1Cq74paTlI7vDGxYfuvfE/mKvA== dependencies: - eventemitter3 "3.1.2" + underscore "1.9.1" + web3-core-helpers "1.2.11" + web3-providers-http "1.2.11" + web3-providers-ipc "1.2.11" + web3-providers-ws "1.2.11" web3-core-requestmanager@1.2.4: version "1.2.4" @@ -9778,16 +9795,14 @@ web3-core-requestmanager@1.2.4: web3-providers-ipc "1.2.4" web3-providers-ws "1.2.4" -web3-core-requestmanager@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.9.tgz#dd6d855256c4dd681434fe0867f8cd742fe10503" - integrity sha512-1PwKV2m46ALUnIN5VPPgjOj8yMLJhhqZYvYJE34hTN5SErOkwhzx5zScvo5MN7v7KyQGFnpVCZKKGCiEnDmtFA== +web3-core-subscriptions@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.11.tgz#beca908fbfcb050c16f45f3f0f4c205e8505accd" + integrity sha512-qEF/OVqkCvQ7MPs1JylIZCZkin0aKK9lDxpAtQ1F8niEDGFqn7DT8E/vzbIa0GsOjL2fZjDhWJsaW+BSoAW1gg== dependencies: + eventemitter3 "4.0.4" underscore "1.9.1" - web3-core-helpers "1.2.9" - web3-providers-http "1.2.9" - web3-providers-ipc "1.2.9" - web3-providers-ws "1.2.9" + web3-core-helpers "1.2.11" web3-core-subscriptions@1.2.4: version "1.2.4" @@ -9798,14 +9813,18 @@ web3-core-subscriptions@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-core-subscriptions@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.9.tgz#335fd7d15dfce5d78b4b7bef05ce4b3d7237b0e4" - integrity sha512-Y48TvXPSPxEM33OmXjGVDMzTd0j8X0t2+sDw66haeBS8eYnrEzasWuBZZXDq0zNUsqyxItgBGDn+cszkgEnFqg== +web3-core@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.11.tgz#1043cacc1becb80638453cc5b2a14be9050288a7" + integrity sha512-CN7MEYOY5ryo5iVleIWRE3a3cZqVaLlIbIzDPsvQRUfzYnvzZQRZBm9Mq+ttDi2STOOzc1MKylspz/o3yq/LjQ== dependencies: - eventemitter3 "3.1.2" - underscore "1.9.1" - web3-core-helpers "1.2.9" + "@types/bn.js" "^4.11.5" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-requestmanager "1.2.11" + web3-utils "1.2.11" web3-core@1.2.4: version "1.2.4" @@ -9820,18 +9839,14 @@ web3-core@1.2.4: web3-core-requestmanager "1.2.4" web3-utils "1.2.4" -web3-core@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.9.tgz#2cba57aa259b6409db532d21bdf57db8d504fd3e" - integrity sha512-fSYv21IP658Ty2wAuU9iqmW7V+75DOYMVZsDH/c14jcF/1VXnedOcxzxSj3vArsCvXZNe6XC5/wAuGZyQwR9RA== +web3-eth-abi@1.2.11, web3-eth-abi@^1.0.0-beta.24: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.11.tgz#a887494e5d447c2926d557a3834edd66e17af9b0" + integrity sha512-PkRYc0+MjuLSgg03QVWqWlQivJqRwKItKtEpRUaxUAeLE7i/uU39gmzm2keHGcQXo3POXAbOnMqkDvOep89Crg== dependencies: - "@types/bn.js" "^4.11.4" - "@types/node" "^12.6.1" - bignumber.js "^9.0.0" - web3-core-helpers "1.2.9" - web3-core-method "1.2.9" - web3-core-requestmanager "1.2.9" - web3-utils "1.2.9" + "@ethersproject/abi" "5.0.0-beta.153" + underscore "1.9.1" + web3-utils "1.2.11" web3-eth-abi@1.2.4: version "1.2.4" @@ -9842,14 +9857,22 @@ web3-eth-abi@1.2.4: underscore "1.9.1" web3-utils "1.2.4" -web3-eth-abi@1.2.9, web3-eth-abi@^1.0.0-beta.24: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.9.tgz#14bedd7e4be04fcca35b2ac84af1400574cd8280" - integrity sha512-3YwUYbh/DMfDbhMWEebAdjSd5bj3ZQieOjLzWFHU23CaLEqT34sUix1lba+hgUH/EN6A7bKAuKOhR3p0OvTn7Q== +web3-eth-accounts@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.11.tgz#a9e3044da442d31903a7ce035a86d8fa33f90520" + integrity sha512-6FwPqEpCfKIh3nSSGeo3uBm2iFSnFJDfwL3oS9pyegRBXNsGRVpgiW63yhNzL0796StsvjHWwQnQHsZNxWAkGw== dependencies: - "@ethersproject/abi" "5.0.0-beta.153" + crypto-browserify "3.12.0" + eth-lib "0.2.8" + ethereumjs-common "^1.3.2" + ethereumjs-tx "^2.1.1" + scrypt-js "^3.0.1" underscore "1.9.1" - web3-utils "1.2.9" + uuid "3.3.2" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-utils "1.2.11" web3-eth-accounts@1.2.4: version "1.2.4" @@ -9869,22 +9892,20 @@ web3-eth-accounts@1.2.4: web3-core-method "1.2.4" web3-utils "1.2.4" -web3-eth-accounts@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.9.tgz#7ec422df90fecb5243603ea49dc28726db7bdab6" - integrity sha512-jkbDCZoA1qv53mFcRHCinoCsgg8WH+M0YUO1awxmqWXRmCRws1wW0TsuSQ14UThih5Dxolgl+e+aGWxG58LMwg== +web3-eth-contract@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.11.tgz#917065902bc27ce89da9a1da26e62ef663663b90" + integrity sha512-MzYuI/Rq2o6gn7vCGcnQgco63isPNK5lMAan2E51AJLknjSLnOxwNY3gM8BcKoy4Z+v5Dv00a03Xuk78JowFow== dependencies: - crypto-browserify "3.12.0" - eth-lib "^0.2.8" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - scrypt-js "^3.0.1" + "@types/bn.js" "^4.11.5" underscore "1.9.1" - uuid "3.3.2" - web3-core "1.2.9" - web3-core-helpers "1.2.9" - web3-core-method "1.2.9" - web3-utils "1.2.9" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-promievent "1.2.11" + web3-core-subscriptions "1.2.11" + web3-eth-abi "1.2.11" + web3-utils "1.2.11" web3-eth-contract@1.2.4: version "1.2.4" @@ -9901,20 +9922,20 @@ web3-eth-contract@1.2.4: web3-eth-abi "1.2.4" web3-utils "1.2.4" -web3-eth-contract@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.9.tgz#713d9c6d502d8c8f22b696b7ffd8e254444e6bfd" - integrity sha512-PYMvJf7EG/HyssUZa+pXrc8IB06K/YFfWYyW4R7ed3sab+9wWUys1TlWxBCBuiBXOokSAyM6H6P6/cKEx8FT8Q== +web3-eth-ens@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.11.tgz#26d4d7f16d6cbcfff918e39832b939edc3162532" + integrity sha512-dbW7dXP6HqT1EAPvnniZVnmw6TmQEKF6/1KgAxbo8iBBYrVTMDGFQUUnZ+C4VETGrwwaqtX4L9d/FrQhZ6SUiA== dependencies: - "@types/bn.js" "^4.11.4" + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" underscore "1.9.1" - web3-core "1.2.9" - web3-core-helpers "1.2.9" - web3-core-method "1.2.9" - web3-core-promievent "1.2.9" - web3-core-subscriptions "1.2.9" - web3-eth-abi "1.2.9" - web3-utils "1.2.9" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-promievent "1.2.11" + web3-eth-abi "1.2.11" + web3-eth-contract "1.2.11" + web3-utils "1.2.11" web3-eth-ens@1.2.4: version "1.2.4" @@ -9930,20 +9951,13 @@ web3-eth-ens@1.2.4: web3-eth-contract "1.2.4" web3-utils "1.2.4" -web3-eth-ens@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.9.tgz#577b9358c036337833fb2bdc59c11be7f6f731b6" - integrity sha512-kG4+ZRgZ8I1WYyOBGI8QVRHfUSbbJjvJAGA1AF/NOW7JXQ+x7gBGeJw6taDWJhSshMoEKWcsgvsiuoG4870YxQ== +web3-eth-iban@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.11.tgz#f5f73298305bc7392e2f188bf38a7362b42144ef" + integrity sha512-ozuVlZ5jwFC2hJY4+fH9pIcuH1xP0HEFhtWsR69u9uDIANHLPQQtWYmdj7xQ3p2YT4bQLq/axKhZi7EZVetmxQ== dependencies: - content-hash "^2.5.2" - eth-ens-namehash "2.0.8" - underscore "1.9.1" - web3-core "1.2.9" - web3-core-helpers "1.2.9" - web3-core-promievent "1.2.9" - web3-eth-abi "1.2.9" - web3-eth-contract "1.2.9" - web3-utils "1.2.9" + bn.js "^4.11.9" + web3-utils "1.2.11" web3-eth-iban@1.2.4: version "1.2.4" @@ -9953,13 +9967,17 @@ web3-eth-iban@1.2.4: bn.js "4.11.8" web3-utils "1.2.4" -web3-eth-iban@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.9.tgz#4ebf3d8783f34d04c4740dc18938556466399f7a" - integrity sha512-RtdVvJE0pyg9dHLy0GzDiqgnLnssSzfz/JYguhC1wsj9+Gnq1M6Diy3NixACWUAp6ty/zafyOaZnNQ+JuH9TjQ== +web3-eth-personal@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.11.tgz#a38b3942a1d87a62070ce0622a941553c3d5aa70" + integrity sha512-42IzUtKq9iHZ8K9VN0vAI50iSU9tOA1V7XU2BhF/tb7We2iKBVdkley2fg26TxlOcKNEHm7o6HRtiiFsVK4Ifw== dependencies: - bn.js "4.11.8" - web3-utils "1.2.9" + "@types/node" "^12.12.6" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-net "1.2.11" + web3-utils "1.2.11" web3-eth-personal@1.2.4: version "1.2.4" @@ -9973,17 +9991,24 @@ web3-eth-personal@1.2.4: web3-net "1.2.4" web3-utils "1.2.4" -web3-eth-personal@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.9.tgz#9b95eb159b950b83cd8ae15873e1d57711b7a368" - integrity sha512-cFiNrktxZ1C/rIdJFzQTvFn3/0zcsR3a+Jf8Y3KxeQDHszQtosjLWptP7bsUmDwEh4hzh0Cy3KpOxlYBWB8bJQ== +web3-eth@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.11.tgz#4c81fcb6285b8caf544058fba3ae802968fdc793" + integrity sha512-REvxW1wJ58AgHPcXPJOL49d1K/dPmuw4LjPLBPStOVkQjzDTVmJEIsiLwn2YeuNDd4pfakBwT8L3bz1G1/wVsQ== dependencies: - "@types/node" "^12.6.1" - web3-core "1.2.9" - web3-core-helpers "1.2.9" - web3-core-method "1.2.9" - web3-net "1.2.9" - web3-utils "1.2.9" + underscore "1.9.1" + web3-core "1.2.11" + web3-core-helpers "1.2.11" + web3-core-method "1.2.11" + web3-core-subscriptions "1.2.11" + web3-eth-abi "1.2.11" + web3-eth-accounts "1.2.11" + web3-eth-contract "1.2.11" + web3-eth-ens "1.2.11" + web3-eth-iban "1.2.11" + web3-eth-personal "1.2.11" + web3-net "1.2.11" + web3-utils "1.2.11" web3-eth@1.2.4: version "1.2.4" @@ -10004,24 +10029,14 @@ web3-eth@1.2.4: web3-net "1.2.4" web3-utils "1.2.4" -web3-eth@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.9.tgz#e40e7b88baffc9b487193211c8b424dc944977b3" - integrity sha512-sIKO4iE9FEBa/CYUd6GdPd7GXt/wISqxUd8PlIld6+hvMJj02lgO7Z7p5T9mZIJcIZJGvZX81ogx8oJ9yif+Ag== +web3-net@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.11.tgz#eda68ef25e5cdb64c96c39085cdb74669aabbe1b" + integrity sha512-sjrSDj0pTfZouR5BSTItCuZ5K/oZPVdVciPQ6981PPPIwJJkCMeVjD7I4zO3qDPCnBjBSbWvVnLdwqUBPtHxyg== dependencies: - underscore "1.9.1" - web3-core "1.2.9" - web3-core-helpers "1.2.9" - web3-core-method "1.2.9" - web3-core-subscriptions "1.2.9" - web3-eth-abi "1.2.9" - web3-eth-accounts "1.2.9" - web3-eth-contract "1.2.9" - web3-eth-ens "1.2.9" - web3-eth-iban "1.2.9" - web3-eth-personal "1.2.9" - web3-net "1.2.9" - web3-utils "1.2.9" + web3-core "1.2.11" + web3-core-method "1.2.11" + web3-utils "1.2.11" web3-net@1.2.4: version "1.2.4" @@ -10032,15 +10047,6 @@ web3-net@1.2.4: web3-core-method "1.2.4" web3-utils "1.2.4" -web3-net@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.9.tgz#51d248ed1bc5c37713c4ac40c0073d9beacd87d3" - integrity sha512-d2mTn8jPlg+SI2hTj2b32Qan6DmtU9ap/IUlJTeQbZQSkTLf0u9suW8Vjwyr4poJYXTurdSshE7OZsPNn30/ZA== - dependencies: - web3-core "1.2.9" - web3-core-method "1.2.9" - web3-utils "1.2.9" - web3-provider-engine@14.0.6: version "14.0.6" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0" @@ -10122,6 +10128,14 @@ web3-provider-engine@^15.0.4: xhr "^2.2.0" xtend "^4.0.1" +web3-providers-http@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.11.tgz#1cd03442c61670572d40e4dcdf1faff8bd91e7c6" + integrity sha512-psh4hYGb1+ijWywfwpB2cvvOIMISlR44F/rJtYkRmQ5jMvG4FOCPlQJPiHQZo+2cc3HbktvvSJzIhkWQJdmvrA== + dependencies: + web3-core-helpers "1.2.11" + xhr2-cookies "1.1.0" + web3-providers-http@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.4.tgz#514fcad71ae77832c2c15574296282fbbc5f4a67" @@ -10130,13 +10144,14 @@ web3-providers-http@1.2.4: web3-core-helpers "1.2.4" xhr2-cookies "1.1.0" -web3-providers-http@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.9.tgz#e698aa5377e2019c24c5a1e6efa0f51018728934" - integrity sha512-F956tCIj60Ttr0UvEHWFIhx+be3He8msoPzyA44/kfzzYoMAsCFRn5cf0zQG6al0znE75g6HlWVSN6s3yAh51A== +web3-providers-ipc@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.11.tgz#d16d6c9be1be6e0b4f4536c4acc16b0f4f27ef21" + integrity sha512-yhc7Y/k8hBV/KlELxynWjJDzmgDEDjIjBzXK+e0rHBsYEhdCNdIH5Psa456c+l0qTEU2YzycF8VAjYpWfPnBpQ== dependencies: - web3-core-helpers "1.2.9" - xhr2-cookies "1.1.0" + oboe "2.1.4" + underscore "1.9.1" + web3-core-helpers "1.2.11" web3-providers-ipc@1.2.4: version "1.2.4" @@ -10147,14 +10162,15 @@ web3-providers-ipc@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-providers-ipc@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.9.tgz#6159eacfcd7ac31edc470d93ef10814fe874763b" - integrity sha512-NQ8QnBleoHA2qTJlqoWu7EJAD/FR5uimf7Ielzk4Z2z+m+6UAuJdJMSuQNj+Umhz9L/Ys6vpS1vHx9NizFl+aQ== +web3-providers-ws@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.11.tgz#a1dfd6d9778d840561d9ec13dd453046451a96bb" + integrity sha512-ZxnjIY1Er8Ty+cE4migzr43zA/+72AF1myzsLaU5eVgdsfV7Jqx7Dix1hbevNZDKFlSoEyq/3j/jYalh3So1Zg== dependencies: - oboe "2.1.4" + eventemitter3 "4.0.4" underscore "1.9.1" - web3-core-helpers "1.2.9" + web3-core-helpers "1.2.11" + websocket "^1.0.31" web3-providers-ws@1.2.4: version "1.2.4" @@ -10165,15 +10181,15 @@ web3-providers-ws@1.2.4: underscore "1.9.1" web3-core-helpers "1.2.4" -web3-providers-ws@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.9.tgz#22c2006655ec44b4ad2b41acae62741a6ae7a88c" - integrity sha512-6+UpvINeI//dglZoAKStUXqxDOXJy6Iitv2z3dbgInG4zb8tkYl/VBDL80UjUg3ZvzWG0g7EKY2nRPEpON2TFA== +web3-shh@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.11.tgz#f5d086f9621c9a47e98d438010385b5f059fd88f" + integrity sha512-B3OrO3oG1L+bv3E1sTwCx66injW1A8hhwpknDUbV+sw3fehFazA06z9SGXUefuFI1kVs4q2vRi0n4oCcI4dZDg== dependencies: - eventemitter3 "^4.0.0" - underscore "1.9.1" - web3-core-helpers "1.2.9" - websocket "^1.0.31" + web3-core "1.2.11" + web3-core-method "1.2.11" + web3-core-subscriptions "1.2.11" + web3-net "1.2.11" web3-shh@1.2.4: version "1.2.4" @@ -10185,23 +10201,13 @@ web3-shh@1.2.4: web3-core-subscriptions "1.2.4" web3-net "1.2.4" -web3-shh@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.9.tgz#c4ba70d6142cfd61341a50752d8cace9a0370911" - integrity sha512-PWa8b/EaxaMinFaxy6cV0i0EOi2M7a/ST+9k9nhyhCjVa2vzXuNoBNo2IUOmeZ0WP2UQB8ByJ2+p4htlJaDOjA== +web3-utils@1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.11.tgz#af1942aead3fb166ae851a985bed8ef2c2d95a82" + integrity sha512-3Tq09izhD+ThqHEaWYX4VOT7dNPdZiO+c/1QMA0s5X2lDFKK/xHJb7cyTRRVzN2LvlHbR7baS1tmQhSua51TcQ== dependencies: - web3-core "1.2.9" - web3-core-method "1.2.9" - web3-core-subscriptions "1.2.9" - web3-net "1.2.9" - -web3-utils@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.4.tgz#96832a39a66b05bf8862a5b0bdad2799d709d951" - integrity sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ== - dependencies: - bn.js "4.11.8" - eth-lib "0.2.7" + bn.js "^4.11.9" + eth-lib "0.2.8" ethereum-bloom-filters "^1.0.6" ethjs-unit "0.1.6" number-to-bn "1.7.0" @@ -10209,10 +10215,10 @@ web3-utils@1.2.4: underscore "1.9.1" utf8 "3.0.0" -web3-utils@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.9.tgz#abe11735221627da943971ef1a630868fb9c61f3" - integrity sha512-9hcpuis3n/LxFzEVjwnVgvJzTirS2S9/MiNAa7l4WOEoywY+BSNwnRX4MuHnjkh9NY25B6QOjuNG6FNnSjTw1w== +web3-utils@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.4.tgz#96832a39a66b05bf8862a5b0bdad2799d709d951" + integrity sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ== dependencies: bn.js "4.11.8" eth-lib "0.2.7" @@ -10237,18 +10243,18 @@ web3@1.2.4: web3-shh "1.2.4" web3-utils "1.2.4" -web3@^1.2.4: - version "1.2.9" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.9.tgz#cbcf1c0fba5e213a6dfb1f2c1f4b37062e4ce337" - integrity sha512-Mo5aBRm0JrcNpN/g4VOrDzudymfOnHRC3s2VarhYxRA8aWgF5rnhQ0ziySaugpic1gksbXPe105pUWyRqw8HUA== +web3@^1.2.11, web3@^1.2.4: + version "1.2.11" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.11.tgz#50f458b2e8b11aa37302071c170ed61cff332975" + integrity sha512-mjQ8HeU41G6hgOYm1pmeH0mRAeNKJGnJEUzDMoerkpw7QUQT4exVREgF1MYPvL/z6vAshOXei25LE/t/Bxl8yQ== dependencies: - web3-bzz "1.2.9" - web3-core "1.2.9" - web3-eth "1.2.9" - web3-eth-personal "1.2.9" - web3-net "1.2.9" - web3-shh "1.2.9" - web3-utils "1.2.9" + web3-bzz "1.2.11" + web3-core "1.2.11" + web3-eth "1.2.11" + web3-eth-personal "1.2.11" + web3-net "1.2.11" + web3-shh "1.2.11" + web3-utils "1.2.11" webidl-conversions@^4.0.2: version "4.0.2" @@ -10290,9 +10296,9 @@ whatwg-fetch@2.0.4: integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== whatwg-fetch@>=0.10.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + version "3.2.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" + integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" @@ -10524,7 +10530,7 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.1: +yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -10574,9 +10580,9 @@ yargs@^13.2.4, yargs@^13.3.0: yargs-parser "^13.1.2" yargs@^15.0.2: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0" decamelize "^1.2.0" @@ -10588,7 +10594,7 @@ yargs@^15.0.2: string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^18.1.1" + yargs-parser "^18.1.2" yargs@^7.1.0: version "7.1.1"