diff --git a/contracts/mocks/external/ZeroExMock.sol b/contracts/mocks/external/ZeroExMock.sol index 89df746a7..bcf9247a1 100644 --- a/contracts/mocks/external/ZeroExMock.sol +++ b/contracts/mocks/external/ZeroExMock.sol @@ -142,6 +142,42 @@ contract ZeroExMock { _transferTokens(); } + function sellEthForTokenToUniswapV3( + bytes memory /* encodedPath */, + uint256 /* minBuyAmount */, + address /* recipient */ + ) + external + payable + returns (uint256) + { + _transferTokens(); + } + + function sellTokenForEthToUniswapV3( + bytes memory /* encodedPath */, + uint256 /* sellAmount */, + uint256 /* minBuyAmount */, + address payable /* recipient */ + ) + external + returns (uint256) + { + _transferTokens(); + } + + function sellTokenForTokenToUniswapV3( + bytes memory /* encodedPath */, + uint256 /* sellAmount */, + uint256 /* minBuyAmount */, + address /* recipient */ + ) + external + returns (uint256) + { + _transferTokens(); + } + function _transferTokens() private { diff --git a/contracts/protocol/integration/exchange/ZeroExApiAdapter.sol b/contracts/protocol/integration/exchange/ZeroExApiAdapter.sol index cf9690c02..87dffac42 100644 --- a/contracts/protocol/integration/exchange/ZeroExApiAdapter.sol +++ b/contracts/protocol/integration/exchange/ZeroExApiAdapter.sol @@ -57,6 +57,14 @@ contract ZeroExApiAdapter { // ETH pseudo-token address used by 0x API. address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + // Byte size of Uniswap V3 encoded path addresses and pool fees + uint256 private constant UNISWAP_V3_PATH_ADDRESS_SIZE = 20; + uint256 private constant UNISWAP_V3_PATH_FEE_SIZE = 3; + // Minimum byte size of a single hop Uniswap V3 encoded path (token address + fee + token adress) + uint256 private constant UNISWAP_V3_SINGLE_HOP_PATH_SIZE = UNISWAP_V3_PATH_ADDRESS_SIZE + UNISWAP_V3_PATH_FEE_SIZE + UNISWAP_V3_PATH_ADDRESS_SIZE; + // Byte size of one hop in the Uniswap V3 encoded path (token address + fee) + uint256 private constant UNISWAP_V3_SINGLE_HOP_OFFSET_SIZE = UNISWAP_V3_PATH_ADDRESS_SIZE + UNISWAP_V3_PATH_FEE_SIZE; + // Address of the deployed ZeroEx contract. address public immutable zeroExAddress; @@ -152,6 +160,29 @@ contract ZeroExApiAdapter { inputToken = fillData.tokens[0]; outputToken = fillData.tokens[fillData.tokens.length - 1]; inputTokenAmount = fillData.sellAmount; + } else if (selector == 0x6af479b2) { + // sellTokenForTokenToUniswapV3() + bytes memory encodedPath; + (encodedPath, inputTokenAmount, minOutputTokenAmount, recipient) = + abi.decode(_data[4:], (bytes, uint256, uint256, address)); + supportsRecipient = true; + (inputToken, outputToken) = _decodeTokensFromUniswapV3EncodedPath(encodedPath); + } else if (selector == 0x803ba26d) { + // sellTokenForEthToUniswapV3() + bytes memory encodedPath; + (encodedPath, inputTokenAmount, minOutputTokenAmount, recipient) = + abi.decode(_data[4:], (bytes, uint256, uint256, address)); + supportsRecipient = true; + (inputToken, outputToken) = _decodeTokensFromUniswapV3EncodedPath(encodedPath); + } else if (selector == 0x3598d8ab) { + // sellEthForTokenToUniswapV3() + inputTokenAmount = _sourceQuantity; + bytes memory encodedPath; + (encodedPath, minOutputTokenAmount, recipient) = + abi.decode(_data[4:], (bytes, uint256, address)); + supportsRecipient = true; + (, outputToken) = _decodeTokensFromUniswapV3EncodedPath(encodedPath); + inputToken = ETH_ADDRESS; } else { revert("Unsupported 0xAPI function selector"); } @@ -170,4 +201,27 @@ contract ZeroExApiAdapter { _data ); } + + // Decode input and output tokens from an arbitrary length encoded Uniswap V3 path + function _decodeTokensFromUniswapV3EncodedPath(bytes memory encodedPath) + private + pure + returns ( + address inputToken, + address outputToken + ) + { + require(encodedPath.length >= UNISWAP_V3_SINGLE_HOP_PATH_SIZE, "UniswapV3 token path too short"); + + // UniswapV3 paths are packed encoded as (address(token0), uint24(fee), address(token1), [...]) + // We want the first and last token. + uint256 numHops = (encodedPath.length - UNISWAP_V3_PATH_ADDRESS_SIZE)/UNISWAP_V3_SINGLE_HOP_OFFSET_SIZE; + uint256 lastTokenOffset = numHops * UNISWAP_V3_SINGLE_HOP_OFFSET_SIZE; + assembly { + let p := add(encodedPath, 32) + inputToken := shr(96, mload(p)) + p := add(p, lastTokenOffset) + outputToken := shr(96, mload(p)) + } + } } diff --git a/package.json b/package.json index b9036ce9a..37d3424d5 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "license": "MIT", "homepage": "https://github.com/SetProtocol", "devDependencies": { + "@0x/utils": "^6.4.3", "@ethersproject/bignumber": "^5.0.12", "@ethersproject/providers": "^5.0.17", "@nomiclabs/hardhat-ethers": "^2.0.1", diff --git a/test/protocol/integration/exchange/zeroExApiAdapter.spec.ts b/test/protocol/integration/exchange/zeroExApiAdapter.spec.ts index 389347dcb..01ef46648 100644 --- a/test/protocol/integration/exchange/zeroExApiAdapter.spec.ts +++ b/test/protocol/integration/exchange/zeroExApiAdapter.spec.ts @@ -1,10 +1,12 @@ import "module-alias/register"; import { Account } from "@utils/test/types"; -import { ADDRESS_ZERO, ONE, ZERO, EMPTY_BYTES } from "@utils/constants"; +import { ADDRESS_ZERO, ONE, ZERO, EMPTY_BYTES, ETH_ADDRESS } from "@utils/constants"; import { ZeroExApiAdapter, ZeroExMock } from "@utils/contracts"; import DeployHelper from "@utils/deploys"; import { addSnapshotBeforeRestoreAfterEach, getAccounts, getWaffleExpect } from "@utils/test/index"; +import { hexUtils } from "@0x/utils"; +import { take } from "lodash"; const expect = getWaffleExpect(); @@ -13,6 +15,7 @@ describe("ZeroExApiAdapter", () => { const sourceToken = "0x6cf5f1d59fddae3a688210953a512b6aee6ea643"; const destToken = "0x5e5d0bea9d4a15db2d0837aff0435faba166190d"; const otherToken = "0xae9902bb655de1a67f334d8661b3ae6a96723d5b"; + const extraHopToken = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; const destination = "0x89b3515cad4f23c1deacea79fc12445cc21bd0e1"; const otherDestination = "0xdeb100c55cccfd6e39753f78c8b0c3bcbef86157"; const sourceQuantity = ONE; @@ -29,12 +32,7 @@ describe("ZeroExApiAdapter", () => { deployer = new DeployHelper(owner.wallet); // Mock OneInch exchange that allows for only fixed exchange amounts - zeroExMock = await deployer.mocks.deployZeroExMock( - ADDRESS_ZERO, - ADDRESS_ZERO, - ZERO, - ZERO, - ); + zeroExMock = await deployer.mocks.deployZeroExMock(ADDRESS_ZERO, ADDRESS_ZERO, ZERO, ZERO); zeroExApiAdapter = await deployer.adapters.deployZeroExApiAdapter(zeroExMock.address); }); @@ -417,10 +415,10 @@ describe("ZeroExApiAdapter", () => { it("validates data", async () => { const data = zeroExMock.interface.encodeFunctionData("batchFill", [ { - inputToken: sourceToken, - outputToken: destToken, - sellAmount: sourceQuantity, - calls: [], + inputToken: sourceToken, + outputToken: destToken, + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -440,10 +438,10 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong input token", async () => { const data = zeroExMock.interface.encodeFunctionData("batchFill", [ { - inputToken: otherToken, - outputToken: destToken, - sellAmount: sourceQuantity, - calls: [], + inputToken: otherToken, + outputToken: destToken, + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -461,10 +459,10 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong output token", async () => { const data = zeroExMock.interface.encodeFunctionData("batchFill", [ { - inputToken: sourceToken, - outputToken: otherToken, - sellAmount: sourceQuantity, - calls: [], + inputToken: sourceToken, + outputToken: otherToken, + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -482,10 +480,10 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong input token quantity", async () => { const data = zeroExMock.interface.encodeFunctionData("batchFill", [ { - inputToken: sourceToken, - outputToken: destToken, - sellAmount: otherQuantity, - calls: [], + inputToken: sourceToken, + outputToken: destToken, + sellAmount: otherQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -503,10 +501,10 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong output token quantity", async () => { const data = zeroExMock.interface.encodeFunctionData("batchFill", [ { - inputToken: sourceToken, - outputToken: destToken, - sellAmount: sourceQuantity, - calls: [], + inputToken: sourceToken, + outputToken: destToken, + sellAmount: sourceQuantity, + calls: [], }, otherQuantity, ]); @@ -526,9 +524,9 @@ describe("ZeroExApiAdapter", () => { it("validates data", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [sourceToken, destToken], - sellAmount: sourceQuantity, - calls: [], + tokens: [sourceToken, destToken], + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -548,9 +546,9 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong input token", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [otherToken, destToken], - sellAmount: sourceQuantity, - calls: [], + tokens: [otherToken, destToken], + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -568,9 +566,9 @@ describe("ZeroExApiAdapter", () => { it("rejects went path too short", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [sourceToken], - sellAmount: sourceQuantity, - calls: [], + tokens: [sourceToken], + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -588,9 +586,9 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong output token", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [sourceToken, otherToken], - sellAmount: sourceQuantity, - calls: [], + tokens: [sourceToken, otherToken], + sellAmount: sourceQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -608,9 +606,9 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong input token quantity", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [sourceToken, destToken], - sellAmount: otherQuantity, - calls: [], + tokens: [sourceToken, destToken], + sellAmount: otherQuantity, + calls: [], }, minDestinationQuantity, ]); @@ -628,9 +626,9 @@ describe("ZeroExApiAdapter", () => { it("rejects wrong output token quantity", async () => { const data = zeroExMock.interface.encodeFunctionData("multiHopFill", [ { - tokens: [sourceToken, destToken], - sellAmount: sourceQuantity, - calls: [], + tokens: [sourceToken, destToken], + sellAmount: sourceQuantity, + calls: [], }, otherQuantity, ]); @@ -646,4 +644,401 @@ describe("ZeroExApiAdapter", () => { }); }); }); + describe("Uniswap V3", () => { + const POOL_FEE = 1234; + function encodePath(tokens_: string[]): string { + const elems: string[] = []; + tokens_.forEach((t, i) => { + if (i) { + elems.push(hexUtils.leftPad(POOL_FEE, 3)); + } + elems.push(hexUtils.leftPad(t, 20)); + }); + return hexUtils.concat(...elems); + } + + describe("sellTokenForTokenToUniswapV3", () => { + const additionalHops = [otherToken, extraHopToken]; + for (let i = 0; i <= additionalHops.length; i++) { + const hops = take(additionalHops, i); + it(`validates data for ${i + 1} hops`, async () => { + const path = [sourceToken, ...hops, destToken]; + + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath(path), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const [target, value, _data] = await zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + expect(target).to.eq(zeroExMock.address); + expect(value).to.deep.eq(ZERO); + expect(_data).to.deep.eq(data); + }); + } + + it("rejects wrong input token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([otherToken, destToken]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched input token"); + }); + + it("rejects wrong output token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([sourceToken, otherToken]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token"); + }); + + it("rejects wrong input token quantity", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([sourceToken, destToken]), + otherQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched input token quantity"); + }); + + it("rejects wrong output token quantity", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([sourceToken, destToken]), + sourceQuantity, + otherQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token quantity"); + }); + + it("rejects invalid uniswap path", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([sourceToken]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("UniswapV3 token path too short"); + }); + + it("rejects wrong destination", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForTokenToUniswapV3", [ + encodePath([sourceToken, destToken]), + sourceQuantity, + minDestinationQuantity, + ADDRESS_ZERO, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched recipient"); + }); + }); + + describe("sellTokenForEthToUniswapV3", () => { + const additionalHops = [otherToken, extraHopToken]; + for (let i = 0; i <= additionalHops.length; i++) { + const hops = take(additionalHops, i); + it(`validates data for ${i + 1} hops`, async () => { + const path = [sourceToken, ...hops, ETH_ADDRESS]; + + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath(path), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const [target, value, _data] = await zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + expect(target).to.eq(zeroExMock.address); + expect(value).to.deep.eq(ZERO); + expect(_data).to.deep.eq(data); + }); + } + + it("rejects wrong input token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([otherToken, ETH_ADDRESS]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched input token"); + }); + + it("rejects wrong output token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([sourceToken, otherToken]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token"); + }); + + it("rejects wrong input token quantity", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([sourceToken, ETH_ADDRESS]), + otherQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched input token quantity"); + }); + + it("rejects wrong output token quantity", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([sourceToken, ETH_ADDRESS]), + sourceQuantity, + otherQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token quantity"); + }); + + it("rejects invalid uniswap path", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([sourceToken]), + sourceQuantity, + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("UniswapV3 token path too short"); + }); + + it("rejects wrong destination", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellTokenForEthToUniswapV3", [ + encodePath([sourceToken, ETH_ADDRESS]), + sourceQuantity, + minDestinationQuantity, + ADDRESS_ZERO, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + sourceToken, + ETH_ADDRESS, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched recipient"); + }); + }); + + describe("sellEthForTokenToUniswapV3", () => { + const additionalHops = [otherToken, extraHopToken]; + for (let i = 0; i <= additionalHops.length; i++) { + const hops = take(additionalHops, i); + it(`validates data for ${i + 1} hops`, async () => { + const path = [ETH_ADDRESS, ...hops, destToken]; + + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath(path), + minDestinationQuantity, + destination, + ]); + const [target, value, _data] = await zeroExApiAdapter.getTradeCalldata( + ETH_ADDRESS, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + expect(target).to.eq(zeroExMock.address); + expect(value).to.deep.eq(sourceQuantity); + expect(_data).to.deep.eq(data); + }); + } + + it("rejects wrong input token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath([ETH_ADDRESS, destToken]), + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + otherToken, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched input token"); + }); + + it("rejects wrong output token", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath([ETH_ADDRESS, otherToken]), + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + ETH_ADDRESS, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token"); + }); + + it("rejects wrong output token quantity", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath([ETH_ADDRESS, destToken]), + otherQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + ETH_ADDRESS, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched output token quantity"); + }); + + it("rejects invalid uniswap path", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath([ETH_ADDRESS]), + minDestinationQuantity, + destination, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + ETH_ADDRESS, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("UniswapV3 token path too short"); + }); + + it("rejects wrong destination", async () => { + const data = zeroExMock.interface.encodeFunctionData("sellEthForTokenToUniswapV3", [ + encodePath([ETH_ADDRESS, destToken]), + minDestinationQuantity, + ADDRESS_ZERO, + ]); + const tx = zeroExApiAdapter.getTradeCalldata( + ETH_ADDRESS, + destToken, + destination, + sourceQuantity, + minDestinationQuantity, + data, + ); + await expect(tx).to.be.revertedWith("Mismatched recipient"); + }); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 0502f6e19..c59f9b286 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,47 @@ # yarn lockfile v1 +"@0x/types@^3.3.3": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@0x/types/-/types-3.3.3.tgz#5df4ec381bba9f62441474b0e54309ddb2fccd17" + integrity sha512-pImq1ukZl+YN64ZKQqNPTOK8noNw4rHMksEEPzFGM26x7Utovf8Py+VFqbZrn1TMw/9WWGeZg8lPxs+LUYxayw== + dependencies: + "@types/node" "12.12.54" + bignumber.js "~9.0.0" + ethereum-types "^3.5.0" + +"@0x/typescript-typings@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-5.2.0.tgz#3eba353a27a83697f6f4f6d65ce14506687f52b7" + integrity sha512-8Gk0riQ37HTv3bNe/iWsb9mcJuRCMk/16PZTzA3IUauNQajcJgTD601pHbmBF57SJDpFhJIRg2Crcf6hePlzBA== + dependencies: + "@types/bn.js" "^4.11.0" + "@types/node" "12.12.54" + "@types/react" "*" + bignumber.js "~9.0.0" + ethereum-types "^3.5.0" + popper.js "1.14.3" + +"@0x/utils@^6.4.3": + version "6.4.3" + resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-6.4.3.tgz#d7c710a8d8b8f2ee3a4e324dc71423078d93ff37" + integrity sha512-HxZ22/fGGL56BhKAtPWe9mAurhzwH775uL6uEpHWt0TuOgNAdDZHz5GhiCh6mTz+5Bf6jmk0/VroMcWUB8qFnw== + dependencies: + "@0x/types" "^3.3.3" + "@0x/typescript-typings" "^5.2.0" + "@types/mocha" "^5.2.7" + "@types/node" "12.12.54" + abortcontroller-polyfill "^1.1.9" + bignumber.js "~9.0.0" + chalk "^2.3.0" + detect-node "2.0.3" + ethereum-types "^3.5.0" + ethereumjs-util "^7.0.10" + ethers "~4.0.4" + isomorphic-fetch "2.2.1" + js-sha3 "^0.7.0" + lodash "^4.17.11" + "@babel/code-frame@^7.0.0": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -740,7 +781,7 @@ resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-5.0.1.tgz#3c7750d0186b954c7f2d2f6acc8c3c7ba0c3412e" integrity sha512-wYxU3kp5zItbxKmeRYCEplS2MW7DzyBnxPGj+GJVHZEUZiK/nn5Ei1sUFgURDh+X051+zsGe28iud3oHjrYWQQ== -"@types/bn.js@*", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4", "@types/bn.js@^4.11.5": +"@types/bn.js@*", "@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== @@ -804,6 +845,11 @@ dependencies: "@types/node" "*" +"@types/mocha@^5.2.7": + version "5.2.7" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" + integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== + "@types/mocha@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" @@ -822,6 +868,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== +"@types/node@12.12.54": + version "12.12.54" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1" + integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w== + "@types/node@^10.12.18": version "10.17.50" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.50.tgz#7a20902af591282aa9176baefc37d4372131c32d" @@ -849,11 +900,25 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03" integrity sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA== +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + "@types/qs@^6.9.4": version "6.9.5" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== +"@types/react@*": + version "17.0.6" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.6.tgz#0ec564566302c562bf497d73219797a5e0297013" + integrity sha512-u/TtPoF/hrvb63LdukET6ncaplYsvCvmkceasx8oG84/ZCsoLxz9Z/raPBP4lTAiWW1Jb889Y9svHmv8R26dWw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@^0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -861,6 +926,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + "@types/secp256k1@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.1.tgz#fb3aa61a1848ad97d7425ff9dcba784549fca5a4" @@ -923,6 +993,11 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abortcontroller-polyfill@^1.1.9: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== + abstract-leveldown@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" @@ -1861,7 +1936,7 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.0: +bignumber.js@^9.0.0, bignumber.js@~9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== @@ -2702,6 +2777,11 @@ crypto-browserify@3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -2895,6 +2975,11 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-node@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + integrity sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc= + detect-port@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1" @@ -3489,6 +3574,14 @@ ethereum-cryptography@^0.1.2, ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" +ethereum-types@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.5.0.tgz#27393f0d86f55bb1dcbff8a6af55a39c1f751c0d" + integrity sha512-vTGJl45DxOK21w3rzlqV8KrfcdIJC+4ZqxFkjNf1aw2GBMXZy2MxiibUqBth2M823d98WgOuFpVHobOfa7ejDw== + dependencies: + "@types/node" "12.12.54" + bignumber.js "~9.0.0" + ethereum-waffle@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.2.1.tgz#9d6d6b93484c5e1b77dfdeb646c050ed877e836e" @@ -3719,7 +3812,7 @@ ethereumjs-wallet@0.6.5: utf8 "^3.0.0" uuid "^3.3.2" -ethers@^4.0.32: +ethers@^4.0.32, ethers@~4.0.4: version "4.0.48" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.48.tgz#330c65b8133e112b0613156e57e92d9009d8fbbe" integrity sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g== @@ -5401,6 +5494,14 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -5454,6 +5555,11 @@ 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.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" + integrity sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -6556,12 +6662,7 @@ node-fetch@2.1.2: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= -node-fetch@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - -node-fetch@~1.7.1: +node-fetch@^1.0.1, node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -6569,6 +6670,11 @@ node-fetch@~1.7.1: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-gyp-build@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" @@ -7110,6 +7216,11 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" +popper.js@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" + integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -9841,6 +9952,11 @@ whatwg-fetch@2.0.4: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== +whatwg-fetch@>=0.10.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"