Compare commits
7 Commits
@0x/contra
...
feat/as/sa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfed83dbbd | ||
|
|
064ac395e3 | ||
|
|
0284a0ebe3 | ||
|
|
4f981be613 | ||
|
|
493af4a501 | ||
|
|
0f207cccb6 | ||
|
|
2727dc3968 |
@@ -51,18 +51,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/tokens",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.7.2",
|
"@0x/abi-gen": "^5.7.3",
|
||||||
"@0x/contracts-gen": "^2.0.43",
|
"@0x/contracts-gen": "^2.0.44",
|
||||||
"@0x/contracts-test-utils": "^5.4.17",
|
"@0x/contracts-test-utils": "^5.4.17",
|
||||||
"@0x/contracts-utils": "^4.8.7",
|
"@0x/contracts-utils": "^4.8.7",
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "12.12.54",
|
"@types/node": "12.12.54",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"chai-as-promised": "^7.1.0",
|
"chai-as-promised": "^7.1.0",
|
||||||
"chai-bignumber": "^3.0.0",
|
"chai-bignumber": "^3.0.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"make-promises-safe": "^1.1.0",
|
"make-promises-safe": "^1.1.0",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.0",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"ethers": "~4.0.4"
|
"ethers": "~4.0.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/test-utils",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/test-utils",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"shx": "^0.2.2",
|
"shx": "^0.2.2",
|
||||||
@@ -42,20 +42,20 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^3.0.31",
|
"@0x/assert": "^3.0.32",
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/json-schemas": "^6.4.1",
|
"@0x/json-schemas": "^6.4.2",
|
||||||
"@0x/order-utils": "^10.4.28",
|
"@0x/order-utils": "^10.4.28",
|
||||||
"@0x/sol-coverage": "^4.0.42",
|
"@0x/sol-coverage": "^4.0.43",
|
||||||
"@0x/sol-profiler": "^4.1.32",
|
"@0x/sol-profiler": "^4.1.33",
|
||||||
"@0x/sol-trace": "^3.0.42",
|
"@0x/sol-trace": "^3.0.43",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"@types/bn.js": "^4.11.0",
|
"@types/bn.js": "^4.11.0",
|
||||||
"@types/js-combinatorics": "^0.5.29",
|
"@types/js-combinatorics": "^0.5.29",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"chai-bignumber": "^3.0.0",
|
"chai-bignumber": "^3.0.0",
|
||||||
"decimal.js": "^10.2.0",
|
"decimal.js": "^10.2.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethereumjs-util": "^7.0.10",
|
"ethereumjs-util": "^7.0.10",
|
||||||
"ethers": "~4.0.4",
|
"ethers": "~4.0.4",
|
||||||
"js-combinatorics": "^0.5.3",
|
"js-combinatorics": "^0.5.3",
|
||||||
|
|||||||
@@ -46,14 +46,14 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.7.2",
|
"@0x/abi-gen": "^5.7.3",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||||
"@0x/contracts-erc20": "^3.3.26",
|
"@0x/contracts-erc20": "^3.3.26",
|
||||||
"@0x/contracts-gen": "^2.0.43",
|
"@0x/contracts-gen": "^2.0.44",
|
||||||
"@0x/contracts-staking": "^2.0.45",
|
"@0x/contracts-staking": "^2.0.45",
|
||||||
"@0x/contracts-test-utils": "^5.4.17",
|
"@0x/contracts-test-utils": "^5.4.17",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@types/isomorphic-fetch": "^0.0.35",
|
"@types/isomorphic-fetch": "^0.0.35",
|
||||||
@@ -72,14 +72,14 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/protocol-utils": "^1.11.0",
|
"@0x/protocol-utils": "^1.11.0",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethereumjs-util": "^7.0.10"
|
"ethereumjs-util": "^7.0.10"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -50,15 +50,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/utils",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/utils",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.7.2",
|
"@0x/abi-gen": "^5.7.3",
|
||||||
"@0x/contracts-gen": "^2.0.43",
|
"@0x/contracts-gen": "^2.0.44",
|
||||||
"@0x/contracts-test-utils": "^5.4.17",
|
"@0x/contracts-test-utils": "^5.4.17",
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/order-utils": "^10.4.28",
|
"@0x/order-utils": "^10.4.28",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"@types/bn.js": "^4.11.0",
|
"@types/bn.js": "^4.11.0",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
@@ -79,11 +79,11 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"bn.js": "^4.11.8",
|
"bn.js": "^4.11.8",
|
||||||
"ethereum-types": "^3.6.0"
|
"ethereum-types": "^3.6.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -55,14 +55,14 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.7.2",
|
"@0x/abi-gen": "^5.7.3",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/contracts-erc20": "^3.3.26",
|
"@0x/contracts-erc20": "^3.3.26",
|
||||||
"@0x/contracts-gen": "^2.0.43",
|
"@0x/contracts-gen": "^2.0.44",
|
||||||
"@0x/contracts-test-utils": "^5.4.17",
|
"@0x/contracts-test-utils": "^5.4.17",
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/order-utils": "^10.4.28",
|
"@0x/order-utils": "^10.4.28",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@types/isomorphic-fetch": "^0.0.35",
|
"@types/isomorphic-fetch": "^0.0.35",
|
||||||
@@ -82,14 +82,14 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/protocol-utils": "^1.11.0",
|
"@0x/protocol-utils": "^1.11.0",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethereumjs-util": "^7.0.10",
|
"ethereumjs-util": "^7.0.10",
|
||||||
"ethers": "~4.0.4"
|
"ethers": "~4.0.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
"ignoreDependencyVersionsForPackage": "contract-wrappers"
|
"ignoreDependencyVersionsForPackage": "contract-wrappers"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/monorepo-scripts": "^3.2.1",
|
"@0x/monorepo-scripts": "^3.2.2",
|
||||||
"@0x-lerna-fork/lerna": "3.16.10",
|
"@0x-lerna-fork/lerna": "3.16.10",
|
||||||
"@0xproject/npm-cli-login": "^0.0.11",
|
"@0xproject/npm-cli-login": "^0.0.11",
|
||||||
"async-child-process": "^1.1.1",
|
"async-child-process": "^1.1.1",
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract ApproximateBuys {
|
|
||||||
|
|
||||||
/// @dev Information computing buy quotes for sources that do not have native
|
|
||||||
/// buy quote support.
|
|
||||||
struct ApproximateBuyQuoteOpts {
|
|
||||||
// Arbitrary maker token data to pass to `getSellQuoteCallback`.
|
|
||||||
bytes makerTokenData;
|
|
||||||
// Arbitrary taker token data to pass to `getSellQuoteCallback`.
|
|
||||||
bytes takerTokenData;
|
|
||||||
// Callback to retrieve a sell quote.
|
|
||||||
function (bytes memory, bytes memory, uint256)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint256) getSellQuoteCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
|
|
||||||
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
|
|
||||||
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
|
|
||||||
/// @dev Maximum iterations to perform when approximating a buy quote.
|
|
||||||
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5;
|
|
||||||
|
|
||||||
function _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts memory opts,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
|
||||||
if (makerTokenAmounts.length == 0) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 sellAmount = opts.getSellQuoteCallback(
|
|
||||||
opts.makerTokenData,
|
|
||||||
opts.takerTokenData,
|
|
||||||
makerTokenAmounts[0]
|
|
||||||
);
|
|
||||||
if (sellAmount == 0) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 buyAmount = opts.getSellQuoteCallback(
|
|
||||||
opts.takerTokenData,
|
|
||||||
opts.makerTokenData,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
if (buyAmount == 0) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
|
||||||
uint256 eps = 0;
|
|
||||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
|
||||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
|
||||||
sellAmount = _safeGetPartialAmountCeil(
|
|
||||||
makerTokenAmounts[i],
|
|
||||||
buyAmount,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
if (sellAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sellAmount = _safeGetPartialAmountCeil(
|
|
||||||
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
|
|
||||||
ONE_HUNDED_PERCENT_BPS,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
if (sellAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
uint256 _buyAmount = opts.getSellQuoteCallback(
|
|
||||||
opts.takerTokenData,
|
|
||||||
opts.makerTokenData,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
if (_buyAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// We re-use buyAmount next iteration, only assign if it is
|
|
||||||
// non zero
|
|
||||||
buyAmount = _buyAmount;
|
|
||||||
// If we've reached our goal, exit early
|
|
||||||
if (buyAmount >= makerTokenAmounts[i]) {
|
|
||||||
eps =
|
|
||||||
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
|
||||||
makerTokenAmounts[i];
|
|
||||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eps == 0 || eps > APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
|
||||||
// if we hit a max iteration limit
|
|
||||||
// We scale the sell amount to get the approximate target
|
|
||||||
takerTokenAmounts[i] = _safeGetPartialAmountCeil(
|
|
||||||
makerTokenAmounts[i],
|
|
||||||
buyAmount,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _safeGetPartialAmountCeil(
|
|
||||||
uint256 numerator,
|
|
||||||
uint256 denominator,
|
|
||||||
uint256 target
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint256 partialAmount)
|
|
||||||
{
|
|
||||||
if (numerator == 0 || target == 0 || denominator == 0) return 0;
|
|
||||||
uint256 c = numerator * target;
|
|
||||||
if (c / numerator != target) return 0;
|
|
||||||
return (c + (denominator - 1)) / denominator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IBalancer.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract BalancerSampler {
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Balancer calls.
|
|
||||||
uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k
|
|
||||||
|
|
||||||
// Balancer math constants
|
|
||||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol
|
|
||||||
uint256 constant private BONE = 10 ** 18;
|
|
||||||
uint256 constant private MAX_IN_RATIO = BONE / 2;
|
|
||||||
uint256 constant private MAX_OUT_RATIO = (BONE / 3) + 1 wei;
|
|
||||||
|
|
||||||
struct BalancerState {
|
|
||||||
uint256 takerTokenBalance;
|
|
||||||
uint256 makerTokenBalance;
|
|
||||||
uint256 takerTokenWeight;
|
|
||||||
uint256 makerTokenWeight;
|
|
||||||
uint256 swapFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Balancer.
|
|
||||||
/// @param poolAddress Address of the Balancer pool to query.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromBalancer(
|
|
||||||
address poolAddress,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
IBalancer pool = IBalancer(poolAddress);
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
BalancerState memory poolState;
|
|
||||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
|
||||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
|
||||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
|
||||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
|
||||||
poolState.swapFee = pool.getSwapFee();
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
// Handles this revert scenario:
|
|
||||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
|
||||||
if (takerTokenAmounts[i] > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
pool.calcOutGivenIn
|
|
||||||
{gas: BALANCER_CALL_GAS}
|
|
||||||
(
|
|
||||||
poolState.takerTokenBalance,
|
|
||||||
poolState.takerTokenWeight,
|
|
||||||
poolState.makerTokenBalance,
|
|
||||||
poolState.makerTokenWeight,
|
|
||||||
takerTokenAmounts[i],
|
|
||||||
poolState.swapFee
|
|
||||||
)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Balancer.
|
|
||||||
/// @param poolAddress Address of the Balancer pool to query.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromBalancer(
|
|
||||||
address poolAddress,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
IBalancer pool = IBalancer(poolAddress);
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
BalancerState memory poolState;
|
|
||||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
|
||||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
|
||||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
|
||||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
|
||||||
poolState.swapFee = pool.getSwapFee();
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
// Handles this revert scenario:
|
|
||||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L505
|
|
||||||
if (makerTokenAmounts[i] > _bmul(poolState.makerTokenBalance, MAX_OUT_RATIO)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
pool.calcInGivenOut
|
|
||||||
{gas: BALANCER_CALL_GAS}
|
|
||||||
(
|
|
||||||
poolState.takerTokenBalance,
|
|
||||||
poolState.takerTokenWeight,
|
|
||||||
poolState.makerTokenBalance,
|
|
||||||
poolState.makerTokenWeight,
|
|
||||||
makerTokenAmounts[i],
|
|
||||||
poolState.swapFee
|
|
||||||
)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
// Handles this revert scenario:
|
|
||||||
// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol#L443
|
|
||||||
if (amount > _bmul(poolState.takerTokenBalance, MAX_IN_RATIO)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
takerTokenAmounts[i] = amount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (takerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Hacked version of Balancer's `bmul` function, returning 0 instead
|
|
||||||
/// of reverting.
|
|
||||||
/// https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol#L63-L73
|
|
||||||
/// @param a The first operand.
|
|
||||||
/// @param b The second operand.
|
|
||||||
/// @param c The result of the multiplication, or 0 if `bmul` would've reverted.
|
|
||||||
function _bmul(uint256 a, uint256 b)
|
|
||||||
private
|
|
||||||
pure
|
|
||||||
returns (uint256 c)
|
|
||||||
{
|
|
||||||
uint c0 = a * b;
|
|
||||||
if (a != 0 && c0 / a != b) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint c1 = c0 + (BONE / 2);
|
|
||||||
if (c1 < c0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint c2 = c1 / BONE;
|
|
||||||
return c2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
/// @dev Minimal Balancer V2 Vault interface
|
|
||||||
/// for documentation refer to https://github.com/balancer-labs/balancer-core-v2/blob/master/contracts/vault/interfaces/IVault.sol
|
|
||||||
interface IBalancerV2Vault {
|
|
||||||
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
|
||||||
|
|
||||||
struct BatchSwapStep {
|
|
||||||
bytes32 poolId;
|
|
||||||
uint256 assetInIndex;
|
|
||||||
uint256 assetOutIndex;
|
|
||||||
uint256 amount;
|
|
||||||
bytes userData;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FundManagement {
|
|
||||||
address sender;
|
|
||||||
bool fromInternalBalance;
|
|
||||||
address payable recipient;
|
|
||||||
bool toInternalBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryBatchSwap(
|
|
||||||
SwapKind kind,
|
|
||||||
BatchSwapStep[] calldata swaps,
|
|
||||||
IAsset[] calldata assets,
|
|
||||||
FundManagement calldata funds
|
|
||||||
) external returns (int256[] memory assetDeltas);
|
|
||||||
}
|
|
||||||
interface IAsset {
|
|
||||||
// solhint-disable-previous-line no-empty-blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
contract BalancerV2Sampler is SamplerUtils {
|
|
||||||
|
|
||||||
struct BalancerV2PoolInfo {
|
|
||||||
bytes32 poolId;
|
|
||||||
address vault;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Balancer V2.
|
|
||||||
/// @param poolInfo Struct with pool related data
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromBalancerV2(
|
|
||||||
BalancerV2PoolInfo memory poolInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
|
||||||
IAsset[] memory swapAssets = new IAsset[](2);
|
|
||||||
swapAssets[0] = IAsset(takerToken);
|
|
||||||
swapAssets[1] = IAsset(makerToken);
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
|
||||||
_createSwapFunds();
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
|
||||||
_createSwapSteps(poolInfo, takerTokenAmounts[i]);
|
|
||||||
|
|
||||||
try
|
|
||||||
// For sells we specify the takerToken which is what the vault will receive from the trade
|
|
||||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_IN, swapSteps, swapAssets, swapFunds)
|
|
||||||
// amounts represent pool balance deltas from the swap (incoming balance, outgoing balance)
|
|
||||||
returns (int256[] memory amounts) {
|
|
||||||
// Outgoing balance is negative so we need to flip the sign
|
|
||||||
int256 amountOutFromPool = amounts[1] * -1;
|
|
||||||
if (amountOutFromPool <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
makerTokenAmounts[i] = uint256(amountOutFromPool);
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Balancer V2.
|
|
||||||
/// @param poolInfo Struct with pool related data
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromBalancerV2(
|
|
||||||
BalancerV2PoolInfo memory poolInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
IBalancerV2Vault vault = IBalancerV2Vault(poolInfo.vault);
|
|
||||||
IAsset[] memory swapAssets = new IAsset[](2);
|
|
||||||
swapAssets[0] = IAsset(takerToken);
|
|
||||||
swapAssets[1] = IAsset(makerToken);
|
|
||||||
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
IBalancerV2Vault.FundManagement memory swapFunds =
|
|
||||||
_createSwapFunds();
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
|
||||||
_createSwapSteps(poolInfo, makerTokenAmounts[i]);
|
|
||||||
|
|
||||||
try
|
|
||||||
// For buys we specify the makerToken which is what taker will receive from the trade
|
|
||||||
vault.queryBatchSwap(IBalancerV2Vault.SwapKind.GIVEN_OUT, swapSteps, swapAssets, swapFunds)
|
|
||||||
returns (int256[] memory amounts) {
|
|
||||||
int256 amountIntoPool = amounts[0];
|
|
||||||
if (amountIntoPool <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
takerTokenAmounts[i] = uint256(amountIntoPool);
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createSwapSteps(
|
|
||||||
BalancerV2PoolInfo memory poolInfo,
|
|
||||||
uint256 amount
|
|
||||||
) private pure returns (IBalancerV2Vault.BatchSwapStep[] memory) {
|
|
||||||
IBalancerV2Vault.BatchSwapStep[] memory swapSteps =
|
|
||||||
new IBalancerV2Vault.BatchSwapStep[](1);
|
|
||||||
swapSteps[0] = IBalancerV2Vault.BatchSwapStep({
|
|
||||||
poolId: poolInfo.poolId,
|
|
||||||
assetInIndex: 0,
|
|
||||||
assetOutIndex: 1,
|
|
||||||
amount: amount,
|
|
||||||
userData: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
return swapSteps;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createSwapFunds()
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (IBalancerV2Vault.FundManagement memory)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
IBalancerV2Vault.FundManagement({
|
|
||||||
sender: address(this),
|
|
||||||
fromInternalBalance: false,
|
|
||||||
recipient: payable(address(this)),
|
|
||||||
toInternalBalance: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IBancor.sol";
|
|
||||||
|
|
||||||
contract CompilerHack {}
|
|
||||||
|
|
||||||
contract BancorSampler is CompilerHack {
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Bancor calls.
|
|
||||||
uint256 constant private BANCOR_CALL_GAS = 300e3; // 300k
|
|
||||||
|
|
||||||
struct BancorSamplerOpts {
|
|
||||||
IBancorRegistry registry;
|
|
||||||
address[][] paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Bancor.
|
|
||||||
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return bancorNetwork the Bancor Network address
|
|
||||||
/// @return path the selected conversion path from bancor
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromBancor(
|
|
||||||
BancorSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (address bancorNetwork, address[] memory path, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
if (opts.paths.length == 0) {
|
|
||||||
return (bancorNetwork, path, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
(bancorNetwork, path) = _findBestPath(opts, takerToken, makerToken, takerTokenAmounts);
|
|
||||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
|
||||||
try
|
|
||||||
IBancorNetwork(bancorNetwork)
|
|
||||||
.rateByPath
|
|
||||||
{gas: BANCOR_CALL_GAS}
|
|
||||||
(path, takerTokenAmounts[i])
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (bancorNetwork, path, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Bancor. Unimplemented
|
|
||||||
/// @param opts BancorSamplerOpts The Bancor registry contract address and paths
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return bancorNetwork the Bancor Network address
|
|
||||||
/// @return path the selected conversion path from bancor
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromBancor(
|
|
||||||
BancorSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (address bancorNetwork, address[] memory path, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
function _findBestPath(
|
|
||||||
BancorSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (address bancorNetwork, address[] memory path)
|
|
||||||
{
|
|
||||||
bancorNetwork = opts.registry.getAddress(opts.registry.BANCOR_NETWORK());
|
|
||||||
if (opts.paths.length == 0) {
|
|
||||||
return (bancorNetwork, path);
|
|
||||||
}
|
|
||||||
uint256 maxBoughtAmount = 0;
|
|
||||||
// Find the best path by selling the largest taker amount
|
|
||||||
for (uint256 i = 0; i < opts.paths.length; i++) {
|
|
||||||
if (opts.paths[i].length < 2) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
IBancorNetwork(bancorNetwork)
|
|
||||||
.rateByPath
|
|
||||||
{gas: BANCOR_CALL_GAS}
|
|
||||||
(opts.paths[i], takerTokenAmounts[takerTokenAmounts.length-1])
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
if (amount > maxBoughtAmount) {
|
|
||||||
maxBoughtAmount = amount;
|
|
||||||
path = opts.paths[i];
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
|
||||||
|
|
||||||
// Minimal CToken interface
|
|
||||||
interface ICToken {
|
|
||||||
function mint(uint mintAmount) external returns (uint);
|
|
||||||
function redeem(uint redeemTokens) external returns (uint);
|
|
||||||
function redeemUnderlying(uint redeemAmount) external returns (uint);
|
|
||||||
function exchangeRateStored() external view returns (uint);
|
|
||||||
function decimals() external view returns (uint8);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract CompoundSampler is SamplerUtils {
|
|
||||||
uint256 constant private EXCHANGE_RATE_SCALE = 1e10;
|
|
||||||
|
|
||||||
function sampleSellsFromCompound(
|
|
||||||
ICToken cToken,
|
|
||||||
IERC20TokenV06 takerToken,
|
|
||||||
IERC20TokenV06 makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
|
||||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
|
||||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
|
||||||
|
|
||||||
if (address(makerToken) == address(cToken)) {
|
|
||||||
// mint
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals) / exchangeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (address(takerToken) == address(cToken)) {
|
|
||||||
// redeem
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
makerTokenAmounts[i] = (takerTokenAmounts[i] * exchangeRate) / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleBuysFromCompound(
|
|
||||||
ICToken cToken,
|
|
||||||
IERC20TokenV06 takerToken,
|
|
||||||
IERC20TokenV06 makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
// Exchange rate is scaled by 1 * 10^(18 - 8 + Underlying Token Decimals
|
|
||||||
uint256 exchangeRate = cToken.exchangeRateStored();
|
|
||||||
uint256 cTokenDecimals = uint256(cToken.decimals());
|
|
||||||
|
|
||||||
if (address(makerToken) == address(cToken)) {
|
|
||||||
// mint
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
takerTokenAmounts[i] = makerTokenAmounts[i] * exchangeRate / (EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals);
|
|
||||||
}
|
|
||||||
} else if (address(takerToken) == address(cToken)) {
|
|
||||||
// redeem
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
takerTokenAmounts[i] = (makerTokenAmounts[i] * EXCHANGE_RATE_SCALE * 10 ** cTokenDecimals)/exchangeRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/ICurve.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract CurveSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Information for sampling from curve sources.
|
|
||||||
struct CurveInfo {
|
|
||||||
address poolAddress;
|
|
||||||
bytes4 sellQuoteFunctionSelector;
|
|
||||||
bytes4 buyQuoteFunctionSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Curve calls. Some Curves have multiple tokens
|
|
||||||
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
|
|
||||||
uint256 constant private CURVE_CALL_GAS = 2000e3; // Was 600k for Curve but SnowSwap is using 1500k+
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Curve.
|
|
||||||
/// @param curveInfo Curve information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromCurve(
|
|
||||||
CurveInfo memory curveInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
(bool didSucceed, bytes memory resultData) =
|
|
||||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
curveInfo.sellQuoteFunctionSelector,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
));
|
|
||||||
uint256 buyAmount = 0;
|
|
||||||
if (didSucceed) {
|
|
||||||
buyAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Curve.
|
|
||||||
/// @param curveInfo Curve information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromCurve(
|
|
||||||
CurveInfo memory curveInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) {
|
|
||||||
// Buys not supported on this curve, so approximate it.
|
|
||||||
return _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(toTokenIdx, curveInfo),
|
|
||||||
takerTokenData: abi.encode(fromTokenIdx, curveInfo),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
(bool didSucceed, bytes memory resultData) =
|
|
||||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
curveInfo.buyQuoteFunctionSelector,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
makerTokenAmounts[i]
|
|
||||||
));
|
|
||||||
uint256 sellAmount = 0;
|
|
||||||
if (didSucceed) {
|
|
||||||
sellAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
takerTokenAmounts[i] = sellAmount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (takerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromCurve(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(int128 takerTokenIdx, CurveInfo memory curveInfo) =
|
|
||||||
abi.decode(takerTokenData, (int128, CurveInfo));
|
|
||||||
(int128 makerTokenIdx) =
|
|
||||||
abi.decode(makerTokenData, (int128));
|
|
||||||
(bool success, bytes memory resultData) =
|
|
||||||
address(this).staticcall(abi.encodeWithSelector(
|
|
||||||
this.sampleSellsFromCurve.selector,
|
|
||||||
curveInfo,
|
|
||||||
takerTokenIdx,
|
|
||||||
makerTokenIdx,
|
|
||||||
_toSingleValueArray(sellAmount)
|
|
||||||
));
|
|
||||||
if (!success) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// solhint-disable-next-line indent
|
|
||||||
return abi.decode(resultData, (uint256[]))[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
interface IDODOZoo {
|
|
||||||
function getDODO(address baseToken, address quoteToken) external view returns (address);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDODOHelper {
|
|
||||||
function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDODO {
|
|
||||||
function querySellBaseToken(uint256 amount) external view returns (uint256);
|
|
||||||
function _TRADE_ALLOWED_() external view returns (bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract DODOSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
|
|
||||||
/// @dev Gas limit for DODO calls.
|
|
||||||
uint256 constant private DODO_CALL_GAS = 300e3; // 300k
|
|
||||||
struct DODOSamplerOpts {
|
|
||||||
address registry;
|
|
||||||
address helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from DODO.
|
|
||||||
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return sellBase whether the bridge needs to sell the base token
|
|
||||||
/// @return pool the DODO pool address
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromDODO(
|
|
||||||
DODOSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
|
||||||
address baseToken;
|
|
||||||
// If pool exists we have the correct order of Base/Quote
|
|
||||||
if (pool != address(0)) {
|
|
||||||
baseToken = takerToken;
|
|
||||||
sellBase = true;
|
|
||||||
} else {
|
|
||||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
|
||||||
// No pool either direction
|
|
||||||
if (address(pool) == address(0)) {
|
|
||||||
return (sellBase, pool, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
baseToken = makerToken;
|
|
||||||
sellBase = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DODO Pool has been disabled
|
|
||||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
|
||||||
return (sellBase, pool, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODO(
|
|
||||||
abi.encode(takerToken, pool, baseToken, opts.helper), // taker token data
|
|
||||||
abi.encode(makerToken, pool, baseToken, opts.helper), // maker token data
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from DODO.
|
|
||||||
/// @param opts DODOSamplerOpts DODO Registry and helper addresses
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
|
||||||
/// @return sellBase whether the bridge needs to sell the base token
|
|
||||||
/// @return pool the DODO pool address
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromDODO(
|
|
||||||
DODOSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
// Pool is BASE/QUOTE
|
|
||||||
// Look up the pool from the taker/maker combination
|
|
||||||
pool = IDODOZoo(opts.registry).getDODO(takerToken, makerToken);
|
|
||||||
address baseToken;
|
|
||||||
// If pool exists we have the correct order of Base/Quote
|
|
||||||
if (pool != address(0)) {
|
|
||||||
baseToken = takerToken;
|
|
||||||
sellBase = true;
|
|
||||||
} else {
|
|
||||||
// Look up the pool from the maker/taker combination
|
|
||||||
pool = IDODOZoo(opts.registry).getDODO(makerToken, takerToken);
|
|
||||||
// No pool either direction
|
|
||||||
if (address(pool) == address(0)) {
|
|
||||||
return (sellBase, pool, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
baseToken = makerToken;
|
|
||||||
sellBase = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DODO Pool has been disabled
|
|
||||||
if (!IDODO(pool)._TRADE_ALLOWED_()) {
|
|
||||||
return (sellBase, pool, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
takerTokenAmounts = _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, pool, baseToken, opts.helper),
|
|
||||||
takerTokenData: abi.encode(takerToken, pool, baseToken, opts.helper),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODO
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromDODO(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory /* makerTokenData */,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
(address takerToken, address pool, address baseToken, address helper) = abi.decode(
|
|
||||||
takerTokenData,
|
|
||||||
(address, address, address, address)
|
|
||||||
);
|
|
||||||
|
|
||||||
// We will get called to sell both the taker token and also to sell the maker token
|
|
||||||
if (takerToken == baseToken) {
|
|
||||||
// If base token then use the original query on the pool
|
|
||||||
try
|
|
||||||
IDODO(pool).querySellBaseToken
|
|
||||||
{gas: DODO_CALL_GAS}
|
|
||||||
(sellAmount)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If quote token then use helper, this is less accurate
|
|
||||||
try
|
|
||||||
IDODOHelper(helper).querySellQuoteToken
|
|
||||||
{gas: DODO_CALL_GAS}
|
|
||||||
(pool, sellAmount)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
interface IDODOV2Registry {
|
|
||||||
function getDODOPool(address baseToken, address quoteToken)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (address[] memory machines);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDODOV2Pool {
|
|
||||||
function querySellBase(address trader, uint256 payBaseAmount)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 receiveQuoteAmount, uint256 mtFee);
|
|
||||||
|
|
||||||
function querySellQuote(address trader, uint256 payQuoteAmount)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 receiveBaseAmount, uint256 mtFee);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract DODOV2Sampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
|
|
||||||
/// @dev Gas limit for DODO V2 calls.
|
|
||||||
uint256 constant private DODO_V2_CALL_GAS = 300e3; // 300k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from DODO V2.
|
|
||||||
/// @param registry Address of the registry to look up.
|
|
||||||
/// @param offset offset index for the pool in the registry.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return sellBase whether the bridge needs to sell the base token
|
|
||||||
/// @return pool the DODO pool address
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromDODOV2(
|
|
||||||
address registry,
|
|
||||||
uint256 offset,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool sellBase, address pool, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
|
||||||
if (pool == address(0)) {
|
|
||||||
return (sellBase, pool, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 buyAmount = _sampleSellForApproximateBuyFromDODOV2(
|
|
||||||
abi.encode(takerToken, pool, sellBase), // taker token data
|
|
||||||
abi.encode(makerToken, pool, sellBase), // maker token data
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from DODO.
|
|
||||||
/// @param registry Address of the registry to look up.
|
|
||||||
/// @param offset offset index for the pool in the registry.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
|
||||||
/// @return sellBase whether the bridge needs to sell the base token
|
|
||||||
/// @return pool the DODO pool address
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromDODOV2(
|
|
||||||
address registry,
|
|
||||||
uint256 offset,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool sellBase, address pool, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
(pool, sellBase) = _getNextDODOV2Pool(registry, offset, takerToken, makerToken);
|
|
||||||
if (pool == address(0)) {
|
|
||||||
return (sellBase, pool, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
takerTokenAmounts = _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, pool, !sellBase),
|
|
||||||
takerTokenData: abi.encode(takerToken, pool, sellBase),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromDODOV2
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromDODOV2(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory /* makerTokenData */,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
(address takerToken, address pool, bool sellBase) = abi.decode(
|
|
||||||
takerTokenData,
|
|
||||||
(address, address, bool)
|
|
||||||
);
|
|
||||||
|
|
||||||
// We will get called to sell both the taker token and also to sell the maker token
|
|
||||||
// since we use approximate buy for sell and buy functions
|
|
||||||
if (sellBase) {
|
|
||||||
try
|
|
||||||
IDODOV2Pool(pool).querySellBase
|
|
||||||
{ gas: DODO_V2_CALL_GAS }
|
|
||||||
(address(0), sellAmount)
|
|
||||||
returns (uint256 amount, uint256)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try
|
|
||||||
IDODOV2Pool(pool).querySellQuote
|
|
||||||
{ gas: DODO_V2_CALL_GAS }
|
|
||||||
(address(0), sellAmount)
|
|
||||||
returns (uint256 amount, uint256)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getNextDODOV2Pool(
|
|
||||||
address registry,
|
|
||||||
uint256 offset,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (address machine, bool sellBase)
|
|
||||||
{
|
|
||||||
// Query in base -> quote direction, if a pool is found then we are selling the base
|
|
||||||
address[] memory machines = IDODOV2Registry(registry).getDODOPool(takerToken, makerToken);
|
|
||||||
sellBase = true;
|
|
||||||
if (machines.length == 0) {
|
|
||||||
// Query in quote -> base direction, if a pool is found then we are selling the quote
|
|
||||||
machines = IDODOV2Registry(registry).getDODOPool(makerToken, takerToken);
|
|
||||||
sellBase = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset >= machines.length) {
|
|
||||||
return (address(0), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
machine = machines[offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./BalancerSampler.sol";
|
|
||||||
import "./BalancerV2Sampler.sol";
|
|
||||||
import "./BancorSampler.sol";
|
|
||||||
import "./CompoundSampler.sol";
|
|
||||||
import "./CurveSampler.sol";
|
|
||||||
import "./DODOSampler.sol";
|
|
||||||
import "./DODOV2Sampler.sol";
|
|
||||||
import "./KyberSampler.sol";
|
|
||||||
import "./KyberDmmSampler.sol";
|
|
||||||
import "./LidoSampler.sol";
|
|
||||||
import "./LiquidityProviderSampler.sol";
|
|
||||||
import "./MakerPSMSampler.sol";
|
|
||||||
import "./MultiBridgeSampler.sol";
|
|
||||||
import "./MStableSampler.sol";
|
|
||||||
import "./MooniswapSampler.sol";
|
|
||||||
import "./NativeOrderSampler.sol";
|
|
||||||
import "./ShellSampler.sol";
|
|
||||||
import "./SmoothySampler.sol";
|
|
||||||
import "./TwoHopSampler.sol";
|
|
||||||
import "./UniswapSampler.sol";
|
|
||||||
import "./UniswapV2Sampler.sol";
|
|
||||||
import "./UniswapV3Sampler.sol";
|
|
||||||
import "./UtilitySampler.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract ERC20BridgeSampler is
|
|
||||||
BalancerSampler,
|
|
||||||
BalancerV2Sampler,
|
|
||||||
BancorSampler,
|
|
||||||
CompoundSampler,
|
|
||||||
CurveSampler,
|
|
||||||
DODOSampler,
|
|
||||||
DODOV2Sampler,
|
|
||||||
KyberSampler,
|
|
||||||
KyberDmmSampler,
|
|
||||||
LidoSampler,
|
|
||||||
LiquidityProviderSampler,
|
|
||||||
MakerPSMSampler,
|
|
||||||
MStableSampler,
|
|
||||||
MooniswapSampler,
|
|
||||||
MultiBridgeSampler,
|
|
||||||
NativeOrderSampler,
|
|
||||||
ShellSampler,
|
|
||||||
SmoothySampler,
|
|
||||||
TwoHopSampler,
|
|
||||||
UniswapSampler,
|
|
||||||
UniswapV2Sampler,
|
|
||||||
UniswapV3Sampler,
|
|
||||||
UtilitySampler
|
|
||||||
{
|
|
||||||
|
|
||||||
struct CallResults {
|
|
||||||
bytes data;
|
|
||||||
bool success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
|
||||||
/// @param callDatas ABI-encoded call data for each function call.
|
|
||||||
/// @return callResults ABI-encoded results data for each call.
|
|
||||||
function batchCall(bytes[] calldata callDatas)
|
|
||||||
external
|
|
||||||
returns (CallResults[] memory callResults)
|
|
||||||
{
|
|
||||||
callResults = new CallResults[](callDatas.length);
|
|
||||||
for (uint256 i = 0; i != callDatas.length; ++i) {
|
|
||||||
callResults[i].success = true;
|
|
||||||
if (callDatas[i].length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(callResults[i].success, callResults[i].data) = address(this).call(callDatas[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
interface IKyberDmmPool {
|
|
||||||
|
|
||||||
function totalSupply()
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IKyberDmmFactory {
|
|
||||||
|
|
||||||
function getPools(address token0, address token1)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (address[] memory _tokenPools);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IKyberDmmRouter {
|
|
||||||
|
|
||||||
function factory() external view returns (address);
|
|
||||||
|
|
||||||
function getAmountsOut(uint256 amountIn, address[] calldata pools, address[] calldata path)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts);
|
|
||||||
|
|
||||||
function getAmountsIn(uint256 amountOut, address[] calldata pools, address[] calldata path)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
contract KyberDmmSampler
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for KyberDmm calls.
|
|
||||||
uint256 constant private KYBER_DMM_CALL_GAS = 150e3; // 150k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from KyberDmm.
|
|
||||||
/// @param router Router to look up tokens and amounts
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return pools The pool addresses involved in the multi path trade
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromKyberDmm(
|
|
||||||
address router,
|
|
||||||
address[] memory path,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (address[] memory pools, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
pools = _getKyberDmmPools(router, path);
|
|
||||||
if (pools.length == 0) {
|
|
||||||
return (pools, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IKyberDmmRouter(router).getAmountsOut
|
|
||||||
{gas: KYBER_DMM_CALL_GAS}
|
|
||||||
(takerTokenAmounts[i], pools, path)
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from KyberDmm.
|
|
||||||
/// @param router Router to look up tokens and amounts
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken.
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return pools The pool addresses involved in the multi path trade
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromKyberDmm(
|
|
||||||
address router,
|
|
||||||
address[] memory path,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (address[] memory pools, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
pools = _getKyberDmmPools(router, path);
|
|
||||||
if (pools.length == 0) {
|
|
||||||
return (pools, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IKyberDmmRouter(router).getAmountsIn
|
|
||||||
{gas: KYBER_DMM_CALL_GAS}
|
|
||||||
(makerTokenAmounts[i], pools, path)
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
takerTokenAmounts[i] = amounts[0];
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (takerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getKyberDmmPools(
|
|
||||||
address router,
|
|
||||||
address[] memory path
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (address[] memory pools)
|
|
||||||
{
|
|
||||||
IKyberDmmFactory factory = IKyberDmmFactory(IKyberDmmRouter(router).factory());
|
|
||||||
pools = new address[](path.length - 1);
|
|
||||||
for (uint256 i = 0; i < pools.length; i++) {
|
|
||||||
// find the best pool
|
|
||||||
address[] memory allPools;
|
|
||||||
try
|
|
||||||
factory.getPools
|
|
||||||
{gas: KYBER_DMM_CALL_GAS}
|
|
||||||
(path[i], path[i + 1])
|
|
||||||
returns (address[] memory allPools)
|
|
||||||
{
|
|
||||||
if (allPools.length == 0) {
|
|
||||||
return new address[](0);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 maxSupply = 0;
|
|
||||||
for (uint256 j = 0; j < allPools.length; j++) {
|
|
||||||
uint256 totalSupply = IKyberDmmPool(allPools[j]).totalSupply();
|
|
||||||
if (totalSupply > maxSupply) {
|
|
||||||
maxSupply = totalSupply;
|
|
||||||
pools[i] = allPools[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
return new address[](0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IKyberNetwork.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract KyberSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for Kyber calls.
|
|
||||||
uint256 constant private KYBER_CALL_GAS = 500e3; // 500k
|
|
||||||
/// @dev Kyber ETH pseudo-address.
|
|
||||||
address constant internal KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
|
||||||
|
|
||||||
struct KyberSamplerOpts {
|
|
||||||
uint256 reserveOffset;
|
|
||||||
address hintHandler;
|
|
||||||
address networkProxy;
|
|
||||||
address weth;
|
|
||||||
bytes hint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Kyber.
|
|
||||||
/// @param opts KyberSamplerOpts The nth reserve
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return reserveId The id of the reserve found at reserveOffset
|
|
||||||
/// @return hint The hint for the selected reserve
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token amount.
|
|
||||||
function sampleSellsFromKyberNetwork(
|
|
||||||
KyberSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bytes32 reserveId, bytes memory hint, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
reserveId = _getNextReserveId(opts, takerToken, makerToken);
|
|
||||||
if (reserveId == 0x0) {
|
|
||||||
return (reserveId, hint, makerTokenAmounts);
|
|
||||||
}
|
|
||||||
opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken);
|
|
||||||
hint = opts.hint;
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 value = this.sampleSellFromKyberNetwork(
|
|
||||||
opts,
|
|
||||||
takerToken,
|
|
||||||
makerToken,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
makerTokenAmounts[i] = value;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Kyber.
|
|
||||||
/// @param opts KyberSamplerOpts The nth reserve
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return reserveId The id of the reserve found at reserveOffset
|
|
||||||
/// @return hint The hint for the selected reserve
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token amount.
|
|
||||||
function sampleBuysFromKyberNetwork(
|
|
||||||
KyberSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bytes32 reserveId, bytes memory hint, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
|
|
||||||
reserveId = _getNextReserveId(opts, takerToken, makerToken);
|
|
||||||
if (reserveId == 0x0) {
|
|
||||||
return (reserveId, hint, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
opts.hint = this.encodeKyberHint(opts, reserveId, takerToken, makerToken);
|
|
||||||
hint = opts.hint;
|
|
||||||
|
|
||||||
takerTokenAmounts = _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, opts),
|
|
||||||
takerTokenData: abi.encode(takerToken, opts),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
return (reserveId, hint, takerTokenAmounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeKyberHint(
|
|
||||||
KyberSamplerOpts memory opts,
|
|
||||||
bytes32 reserveId,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bytes memory hint)
|
|
||||||
{
|
|
||||||
// Build a hint selecting the single reserve
|
|
||||||
IKyberHintHandler kyberHint = IKyberHintHandler(opts.hintHandler);
|
|
||||||
|
|
||||||
// All other reserves should be ignored with this hint
|
|
||||||
bytes32[] memory selectedReserves = new bytes32[](1);
|
|
||||||
selectedReserves[0] = reserveId;
|
|
||||||
uint256[] memory emptySplits = new uint256[](0);
|
|
||||||
|
|
||||||
if (takerToken == opts.weth) {
|
|
||||||
// ETH to Token
|
|
||||||
try
|
|
||||||
kyberHint.buildEthToTokenHint
|
|
||||||
{gas: KYBER_CALL_GAS}
|
|
||||||
(
|
|
||||||
makerToken,
|
|
||||||
IKyberHintHandler.TradeType.MaskIn,
|
|
||||||
selectedReserves,
|
|
||||||
emptySplits
|
|
||||||
)
|
|
||||||
returns (bytes memory result)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
}
|
|
||||||
} else if (makerToken == opts.weth) {
|
|
||||||
// Token to ETH
|
|
||||||
try
|
|
||||||
kyberHint.buildTokenToEthHint
|
|
||||||
{gas: KYBER_CALL_GAS}
|
|
||||||
(
|
|
||||||
takerToken,
|
|
||||||
IKyberHintHandler.TradeType.MaskIn,
|
|
||||||
selectedReserves,
|
|
||||||
emptySplits
|
|
||||||
)
|
|
||||||
returns (bytes memory result)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Token to Token
|
|
||||||
// We use the same reserve both ways
|
|
||||||
try
|
|
||||||
kyberHint.buildTokenToTokenHint
|
|
||||||
{gas: KYBER_CALL_GAS}
|
|
||||||
(
|
|
||||||
takerToken,
|
|
||||||
IKyberHintHandler.TradeType.MaskIn,
|
|
||||||
selectedReserves,
|
|
||||||
emptySplits,
|
|
||||||
makerToken,
|
|
||||||
IKyberHintHandler.TradeType.MaskIn,
|
|
||||||
selectedReserves,
|
|
||||||
emptySplits
|
|
||||||
)
|
|
||||||
returns (bytes memory result)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromKyber(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
(address makerToken, KyberSamplerOpts memory opts) =
|
|
||||||
abi.decode(makerTokenData, (address, KyberSamplerOpts));
|
|
||||||
(address takerToken, ) =
|
|
||||||
abi.decode(takerTokenData, (address, KyberSamplerOpts));
|
|
||||||
try
|
|
||||||
this.sampleSellFromKyberNetwork
|
|
||||||
(opts, takerToken, makerToken, sellAmount)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleSellFromKyberNetwork(
|
|
||||||
KyberSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256 takerTokenAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256 makerTokenAmount)
|
|
||||||
{
|
|
||||||
// If there is no hint do not continue
|
|
||||||
if (opts.hint.length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
IKyberNetworkProxy(opts.networkProxy).getExpectedRateAfterFee
|
|
||||||
{gas: KYBER_CALL_GAS}
|
|
||||||
(
|
|
||||||
takerToken == opts.weth ? KYBER_ETH_ADDRESS : takerToken,
|
|
||||||
makerToken == opts.weth ? KYBER_ETH_ADDRESS : makerToken,
|
|
||||||
takerTokenAmount,
|
|
||||||
0, // fee
|
|
||||||
opts.hint
|
|
||||||
)
|
|
||||||
returns (uint256 rate)
|
|
||||||
{
|
|
||||||
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
|
||||||
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
|
||||||
makerTokenAmount =
|
|
||||||
rate *
|
|
||||||
takerTokenAmount *
|
|
||||||
10 ** makerTokenDecimals /
|
|
||||||
10 ** takerTokenDecimals /
|
|
||||||
10 ** 18;
|
|
||||||
return makerTokenAmount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getNextReserveId(
|
|
||||||
KyberSamplerOpts memory opts,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (bytes32 reserveId)
|
|
||||||
{
|
|
||||||
// Fetch the registered reserves for this pair
|
|
||||||
IKyberHintHandler kyberHint = IKyberHintHandler(opts.hintHandler);
|
|
||||||
(bytes32[] memory reserveIds, ,) = kyberHint.getTradingReserves(
|
|
||||||
takerToken == opts.weth ? KYBER_ETH_ADDRESS : takerToken,
|
|
||||||
makerToken == opts.weth ? KYBER_ETH_ADDRESS : makerToken,
|
|
||||||
true,
|
|
||||||
new bytes(0) // empty hint
|
|
||||||
);
|
|
||||||
|
|
||||||
if (opts.reserveOffset >= reserveIds.length) {
|
|
||||||
return 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
reserveId = reserveIds[opts.reserveOffset];
|
|
||||||
// Ignore Kyber Bridged Reserves (0xbb)
|
|
||||||
if (uint256(reserveId >> 248) == 0xbb) {
|
|
||||||
return 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return reserveId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
contract LidoSampler is SamplerUtils {
|
|
||||||
struct LidoInfo {
|
|
||||||
address stEthToken;
|
|
||||||
address wethToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Lido
|
|
||||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromLido(
|
|
||||||
LidoInfo memory lidoInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
pure
|
|
||||||
returns (uint256[] memory)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
|
|
||||||
if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) {
|
|
||||||
// Return 0 values if not selling WETH for stETH
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
uint256[] memory makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Lido.
|
|
||||||
/// @param lidoInfo Info regarding a specific Lido deployment
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromLido(
|
|
||||||
LidoInfo memory lidoInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
pure
|
|
||||||
returns (uint256[] memory)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
|
|
||||||
if (takerToken != lidoInfo.wethToken || makerToken != address(lidoInfo.stEthToken)) {
|
|
||||||
// Return 0 values if not buying stETH for WETH
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
uint256[] memory takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minting stETH is always 1:1 therefore we can just return the same amounts back
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
|
||||||
import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract LiquidityProviderSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Default gas limit for liquidity provider calls.
|
|
||||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
|
|
||||||
/// @param providerAddress Address of the liquidity provider.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromLiquidityProvider(
|
|
||||||
address providerAddress,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Initialize array of maker token amounts.
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
ILiquidityProvider(providerAddress).getSellQuote
|
|
||||||
{gas: DEFAULT_CALL_GAS}
|
|
||||||
(
|
|
||||||
IERC20TokenV06(takerToken),
|
|
||||||
IERC20TokenV06(makerToken),
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
|
|
||||||
/// @param providerAddress Address of the liquidity provider.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromLiquidityProvider(
|
|
||||||
address providerAddress,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
takerTokenAmounts = _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, providerAddress),
|
|
||||||
takerTokenData: abi.encode(takerToken, providerAddress),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromLiquidityProvider(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(address takerToken, address providerAddress) =
|
|
||||||
abi.decode(takerTokenData, (address, address));
|
|
||||||
(address makerToken) =
|
|
||||||
abi.decode(makerTokenData, (address));
|
|
||||||
try
|
|
||||||
this.sampleSellsFromLiquidityProvider
|
|
||||||
{gas: DEFAULT_CALL_GAS}
|
|
||||||
(providerAddress, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
return amounts[0];
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IMStable.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract MStableSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Default gas limit for mStable calls.
|
|
||||||
uint256 constant private DEFAULT_CALL_GAS = 800e3; // 800k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from the mStable contract
|
|
||||||
/// @param router Address of the mStable contract
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromMStable(
|
|
||||||
address router,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
// Initialize array of maker token amounts.
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IMStable(router).getSwapOutput
|
|
||||||
{gas: DEFAULT_CALL_GAS}
|
|
||||||
(takerToken, makerToken, takerTokenAmounts[i])
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from MStable contract
|
|
||||||
/// @param router Address of the mStable contract
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromMStable(
|
|
||||||
address router,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
return _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, router),
|
|
||||||
takerTokenData: abi.encode(takerToken, router),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMStable
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromMStable(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(address takerToken, address router) =
|
|
||||||
abi.decode(takerTokenData, (address, address));
|
|
||||||
(address makerToken) =
|
|
||||||
abi.decode(makerTokenData, (address));
|
|
||||||
try
|
|
||||||
this.sampleSellsFromMStable
|
|
||||||
(router, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
return amounts[0];
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|
||||||
|
|
||||||
interface IPSM {
|
|
||||||
// @dev Get the fee for selling USDC to DAI in PSM
|
|
||||||
// @return tin toll in [wad]
|
|
||||||
function tin() external view returns (uint256);
|
|
||||||
// @dev Get the fee for selling DAI to USDC in PSM
|
|
||||||
// @return tout toll out [wad]
|
|
||||||
function tout() external view returns (uint256);
|
|
||||||
|
|
||||||
// @dev Get the address of the PSM state Vat
|
|
||||||
// @return address of the Vat
|
|
||||||
function vat() external view returns (address);
|
|
||||||
|
|
||||||
// @dev Get the address of the underlying vault powering PSM
|
|
||||||
// @return address of gemJoin contract
|
|
||||||
function gemJoin() external view returns (address);
|
|
||||||
|
|
||||||
// @dev Get the address of DAI
|
|
||||||
// @return address of DAI contract
|
|
||||||
function dai() external view returns (address);
|
|
||||||
|
|
||||||
// @dev Sell USDC for DAI
|
|
||||||
// @param usr The address of the account trading USDC for DAI.
|
|
||||||
// @param gemAmt The amount of USDC to sell in USDC base units
|
|
||||||
function sellGem(
|
|
||||||
address usr,
|
|
||||||
uint256 gemAmt
|
|
||||||
) external;
|
|
||||||
// @dev Buy USDC for DAI
|
|
||||||
// @param usr The address of the account trading DAI for USDC
|
|
||||||
// @param gemAmt The amount of USDC to buy in USDC base units
|
|
||||||
function buyGem(
|
|
||||||
address usr,
|
|
||||||
uint256 gemAmt
|
|
||||||
) external;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IVAT {
|
|
||||||
// @dev Get a collateral type by identifier
|
|
||||||
// @param ilkIdentifier bytes32 identifier. Example: ethers.utils.formatBytes32String("PSM-USDC-A")
|
|
||||||
// @return ilk
|
|
||||||
// @return ilk.Art Total Normalised Debt in wad
|
|
||||||
// @return ilk.rate Accumulated Rates in ray
|
|
||||||
// @return ilk.spot Price with Safety Margin in ray
|
|
||||||
// @return ilk.line Debt Ceiling in rad
|
|
||||||
// @return ilk.dust Urn Debt Floor in rad
|
|
||||||
function ilks(
|
|
||||||
bytes32 ilkIdentifier
|
|
||||||
) external view returns (
|
|
||||||
uint256 Art,
|
|
||||||
uint256 rate,
|
|
||||||
uint256 spot,
|
|
||||||
uint256 line,
|
|
||||||
uint256 dust
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract MakerPSMSampler is
|
|
||||||
SamplerUtils
|
|
||||||
{
|
|
||||||
using LibSafeMathV06 for uint256;
|
|
||||||
|
|
||||||
/// @dev Information about which PSM module to use
|
|
||||||
struct MakerPsmInfo {
|
|
||||||
address psmAddress;
|
|
||||||
bytes32 ilkIdentifier;
|
|
||||||
address gemTokenAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Gas limit for MakerPsm calls.
|
|
||||||
uint256 constant private MAKER_PSM_CALL_GAS = 300e3; // 300k
|
|
||||||
|
|
||||||
|
|
||||||
// Maker units
|
|
||||||
// wad: fixed point decimal with 18 decimals (for basic quantities, e.g. balances)
|
|
||||||
uint256 constant private WAD = 10 ** 18;
|
|
||||||
// ray: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios)
|
|
||||||
uint256 constant private RAY = 10 ** 27;
|
|
||||||
// rad: fixed point decimal with 45 decimals (result of integer multiplication with a wad and a ray)
|
|
||||||
uint256 constant private RAD = 10 ** 45;
|
|
||||||
// See https://github.com/makerdao/dss/blob/master/DEVELOPING.m
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Maker PSM
|
|
||||||
function sampleSellsFromMakerPsm(
|
|
||||||
MakerPsmInfo memory psmInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
|
||||||
IVAT vat = IVAT(psm.vat());
|
|
||||||
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 buyAmount = _samplePSMSell(psmInfo, makerToken, takerToken, takerTokenAmounts[i], psm, vat);
|
|
||||||
|
|
||||||
if (buyAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleBuysFromMakerPsm(
|
|
||||||
MakerPsmInfo memory psmInfo,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
IPSM psm = IPSM(psmInfo.psmAddress);
|
|
||||||
IVAT vat = IVAT(psm.vat());
|
|
||||||
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
if (makerToken != psm.dai() && takerToken != psm.dai()) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 sellAmount = _samplePSMBuy(psmInfo, makerToken, takerToken, makerTokenAmounts[i], psm, vat);
|
|
||||||
|
|
||||||
if (sellAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
takerTokenAmounts[i] = sellAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function _samplePSMSell(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 takerTokenAmount, IPSM psm, IVAT vat)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
|
||||||
uint256 gemTokenBaseUnit = uint256(1e6);
|
|
||||||
|
|
||||||
if (takerToken == psmInfo.gemTokenAddress) {
|
|
||||||
// Simulate sellGem
|
|
||||||
// Selling USDC to the PSM, increasing the total debt
|
|
||||||
// Convert USDC 6 decimals to 18 decimals [wad]
|
|
||||||
uint256 takerTokenAmountInWad = takerTokenAmount.safeMul(1e12);
|
|
||||||
|
|
||||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
|
||||||
|
|
||||||
// PSM is too full to fit
|
|
||||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 feeInWad = takerTokenAmountInWad.safeMul(psm.tin()).safeDiv(WAD);
|
|
||||||
uint256 makerTokenAmountInWad = takerTokenAmountInWad.safeSub(feeInWad);
|
|
||||||
|
|
||||||
return makerTokenAmountInWad;
|
|
||||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
|
||||||
// Simulate buyGem
|
|
||||||
// Buying USDC from the PSM, decreasing the total debt
|
|
||||||
// Selling DAI for USDC, already in 18 decimals [wad]
|
|
||||||
uint256 takerTokenAmountInWad = takerTokenAmount;
|
|
||||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
|
||||||
|
|
||||||
// PSM is empty, not enough USDC to buy from it
|
|
||||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 feeDivisorInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
|
||||||
uint256 makerTokenAmountInGemTokenBaseUnits = takerTokenAmountInWad.safeMul(gemTokenBaseUnit).safeDiv(feeDivisorInWad);
|
|
||||||
|
|
||||||
return makerTokenAmountInGemTokenBaseUnits;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _samplePSMBuy(MakerPsmInfo memory psmInfo, address makerToken, address takerToken, uint256 makerTokenAmount, IPSM psm, IVAT vat)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
(uint256 totalDebtInWad,,, uint256 debtCeilingInRad, uint256 debtFloorInRad) = vat.ilks(psmInfo.ilkIdentifier);
|
|
||||||
|
|
||||||
if (takerToken == psmInfo.gemTokenAddress) {
|
|
||||||
// Simulate sellGem
|
|
||||||
// Selling USDC to the PSM, increasing the total debt
|
|
||||||
uint256 makerTokenAmountInWad = makerTokenAmount;
|
|
||||||
uint256 feeDivisorInWad = WAD.safeSub(psm.tin()); // eg. 0.999 * 10 ** 18 with 0.1% tin;
|
|
||||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(WAD).safeDiv(feeDivisorInWad);
|
|
||||||
uint256 newTotalDebtInRad = totalDebtInWad.safeAdd(takerTokenAmountInWad).safeMul(RAY);
|
|
||||||
|
|
||||||
// PSM is too full to fit
|
|
||||||
if (newTotalDebtInRad >= debtCeilingInRad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 takerTokenAmountInGemInGemBaseUnits = (takerTokenAmountInWad.safeDiv(1e12)).safeAdd(1); // Add 1 to deal with cut off decimals converting to lower decimals
|
|
||||||
|
|
||||||
return takerTokenAmountInGemInGemBaseUnits;
|
|
||||||
} else if (makerToken == psmInfo.gemTokenAddress) {
|
|
||||||
// Simulate buyGem
|
|
||||||
// Buying USDC from the PSM, decreasing the total debt
|
|
||||||
uint256 makerTokenAmountInWad = makerTokenAmount.safeMul(1e12);
|
|
||||||
uint256 feeMultiplierInWad = WAD.safeAdd(psm.tout()); // eg. 1.001 * 10 ** 18 with 0.1% tout;
|
|
||||||
uint256 takerTokenAmountInWad = makerTokenAmountInWad.safeMul(feeMultiplierInWad).safeDiv(WAD);
|
|
||||||
if (takerTokenAmountInWad > totalDebtInWad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint256 newTotalDebtInRad = totalDebtInWad.safeSub(takerTokenAmountInWad).safeMul(RAY);
|
|
||||||
|
|
||||||
// PSM is empty, not enough USDC to buy
|
|
||||||
if (newTotalDebtInRad <= debtFloorInRad) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return takerTokenAmountInWad;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IMooniswap.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract MooniswapSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for Mooniswap calls.
|
|
||||||
uint256 constant private MOONISWAP_CALL_GAS = 150e3; // 150k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Mooniswap.
|
|
||||||
/// @param registry Address of the Mooniswap Registry.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return pool The contract address for the pool
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromMooniswap(
|
|
||||||
address registry,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (IMooniswap pool, uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
uint256 buyAmount = sampleSingleSellFromMooniswapPool(
|
|
||||||
registry,
|
|
||||||
takerToken,
|
|
||||||
makerToken,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pool = IMooniswap(
|
|
||||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleSingleSellFromMooniswapPool(
|
|
||||||
address registry,
|
|
||||||
address mooniswapTakerToken,
|
|
||||||
address mooniswapMakerToken,
|
|
||||||
uint256 takerTokenAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
// Find the pool for the pair.
|
|
||||||
IMooniswap pool = IMooniswap(
|
|
||||||
IMooniswapRegistry(registry).pools(mooniswapTakerToken, mooniswapMakerToken)
|
|
||||||
);
|
|
||||||
// If there is no pool then return early
|
|
||||||
if (address(pool) == address(0)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint256 poolBalance = mooniswapTakerToken == address(0)
|
|
||||||
? address(pool).balance
|
|
||||||
: IERC20TokenV06(mooniswapTakerToken).balanceOf(address(pool));
|
|
||||||
// If the pool balance is smaller than the sell amount
|
|
||||||
// don't sample to avoid multiplication overflow in buys
|
|
||||||
if (poolBalance < takerTokenAmount) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
pool.getReturn
|
|
||||||
{gas: MOONISWAP_CALL_GAS}
|
|
||||||
(mooniswapTakerToken, mooniswapMakerToken, takerTokenAmount)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
return amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Mooniswap.
|
|
||||||
/// @param registry Address of the Mooniswap Registry.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
|
||||||
/// @return pool The contract address for the pool
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromMooniswap(
|
|
||||||
address registry,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (IMooniswap pool, uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
takerTokenAmounts = _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(registry, makerToken),
|
|
||||||
takerTokenData: abi.encode(registry, takerToken),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromMooniswap
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
|
|
||||||
pool = IMooniswap(
|
|
||||||
IMooniswapRegistry(registry).pools(takerToken, makerToken)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromMooniswap(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(address registry, address mooniswapTakerToken) = abi.decode(takerTokenData, (address, address));
|
|
||||||
(address _registry, address mooniswapMakerToken) = abi.decode(makerTokenData, (address, address));
|
|
||||||
return sampleSingleSellFromMooniswapPool(
|
|
||||||
registry,
|
|
||||||
mooniswapTakerToken,
|
|
||||||
mooniswapMakerToken,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IMultiBridge.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract MultiBridgeSampler {
|
|
||||||
|
|
||||||
/// @dev Default gas limit for multibridge calls.
|
|
||||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from MultiBridge.
|
|
||||||
/// @param multibridge Address of the MultiBridge contract.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param intermediateToken The address of the intermediate token to
|
|
||||||
/// use in an indirect route.
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromMultiBridge(
|
|
||||||
address multibridge,
|
|
||||||
address takerToken,
|
|
||||||
address intermediateToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Initialize array of maker token amounts.
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
// If no address provided, return all zeros.
|
|
||||||
if (multibridge == address(0)) {
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
(bool didSucceed, bytes memory resultData) =
|
|
||||||
multibridge.staticcall.gas(DEFAULT_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
IMultiBridge(0).getSellQuote.selector,
|
|
||||||
takerToken,
|
|
||||||
intermediateToken,
|
|
||||||
makerToken,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
));
|
|
||||||
uint256 buyAmount = 0;
|
|
||||||
if (didSucceed) {
|
|
||||||
buyAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
// Exit early if the amount is too high for the source to serve
|
|
||||||
if (buyAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
|
||||||
|
|
||||||
|
|
||||||
interface IExchange {
|
|
||||||
|
|
||||||
enum OrderStatus {
|
|
||||||
INVALID,
|
|
||||||
FILLABLE,
|
|
||||||
FILLED,
|
|
||||||
CANCELLED,
|
|
||||||
EXPIRED
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev A standard OTC or OO limit order.
|
|
||||||
struct LimitOrder {
|
|
||||||
IERC20TokenV06 makerToken;
|
|
||||||
IERC20TokenV06 takerToken;
|
|
||||||
uint128 makerAmount;
|
|
||||||
uint128 takerAmount;
|
|
||||||
uint128 takerTokenFeeAmount;
|
|
||||||
address maker;
|
|
||||||
address taker;
|
|
||||||
address sender;
|
|
||||||
address feeRecipient;
|
|
||||||
bytes32 pool;
|
|
||||||
uint64 expiry;
|
|
||||||
uint256 salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev An RFQ limit order.
|
|
||||||
struct RfqOrder {
|
|
||||||
IERC20TokenV06 makerToken;
|
|
||||||
IERC20TokenV06 takerToken;
|
|
||||||
uint128 makerAmount;
|
|
||||||
uint128 takerAmount;
|
|
||||||
address maker;
|
|
||||||
address taker;
|
|
||||||
address txOrigin;
|
|
||||||
bytes32 pool;
|
|
||||||
uint64 expiry;
|
|
||||||
uint256 salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Info on a limit or RFQ order.
|
|
||||||
struct OrderInfo {
|
|
||||||
bytes32 orderHash;
|
|
||||||
OrderStatus status;
|
|
||||||
uint128 takerTokenFilledAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allowed signature types.
|
|
||||||
enum SignatureType {
|
|
||||||
ILLEGAL,
|
|
||||||
INVALID,
|
|
||||||
EIP712,
|
|
||||||
ETHSIGN
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Encoded EC signature.
|
|
||||||
struct Signature {
|
|
||||||
// How to validate the signature.
|
|
||||||
SignatureType signatureType;
|
|
||||||
// EC Signature data.
|
|
||||||
uint8 v;
|
|
||||||
// EC Signature data.
|
|
||||||
bytes32 r;
|
|
||||||
// EC Signature data.
|
|
||||||
bytes32 s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Get the order info for a limit order.
|
|
||||||
/// @param order The limit order.
|
|
||||||
/// @return orderInfo Info about the order.
|
|
||||||
function getLimitOrderInfo(LimitOrder memory order)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (OrderInfo memory orderInfo);
|
|
||||||
|
|
||||||
/// @dev Get order info, fillable amount, and signature validity for a limit order.
|
|
||||||
/// Fillable amount is determined using balances and allowances of the maker.
|
|
||||||
/// @param order The limit order.
|
|
||||||
/// @param signature The order signature.
|
|
||||||
/// @return orderInfo Info about the order.
|
|
||||||
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
|
||||||
/// based on maker funds, in taker tokens.
|
|
||||||
/// @return isSignatureValid Whether the signature is valid.
|
|
||||||
function getLimitOrderRelevantState(
|
|
||||||
LimitOrder memory order,
|
|
||||||
Signature calldata signature
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (
|
|
||||||
OrderInfo memory orderInfo,
|
|
||||||
uint128 actualFillableTakerTokenAmount,
|
|
||||||
bool isSignatureValid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract NativeOrderSampler {
|
|
||||||
using LibSafeMathV06 for uint256;
|
|
||||||
using LibBytesV06 for bytes;
|
|
||||||
|
|
||||||
/// @dev Gas limit for calls to `getOrderFillableTakerAmount()`.
|
|
||||||
uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k
|
|
||||||
|
|
||||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
|
||||||
/// Effectively ignores orders that have empty signatures or
|
|
||||||
/// maker/taker asset amounts (returning 0).
|
|
||||||
/// @param orders Native limit orders to query.
|
|
||||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
|
||||||
/// @param exchange The V4 exchange.
|
|
||||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
|
||||||
/// by each order in `orders`.
|
|
||||||
function getLimitOrderFillableTakerAssetAmounts(
|
|
||||||
IExchange.LimitOrder[] memory orders,
|
|
||||||
IExchange.Signature[] memory orderSignatures,
|
|
||||||
IExchange exchange
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory orderFillableTakerAssetAmounts)
|
|
||||||
{
|
|
||||||
orderFillableTakerAssetAmounts = new uint256[](orders.length);
|
|
||||||
for (uint256 i = 0; i != orders.length; i++) {
|
|
||||||
try
|
|
||||||
this.getLimitOrderFillableTakerAmount
|
|
||||||
{gas: DEFAULT_CALL_GAS}
|
|
||||||
(
|
|
||||||
orders[i],
|
|
||||||
orderSignatures[i],
|
|
||||||
exchange
|
|
||||||
)
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
orderFillableTakerAssetAmounts[i] = amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
orderFillableTakerAssetAmounts[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
|
||||||
/// Effectively ignores orders that have empty signatures or
|
|
||||||
/// @param orders Native orders to query.
|
|
||||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
|
||||||
/// @param exchange The V4 exchange.
|
|
||||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
|
||||||
/// by each order in `orders`.
|
|
||||||
function getLimitOrderFillableMakerAssetAmounts(
|
|
||||||
IExchange.LimitOrder[] memory orders,
|
|
||||||
IExchange.Signature[] memory orderSignatures,
|
|
||||||
IExchange exchange
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory orderFillableMakerAssetAmounts)
|
|
||||||
{
|
|
||||||
orderFillableMakerAssetAmounts = getLimitOrderFillableTakerAssetAmounts(
|
|
||||||
orders,
|
|
||||||
orderSignatures,
|
|
||||||
exchange
|
|
||||||
);
|
|
||||||
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
|
|
||||||
// convert them to maker asset amounts.
|
|
||||||
for (uint256 i = 0; i < orders.length; ++i) {
|
|
||||||
if (orderFillableMakerAssetAmounts[i] != 0) {
|
|
||||||
orderFillableMakerAssetAmounts[i] = LibMathV06.getPartialAmountCeil(
|
|
||||||
orderFillableMakerAssetAmounts[i],
|
|
||||||
orders[i].takerAmount,
|
|
||||||
orders[i].makerAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Get the fillable taker amount of an order, taking into account
|
|
||||||
/// order state, maker fees, and maker balances.
|
|
||||||
function getLimitOrderFillableTakerAmount(
|
|
||||||
IExchange.LimitOrder memory order,
|
|
||||||
IExchange.Signature memory signature,
|
|
||||||
IExchange exchange
|
|
||||||
)
|
|
||||||
virtual
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256 fillableTakerAmount)
|
|
||||||
{
|
|
||||||
if (signature.signatureType == IExchange.SignatureType.ILLEGAL ||
|
|
||||||
signature.signatureType == IExchange.SignatureType.INVALID ||
|
|
||||||
order.makerAmount == 0 ||
|
|
||||||
order.takerAmount == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
IExchange.OrderInfo memory orderInfo,
|
|
||||||
uint128 remainingFillableTakerAmount,
|
|
||||||
bool isSignatureValid
|
|
||||||
) = exchange.getLimitOrderRelevantState(order, signature);
|
|
||||||
|
|
||||||
if (
|
|
||||||
orderInfo.status != IExchange.OrderStatus.FILLABLE ||
|
|
||||||
!isSignatureValid ||
|
|
||||||
order.makerToken == IERC20TokenV06(0)
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fillableTakerAmount = uint256(remainingFillableTakerAmount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract SamplerUtils {
|
|
||||||
|
|
||||||
/// @dev Overridable way to get token decimals.
|
|
||||||
/// @param tokenAddress Address of the token.
|
|
||||||
/// @return decimals The decimal places for the token.
|
|
||||||
function _getTokenDecimals(address tokenAddress)
|
|
||||||
virtual
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint8 decimals)
|
|
||||||
{
|
|
||||||
return LibERC20TokenV06.compatDecimals(IERC20TokenV06(tokenAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
function _toSingleValueArray(uint256 v)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (uint256[] memory arr)
|
|
||||||
{
|
|
||||||
arr = new uint256[](1);
|
|
||||||
arr[0] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Assert that the tokens in a trade pair are valid.
|
|
||||||
/// @param makerToken Address of the maker token.
|
|
||||||
/// @param takerToken Address of the taker token.
|
|
||||||
function _assertValidPair(address makerToken, address takerToken)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
{
|
|
||||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./interfaces/IShell.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract ShellSampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
|
|
||||||
struct ShellInfo {
|
|
||||||
address poolAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Default gas limit for Shell calls.
|
|
||||||
uint256 constant private DEFAULT_CALL_GAS = 300e3; // 300k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from the Shell pool contract
|
|
||||||
/// @param pool Address of the Shell pool contract
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromShell(
|
|
||||||
address pool,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Initialize array of maker token amounts.
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IShell(pool).viewOriginSwap
|
|
||||||
{gas: DEFAULT_CALL_GAS}
|
|
||||||
(takerToken, makerToken, takerTokenAmounts[i])
|
|
||||||
returns (uint256 amount)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amount;
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Shell pool contract
|
|
||||||
/// @param pool Address of the Shell pool contract
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromShell(
|
|
||||||
address pool,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
return _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(makerToken, pool),
|
|
||||||
takerTokenData: abi.encode(takerToken, pool),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromShell
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromShell(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(address takerToken, address pool) = abi.decode(takerTokenData, (address, address));
|
|
||||||
(address makerToken) = abi.decode(makerTokenData, (address));
|
|
||||||
|
|
||||||
try
|
|
||||||
this.sampleSellsFromShell
|
|
||||||
(pool, takerToken, makerToken, _toSingleValueArray(sellAmount))
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
return amounts[0];
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
// import "./interfaces/ISmoothy.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
import "./interfaces/ISmoothy.sol";
|
|
||||||
|
|
||||||
contract SmoothySampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Information for sampling from smoothy sources.
|
|
||||||
struct SmoothyInfo {
|
|
||||||
address poolAddress;
|
|
||||||
bytes4 sellQuoteFunctionSelector;
|
|
||||||
bytes4 buyQuoteFunctionSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Smoothy calls.
|
|
||||||
uint256 constant private SMOOTHY_CALL_GAS = 600e3;
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Smoothy.
|
|
||||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromSmoothy(
|
|
||||||
SmoothyInfo memory smoothyInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Basically a Curve fork
|
|
||||||
|
|
||||||
// Smoothy only keep a percentage of its tokens available in reserve
|
|
||||||
uint256 poolReserveMakerAmount = ISmoothy(smoothyInfo.poolAddress).getBalance(uint256(toTokenIdx)) -
|
|
||||||
ISmoothy(smoothyInfo.poolAddress)._yBalances(uint256(toTokenIdx));
|
|
||||||
(, , , uint256 decimals) = ISmoothy(smoothyInfo.poolAddress).getTokenStats(uint256(toTokenIdx));
|
|
||||||
poolReserveMakerAmount = poolReserveMakerAmount/(10**(18-decimals));
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
(bool didSucceed, bytes memory resultData) =
|
|
||||||
smoothyInfo.poolAddress.staticcall.gas(SMOOTHY_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
smoothyInfo.sellQuoteFunctionSelector,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
));
|
|
||||||
uint256 buyAmount = 0;
|
|
||||||
if (didSucceed) {
|
|
||||||
buyAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the quoted buyAmount is available in the pool reserve
|
|
||||||
if (buyAmount >= poolReserveMakerAmount) {
|
|
||||||
// Assign pool reserve amount for all higher samples to break early
|
|
||||||
for (uint256 j = i; j < numSamples; j++) {
|
|
||||||
makerTokenAmounts[j] = poolReserveMakerAmount;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Smoothy.
|
|
||||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromSmoothy(
|
|
||||||
SmoothyInfo memory smoothyInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Buys not supported so approximate it.
|
|
||||||
return _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(toTokenIdx, smoothyInfo),
|
|
||||||
takerTokenData: abi.encode(fromTokenIdx, smoothyInfo),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromSmoothy
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromSmoothy(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(int128 takerTokenIdx, SmoothyInfo memory smoothyInfo) =
|
|
||||||
abi.decode(takerTokenData, (int128, SmoothyInfo));
|
|
||||||
(int128 makerTokenIdx) =
|
|
||||||
abi.decode(makerTokenData, (int128));
|
|
||||||
(bool success, bytes memory resultData) =
|
|
||||||
address(this).staticcall(abi.encodeWithSelector(
|
|
||||||
this.sampleSellsFromSmoothy.selector,
|
|
||||||
smoothyInfo,
|
|
||||||
takerTokenIdx,
|
|
||||||
makerTokenIdx,
|
|
||||||
_toSingleValueArray(sellAmount)
|
|
||||||
));
|
|
||||||
if (!success) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// solhint-disable-next-line indent
|
|
||||||
return abi.decode(resultData, (uint256[]))[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TwoHopSampler {
|
|
||||||
using LibBytesV06 for bytes;
|
|
||||||
|
|
||||||
struct HopInfo {
|
|
||||||
uint256 sourceIndex;
|
|
||||||
bytes returnData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleTwoHopSell(
|
|
||||||
bytes[] memory firstHopCalls,
|
|
||||||
bytes[] memory secondHopCalls,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (
|
|
||||||
HopInfo memory firstHop,
|
|
||||||
HopInfo memory secondHop,
|
|
||||||
uint256 buyAmount
|
|
||||||
)
|
|
||||||
{
|
|
||||||
uint256 intermediateAssetAmount = 0;
|
|
||||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
|
||||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount);
|
|
||||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
|
||||||
if (didSucceed) {
|
|
||||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
|
||||||
if (amount > intermediateAssetAmount) {
|
|
||||||
intermediateAssetAmount = amount;
|
|
||||||
firstHop.sourceIndex = i;
|
|
||||||
firstHop.returnData = returnData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (intermediateAssetAmount == 0) {
|
|
||||||
return (firstHop, secondHop, buyAmount);
|
|
||||||
}
|
|
||||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
|
||||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
|
|
||||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
|
||||||
if (didSucceed) {
|
|
||||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
|
||||||
if (amount > buyAmount) {
|
|
||||||
buyAmount = amount;
|
|
||||||
secondHop.sourceIndex = j;
|
|
||||||
secondHop.returnData = returnData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleTwoHopBuy(
|
|
||||||
bytes[] memory firstHopCalls,
|
|
||||||
bytes[] memory secondHopCalls,
|
|
||||||
uint256 buyAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (
|
|
||||||
HopInfo memory firstHop,
|
|
||||||
HopInfo memory secondHop,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
{
|
|
||||||
sellAmount = uint256(-1);
|
|
||||||
uint256 intermediateAssetAmount = uint256(-1);
|
|
||||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
|
||||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount);
|
|
||||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
|
||||||
if (didSucceed) {
|
|
||||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
|
||||||
if (
|
|
||||||
amount > 0 &&
|
|
||||||
amount < intermediateAssetAmount
|
|
||||||
) {
|
|
||||||
intermediateAssetAmount = amount;
|
|
||||||
secondHop.sourceIndex = j;
|
|
||||||
secondHop.returnData = returnData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (intermediateAssetAmount == uint256(-1)) {
|
|
||||||
return (firstHop, secondHop, sellAmount);
|
|
||||||
}
|
|
||||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
|
||||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount);
|
|
||||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
|
||||||
if (didSucceed) {
|
|
||||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
|
||||||
if (
|
|
||||||
amount > 0 &&
|
|
||||||
amount < sellAmount
|
|
||||||
) {
|
|
||||||
sellAmount = amount;
|
|
||||||
firstHop.sourceIndex = i;
|
|
||||||
firstHop.returnData = returnData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IUniswapExchangeQuotes.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
|
|
||||||
|
|
||||||
interface IUniswapExchangeFactory {
|
|
||||||
|
|
||||||
/// @dev Get the exchange for a token.
|
|
||||||
/// @param tokenAddress The address of the token contract.
|
|
||||||
function getExchange(address tokenAddress)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (address);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
contract UniswapSampler is
|
|
||||||
SamplerUtils
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for Uniswap calls.
|
|
||||||
uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Uniswap.
|
|
||||||
/// @param router Address of the Uniswap Router
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromUniswap(
|
|
||||||
address router,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
|
||||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
bool didSucceed = true;
|
|
||||||
if (makerToken == address(0)) {
|
|
||||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(takerTokenExchange),
|
|
||||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
} else if (takerToken == address(0)) {
|
|
||||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(makerTokenExchange),
|
|
||||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
uint256 ethBought;
|
|
||||||
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(takerTokenExchange),
|
|
||||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
if (ethBought != 0) {
|
|
||||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(makerTokenExchange),
|
|
||||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
|
||||||
ethBought
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
makerTokenAmounts[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Break early if amounts are 0
|
|
||||||
if (!didSucceed || makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Uniswap.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromUniswap(
|
|
||||||
address router,
|
|
||||||
address takerToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
_assertValidPair(makerToken, takerToken);
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
|
|
||||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == address(0) ?
|
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, takerToken);
|
|
||||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == address(0) ?
|
|
||||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(router, makerToken);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
bool didSucceed = true;
|
|
||||||
if (makerToken == address(0)) {
|
|
||||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(takerTokenExchange),
|
|
||||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
|
||||||
makerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
} else if (takerToken == address(0)) {
|
|
||||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(makerTokenExchange),
|
|
||||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
|
||||||
makerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
uint256 ethSold;
|
|
||||||
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(makerTokenExchange),
|
|
||||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
|
||||||
makerTokenAmounts[i]
|
|
||||||
);
|
|
||||||
if (ethSold != 0) {
|
|
||||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
|
||||||
address(takerTokenExchange),
|
|
||||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
|
||||||
ethSold
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
takerTokenAmounts[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Break early if amounts are 0
|
|
||||||
if (!didSucceed || takerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Gracefully calls a Uniswap pricing function.
|
|
||||||
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
|
|
||||||
/// @param functionSelector Selector of the target function.
|
|
||||||
/// @param inputAmount Quantity parameter particular to the pricing function.
|
|
||||||
/// @return outputAmount The returned amount from the function call. Will be
|
|
||||||
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
|
|
||||||
function _callUniswapExchangePriceFunction(
|
|
||||||
address uniswapExchangeAddress,
|
|
||||||
bytes4 functionSelector,
|
|
||||||
uint256 inputAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 outputAmount, bool didSucceed)
|
|
||||||
{
|
|
||||||
if (uniswapExchangeAddress == address(0)) {
|
|
||||||
return (outputAmount, didSucceed);
|
|
||||||
}
|
|
||||||
bytes memory resultData;
|
|
||||||
(didSucceed, resultData) =
|
|
||||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
functionSelector,
|
|
||||||
inputAmount
|
|
||||||
));
|
|
||||||
if (didSucceed) {
|
|
||||||
outputAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Retrive an existing Uniswap exchange contract.
|
|
||||||
/// Throws if the exchange does not exist.
|
|
||||||
/// @param router Address of the Uniswap router.
|
|
||||||
/// @param tokenAddress Address of the token contract.
|
|
||||||
/// @return exchange `IUniswapExchangeQuotes` for the token.
|
|
||||||
function _getUniswapExchange(address router, address tokenAddress)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (IUniswapExchangeQuotes exchange)
|
|
||||||
{
|
|
||||||
exchange = IUniswapExchangeQuotes(
|
|
||||||
address(IUniswapExchangeFactory(router)
|
|
||||||
.getExchange(tokenAddress))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "./interfaces/IUniswapV2Router01.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract UniswapV2Sampler
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for UniswapV2 calls.
|
|
||||||
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from UniswapV2.
|
|
||||||
/// @param router Router to look up tokens and amounts
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromUniswapV2(
|
|
||||||
address router,
|
|
||||||
address[] memory path,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IUniswapV2Router01(router).getAmountsOut
|
|
||||||
{gas: UNISWAPV2_CALL_GAS}
|
|
||||||
(takerTokenAmounts[i], path)
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
makerTokenAmounts[i] = amounts[path.length - 1];
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from UniswapV2.
|
|
||||||
/// @param router Router to look up tokens and amounts
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken.
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromUniswapV2(
|
|
||||||
address router,
|
|
||||||
address[] memory path,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
uint256 numSamples = makerTokenAmounts.length;
|
|
||||||
takerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
try
|
|
||||||
IUniswapV2Router01(router).getAmountsIn
|
|
||||||
{gas: UNISWAPV2_CALL_GAS}
|
|
||||||
(makerTokenAmounts[i], path)
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
takerTokenAmounts[i] = amounts[0];
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (takerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (bytes memory) {
|
|
||||||
// Swallow failures, leaving all results as zero.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
|
||||||
|
|
||||||
interface IUniswapV3Quoter {
|
|
||||||
function factory()
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (IUniswapV3Factory factory);
|
|
||||||
function quoteExactInput(bytes memory path, uint256 amountIn)
|
|
||||||
external
|
|
||||||
returns (uint256 amountOut);
|
|
||||||
function quoteExactOutput(bytes memory path, uint256 amountOut)
|
|
||||||
external
|
|
||||||
returns (uint256 amountIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IUniswapV3Factory {
|
|
||||||
function getPool(IERC20TokenV06 a, IERC20TokenV06 b, uint24 fee)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (IUniswapV3Pool pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IUniswapV3Pool {
|
|
||||||
function token0() external view returns (IERC20TokenV06);
|
|
||||||
function token1() external view returns (IERC20TokenV06);
|
|
||||||
function fee() external view returns (uint24);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract UniswapV3Sampler
|
|
||||||
{
|
|
||||||
/// @dev Gas limit for UniswapV3 calls. This is 100% a guess.
|
|
||||||
uint256 constant private QUOTE_GAS = 600e3;
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from UniswapV3.
|
|
||||||
/// @param quoter UniswapV3 Quoter contract.
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromUniswapV3(
|
|
||||||
IUniswapV3Quoter quoter,
|
|
||||||
IERC20TokenV06[] memory path,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (
|
|
||||||
bytes[] memory uniswapPaths,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
{
|
|
||||||
IUniswapV3Pool[][] memory poolPaths =
|
|
||||||
_getValidPoolPaths(quoter.factory(), path, 0);
|
|
||||||
|
|
||||||
makerTokenAmounts = new uint256[](takerTokenAmounts.length);
|
|
||||||
uniswapPaths = new bytes[](takerTokenAmounts.length);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < takerTokenAmounts.length; ++i) {
|
|
||||||
// Pick the best result from all the paths.
|
|
||||||
bytes memory topUniswapPath;
|
|
||||||
uint256 topBuyAmount = 0;
|
|
||||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
|
||||||
bytes memory uniswapPath = _toUniswapPath(path, poolPaths[j]);
|
|
||||||
try
|
|
||||||
quoter.quoteExactInput
|
|
||||||
{ gas: QUOTE_GAS }
|
|
||||||
(uniswapPath, takerTokenAmounts[i])
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
if (topBuyAmount <= buyAmount) {
|
|
||||||
topBuyAmount = buyAmount;
|
|
||||||
topUniswapPath = uniswapPath;
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
}
|
|
||||||
// Break early if we can't complete the buys.
|
|
||||||
if (topBuyAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
makerTokenAmounts[i] = topBuyAmount;
|
|
||||||
uniswapPaths[i] = topUniswapPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from UniswapV3.
|
|
||||||
/// @param quoter UniswapV3 Quoter contract.
|
|
||||||
/// @param path Token route. Should be takerToken -> makerToken.
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromUniswapV3(
|
|
||||||
IUniswapV3Quoter quoter,
|
|
||||||
IERC20TokenV06[] memory path,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (
|
|
||||||
bytes[] memory uniswapPaths,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
{
|
|
||||||
IUniswapV3Pool[][] memory poolPaths =
|
|
||||||
_getValidPoolPaths(quoter.factory(), path, 0);
|
|
||||||
IERC20TokenV06[] memory reversedPath = _reverseTokenPath(path);
|
|
||||||
|
|
||||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
|
||||||
uniswapPaths = new bytes[](makerTokenAmounts.length);
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < makerTokenAmounts.length; ++i) {
|
|
||||||
// Pick the best result from all the paths.
|
|
||||||
bytes memory topUniswapPath;
|
|
||||||
uint256 topSellAmount = 0;
|
|
||||||
for (uint256 j = 0; j < poolPaths.length; ++j) {
|
|
||||||
// quoter requires path to be reversed for buys.
|
|
||||||
bytes memory uniswapPath = _toUniswapPath(
|
|
||||||
reversedPath,
|
|
||||||
_reversePoolPath(poolPaths[j])
|
|
||||||
);
|
|
||||||
try
|
|
||||||
quoter.quoteExactOutput
|
|
||||||
{ gas: QUOTE_GAS }
|
|
||||||
(uniswapPath, makerTokenAmounts[i])
|
|
||||||
returns (uint256 sellAmount)
|
|
||||||
{
|
|
||||||
if (topSellAmount == 0 || topSellAmount >= sellAmount) {
|
|
||||||
topSellAmount = sellAmount;
|
|
||||||
// But the output path should still be encoded for sells.
|
|
||||||
topUniswapPath = _toUniswapPath(path, poolPaths[j]);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
// Break early if we can't complete the buys.
|
|
||||||
if (topSellAmount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
takerTokenAmounts[i] = topSellAmount;
|
|
||||||
uniswapPaths[i] = topUniswapPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getValidPoolPaths(
|
|
||||||
IUniswapV3Factory factory,
|
|
||||||
IERC20TokenV06[] memory tokenPath,
|
|
||||||
uint256 startIndex
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (IUniswapV3Pool[][] memory poolPaths)
|
|
||||||
{
|
|
||||||
require(
|
|
||||||
tokenPath.length - startIndex >= 2,
|
|
||||||
"UniswapV3Sampler/tokenPath too short"
|
|
||||||
);
|
|
||||||
uint24[4] memory validPoolFees = [
|
|
||||||
// The launch pool fees. Could get hairier if they add more.
|
|
||||||
uint24(0.0001e6),
|
|
||||||
uint24(0.0005e6),
|
|
||||||
uint24(0.003e6),
|
|
||||||
uint24(0.01e6)
|
|
||||||
];
|
|
||||||
IUniswapV3Pool[] memory validPools =
|
|
||||||
new IUniswapV3Pool[](validPoolFees.length);
|
|
||||||
uint256 numValidPools = 0;
|
|
||||||
{
|
|
||||||
IERC20TokenV06 inputToken = tokenPath[startIndex];
|
|
||||||
IERC20TokenV06 outputToken = tokenPath[startIndex + 1];
|
|
||||||
for (uint256 i = 0; i < validPoolFees.length; ++i) {
|
|
||||||
IUniswapV3Pool pool =
|
|
||||||
factory.getPool(inputToken, outputToken, validPoolFees[i]);
|
|
||||||
if (_isValidPool(pool)) {
|
|
||||||
validPools[numValidPools++] = pool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numValidPools == 0) {
|
|
||||||
// No valid pools for this hop.
|
|
||||||
return poolPaths;
|
|
||||||
}
|
|
||||||
if (startIndex + 2 == tokenPath.length) {
|
|
||||||
// End of path.
|
|
||||||
poolPaths = new IUniswapV3Pool[][](numValidPools);
|
|
||||||
for (uint256 i = 0; i < numValidPools; ++i) {
|
|
||||||
poolPaths[i] = new IUniswapV3Pool[](1);
|
|
||||||
poolPaths[i][0] = validPools[i];
|
|
||||||
}
|
|
||||||
return poolPaths;
|
|
||||||
}
|
|
||||||
// Get paths for subsequent hops.
|
|
||||||
IUniswapV3Pool[][] memory subsequentPoolPaths =
|
|
||||||
_getValidPoolPaths(factory, tokenPath, startIndex + 1);
|
|
||||||
if (subsequentPoolPaths.length == 0) {
|
|
||||||
// Could not complete the path.
|
|
||||||
return poolPaths;
|
|
||||||
}
|
|
||||||
// Combine our pools with the next hop paths.
|
|
||||||
poolPaths = new IUniswapV3Pool[][](
|
|
||||||
numValidPools * subsequentPoolPaths.length
|
|
||||||
);
|
|
||||||
for (uint256 i = 0; i < numValidPools; ++i) {
|
|
||||||
for (uint256 j = 0; j < subsequentPoolPaths.length; ++j) {
|
|
||||||
uint256 o = i * subsequentPoolPaths.length + j;
|
|
||||||
// Prepend pool to the subsequent path.
|
|
||||||
poolPaths[o] =
|
|
||||||
new IUniswapV3Pool[](1 + subsequentPoolPaths[j].length);
|
|
||||||
poolPaths[o][0] = validPools[i];
|
|
||||||
for (uint256 k = 0; k < subsequentPoolPaths[j].length; ++k) {
|
|
||||||
poolPaths[o][1 + k] = subsequentPoolPaths[j][k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return poolPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _reverseTokenPath(IERC20TokenV06[] memory tokenPath)
|
|
||||||
private
|
|
||||||
returns (IERC20TokenV06[] memory reversed)
|
|
||||||
{
|
|
||||||
reversed = new IERC20TokenV06[](tokenPath.length);
|
|
||||||
for (uint256 i = 0; i < tokenPath.length; ++i) {
|
|
||||||
reversed[i] = tokenPath[tokenPath.length - i - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _reversePoolPath(IUniswapV3Pool[] memory poolPath)
|
|
||||||
private
|
|
||||||
returns (IUniswapV3Pool[] memory reversed)
|
|
||||||
{
|
|
||||||
reversed = new IUniswapV3Pool[](poolPath.length);
|
|
||||||
for (uint256 i = 0; i < poolPath.length; ++i) {
|
|
||||||
reversed[i] = poolPath[poolPath.length - i - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _isValidPool(IUniswapV3Pool pool)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (bool isValid)
|
|
||||||
{
|
|
||||||
// Check if it has been deployed.
|
|
||||||
{
|
|
||||||
uint256 codeSize;
|
|
||||||
assembly {
|
|
||||||
codeSize := extcodesize(pool)
|
|
||||||
}
|
|
||||||
if (codeSize == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Must have a balance of both tokens.
|
|
||||||
if (pool.token0().balanceOf(address(pool)) == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pool.token1().balanceOf(address(pool)) == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _toUniswapPath(
|
|
||||||
IERC20TokenV06[] memory tokenPath,
|
|
||||||
IUniswapV3Pool[] memory poolPath
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (bytes memory uniswapPath)
|
|
||||||
{
|
|
||||||
require(
|
|
||||||
tokenPath.length >= 2 && tokenPath.length == poolPath.length + 1,
|
|
||||||
"UniswapV3Sampler/invalid path lengths"
|
|
||||||
);
|
|
||||||
// Uniswap paths are tightly packed as:
|
|
||||||
// [token0, token0token1PairFee, token1, token1Token2PairFee, token2, ...]
|
|
||||||
uniswapPath = new bytes(tokenPath.length * 20 + poolPath.length * 3);
|
|
||||||
uint256 o;
|
|
||||||
assembly { o := add(uniswapPath, 32) }
|
|
||||||
for (uint256 i = 0; i < tokenPath.length; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
uint24 poolFee = poolPath[i - 1].fee();
|
|
||||||
assembly {
|
|
||||||
mstore(o, shl(232, poolFee))
|
|
||||||
o := add(o, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IERC20TokenV06 token = tokenPath[i];
|
|
||||||
assembly {
|
|
||||||
mstore(o, shl(96, token))
|
|
||||||
o := add(o, 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
|
||||||
|
|
||||||
contract UtilitySampler {
|
|
||||||
|
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
|
||||||
|
|
||||||
IERC20TokenV06 private immutable UTILITY_ETH_ADDRESS = IERC20TokenV06(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
|
|
||||||
|
|
||||||
function getTokenDecimals(IERC20TokenV06[] memory tokens)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory decimals)
|
|
||||||
{
|
|
||||||
decimals = new uint256[](tokens.length);
|
|
||||||
for (uint256 i = 0; i != tokens.length; i++) {
|
|
||||||
decimals[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
|
||||||
? 18
|
|
||||||
: tokens[i].compatDecimals();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBalanceOf(IERC20TokenV06[] memory tokens, address account)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory balances)
|
|
||||||
{
|
|
||||||
balances = new uint256[](tokens.length);
|
|
||||||
for (uint256 i = 0; i != tokens.length; i++) {
|
|
||||||
balances[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
|
||||||
? account.balance
|
|
||||||
: tokens[i].compatBalanceOf(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllowanceOf(IERC20TokenV06[] memory tokens, address account, address spender)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory allowances)
|
|
||||||
{
|
|
||||||
allowances = new uint256[](tokens.length);
|
|
||||||
for (uint256 i = 0; i != tokens.length; i++) {
|
|
||||||
allowances[i] = tokens[i] == UTILITY_ETH_ADDRESS
|
|
||||||
? 0
|
|
||||||
: tokens[i].compatAllowance(account, spender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isContract(address account)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (bool)
|
|
||||||
{
|
|
||||||
uint256 size;
|
|
||||||
assembly { size := extcodesize(account) }
|
|
||||||
return size > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGasLeft()
|
|
||||||
public
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
return gasleft();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBlockNumber()
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
return block.number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IBalancer {
|
|
||||||
function isBound(address t) external view returns (bool);
|
|
||||||
function getDenormalizedWeight(address token) external view returns (uint256);
|
|
||||||
function getBalance(address token) external view returns (uint256);
|
|
||||||
function getSwapFee() external view returns (uint256);
|
|
||||||
function calcOutGivenIn(
|
|
||||||
uint256 tokenBalanceIn,
|
|
||||||
uint256 tokenWeightIn,
|
|
||||||
uint256 tokenBalanceOut,
|
|
||||||
uint256 tokenWeightOut,
|
|
||||||
uint256 tokenAmountIn,
|
|
||||||
uint256 swapFee
|
|
||||||
) external pure returns (uint256 tokenAmountOut);
|
|
||||||
function calcInGivenOut(
|
|
||||||
uint256 tokenBalanceIn,
|
|
||||||
uint256 tokenWeightIn,
|
|
||||||
uint256 tokenBalanceOut,
|
|
||||||
uint256 tokenWeightOut,
|
|
||||||
uint256 tokenAmountOut,
|
|
||||||
uint256 swapFee
|
|
||||||
) external pure returns (uint256 tokenAmountIn);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IBancor {}
|
|
||||||
|
|
||||||
interface IBancorNetwork {
|
|
||||||
function conversionPath(address _sourceToken, address _targetToken) external view returns (address[] memory);
|
|
||||||
function rateByPath(address[] memory _path, uint256 _amount) external view returns (uint256);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IBancorRegistry {
|
|
||||||
function getAddress(bytes32 _contractName) external view returns (address);
|
|
||||||
function BANCOR_NETWORK() external view returns (bytes32);
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable func-name-mixedcase
|
|
||||||
interface ICurve {
|
|
||||||
|
|
||||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
|
||||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
|
||||||
/// @param i The token index being sold.
|
|
||||||
/// @param j The token index being bought.
|
|
||||||
/// @param sellAmount The amount of token being bought.
|
|
||||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
|
||||||
function exchange_underlying(
|
|
||||||
int128 i,
|
|
||||||
int128 j,
|
|
||||||
uint256 sellAmount,
|
|
||||||
uint256 minBuyAmount
|
|
||||||
)
|
|
||||||
external;
|
|
||||||
|
|
||||||
/// @dev Get the amount of `toToken` by selling `sellAmount` of `fromToken`
|
|
||||||
/// @param i The token index being sold.
|
|
||||||
/// @param j The token index being bought.
|
|
||||||
/// @param sellAmount The amount of token being bought.
|
|
||||||
function get_dy_underlying(
|
|
||||||
int128 i,
|
|
||||||
int128 j,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
returns (uint256 dy);
|
|
||||||
|
|
||||||
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
|
|
||||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
|
||||||
/// @param i The token index being sold.
|
|
||||||
/// @param j The token index being bought.
|
|
||||||
/// @param buyAmount The amount of token being bought.
|
|
||||||
function get_dx_underlying(
|
|
||||||
int128 i,
|
|
||||||
int128 j,
|
|
||||||
uint256 buyAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
returns (uint256 dx);
|
|
||||||
|
|
||||||
/// @dev Get the underlying token address from the token index
|
|
||||||
/// @param i The token index.
|
|
||||||
function underlying_coins(
|
|
||||||
int128 i
|
|
||||||
)
|
|
||||||
external
|
|
||||||
returns (address tokenAddress);
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
// Keepin everything together
|
|
||||||
interface IKyberNetwork {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface IKyberNetworkProxy {
|
|
||||||
|
|
||||||
function getExpectedRateAfterFee(
|
|
||||||
address src,
|
|
||||||
address dest,
|
|
||||||
uint256 srcQty,
|
|
||||||
uint256 platformFeeBps,
|
|
||||||
bytes calldata hint
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 expectedRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IKyberHintHandler {
|
|
||||||
|
|
||||||
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
|
|
||||||
|
|
||||||
enum ProcessWithRate {NotRequired, Required}
|
|
||||||
|
|
||||||
function getTradingReserves(
|
|
||||||
address tokenSrc,
|
|
||||||
address tokenDest,
|
|
||||||
bool isTokenToToken,
|
|
||||||
bytes calldata hint
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (
|
|
||||||
bytes32[] memory reserveIds,
|
|
||||||
uint256[] memory splitValuesBps,
|
|
||||||
ProcessWithRate processWithRate
|
|
||||||
);
|
|
||||||
|
|
||||||
function buildTokenToEthHint(
|
|
||||||
address tokenSrc,
|
|
||||||
TradeType tokenToEthType,
|
|
||||||
bytes32[] calldata tokenToEthReserveIds,
|
|
||||||
uint256[] calldata tokenToEthSplits
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (bytes memory hint);
|
|
||||||
|
|
||||||
function buildEthToTokenHint(
|
|
||||||
address tokenDest,
|
|
||||||
TradeType ethToTokenType,
|
|
||||||
bytes32[] calldata ethToTokenReserveIds,
|
|
||||||
uint256[] calldata ethToTokenSplits
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (bytes memory hint);
|
|
||||||
|
|
||||||
function buildTokenToTokenHint(
|
|
||||||
address tokenSrc,
|
|
||||||
TradeType tokenToEthType,
|
|
||||||
bytes32[] calldata tokenToEthReserveIds,
|
|
||||||
uint256[] calldata tokenToEthSplits,
|
|
||||||
address tokenDest,
|
|
||||||
TradeType ethToTokenType,
|
|
||||||
bytes32[] calldata ethToTokenReserveIds,
|
|
||||||
uint256[] calldata ethToTokenSplits
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (bytes memory hint);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IMStable {
|
|
||||||
|
|
||||||
function getSwapOutput(
|
|
||||||
address _input,
|
|
||||||
address _output,
|
|
||||||
uint256 _quantity
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 swapOutput);
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IMooniswapRegistry {
|
|
||||||
|
|
||||||
function pools(address token1, address token2) external view returns(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMooniswap {
|
|
||||||
|
|
||||||
function getReturn(
|
|
||||||
address fromToken,
|
|
||||||
address destToken,
|
|
||||||
uint256 amount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns(uint256 returnAmount);
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IMultiBridge {
|
|
||||||
|
|
||||||
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
|
|
||||||
/// @param tokenAddress The address of the ERC20 token to transfer.
|
|
||||||
/// @param from Address to transfer asset from.
|
|
||||||
/// @param to Address to transfer asset to.
|
|
||||||
/// @param amount Amount of asset to transfer.
|
|
||||||
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
|
|
||||||
/// @return success The magic bytes `0xdc1600f3` if successful.
|
|
||||||
function bridgeTransferFrom(
|
|
||||||
address tokenAddress,
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 amount,
|
|
||||||
bytes calldata bridgeData
|
|
||||||
)
|
|
||||||
external
|
|
||||||
returns (bytes4 success);
|
|
||||||
|
|
||||||
/// @dev Quotes the amount of `makerToken` that would be obtained by
|
|
||||||
/// selling `sellAmount` of `takerToken`.
|
|
||||||
/// @param takerToken Address of the taker token (what to sell).
|
|
||||||
/// @param intermediateToken The address of the intermediate token to
|
|
||||||
/// use in an indirect route.
|
|
||||||
/// @param makerToken Address of the maker token (what to buy).
|
|
||||||
/// @param sellAmount Amount of `takerToken` to sell.
|
|
||||||
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
|
|
||||||
function getSellQuote(
|
|
||||||
address takerToken,
|
|
||||||
address intermediateToken,
|
|
||||||
address makerToken,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 makerTokenAmount);
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IShell {
|
|
||||||
|
|
||||||
function viewOriginSwap (
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 fromAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 toAmount);
|
|
||||||
|
|
||||||
function viewTargetSwap (
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 toAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 fromAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface ISmoothy {
|
|
||||||
|
|
||||||
function getBalance (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 balance);
|
|
||||||
|
|
||||||
function _yBalances (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 balance);
|
|
||||||
|
|
||||||
function getTokenStats (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IUniswapExchangeQuotes {
|
|
||||||
|
|
||||||
function getEthToTokenInputPrice(
|
|
||||||
uint256 ethSold
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 tokensBought);
|
|
||||||
|
|
||||||
function getEthToTokenOutputPrice(
|
|
||||||
uint256 tokensBought
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 ethSold);
|
|
||||||
|
|
||||||
function getTokenToEthInputPrice(
|
|
||||||
uint256 tokensSold
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 ethBought);
|
|
||||||
|
|
||||||
function getTokenToEthOutputPrice(
|
|
||||||
uint256 ethBought
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 tokensSold);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface IUniswapV2Router01 {
|
|
||||||
|
|
||||||
function getAmountsOut(uint256 amountIn, address[] calldata path)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts);
|
|
||||||
|
|
||||||
function getAmountsIn(uint256 amountOut, address[] calldata path)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
|
|
||||||
contract DummyLiquidityProvider
|
|
||||||
{
|
|
||||||
/// @dev Quotes the amount of `makerToken` that would be obtained by
|
|
||||||
/// selling `sellAmount` of `takerToken`.
|
|
||||||
/// @param sellAmount Amount of `takerToken` to sell.
|
|
||||||
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
|
|
||||||
function getSellQuote(
|
|
||||||
address, /* takerToken */
|
|
||||||
address, /* makerToken */
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 makerTokenAmount)
|
|
||||||
{
|
|
||||||
makerTokenAmount = sellAmount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Quotes the amount of `takerToken` that would need to be sold in
|
|
||||||
/// order to obtain `buyAmount` of `makerToken`.
|
|
||||||
/// @param buyAmount Amount of `makerToken` to buy.
|
|
||||||
/// @return takerTokenAmount Amount of `takerToken` that would need to be sold.
|
|
||||||
function getBuyQuote(
|
|
||||||
address, /* takerToken */
|
|
||||||
address, /* makerToken */
|
|
||||||
uint256 buyAmount
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 takerTokenAmount)
|
|
||||||
{
|
|
||||||
takerTokenAmount = buyAmount + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,455 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "../src/ERC20BridgeSampler.sol";
|
|
||||||
import "../src/interfaces/IKyberNetwork.sol";
|
|
||||||
import "../src/interfaces/IUniswapV2Router01.sol";
|
|
||||||
|
|
||||||
|
|
||||||
library LibDeterministicQuotes {
|
|
||||||
|
|
||||||
address private constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
||||||
uint256 private constant RATE_DENOMINATOR = 1 ether;
|
|
||||||
uint256 private constant MIN_RATE = RATE_DENOMINATOR / 100;
|
|
||||||
uint256 private constant MAX_RATE = 100 * RATE_DENOMINATOR;
|
|
||||||
uint8 private constant MIN_DECIMALS = 4;
|
|
||||||
uint8 private constant MAX_DECIMALS = 20;
|
|
||||||
|
|
||||||
function getDeterministicSellQuote(
|
|
||||||
bytes32 salt,
|
|
||||||
address sellToken,
|
|
||||||
address buyToken,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken);
|
|
||||||
uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken);
|
|
||||||
uint256 rate = getDeterministicRate(salt, sellToken, buyToken);
|
|
||||||
return sellAmount * rate * buyBase / sellBase / RATE_DENOMINATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeterministicBuyQuote(
|
|
||||||
bytes32 salt,
|
|
||||||
address sellToken,
|
|
||||||
address buyToken,
|
|
||||||
uint256 buyAmount
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (uint256 sellAmount)
|
|
||||||
{
|
|
||||||
uint256 sellBase = uint256(10) ** getDeterministicTokenDecimals(sellToken);
|
|
||||||
uint256 buyBase = uint256(10) ** getDeterministicTokenDecimals(buyToken);
|
|
||||||
uint256 rate = getDeterministicRate(salt, sellToken, buyToken);
|
|
||||||
return buyAmount * RATE_DENOMINATOR * sellBase / rate / buyBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeterministicTokenDecimals(address token)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (uint8 decimals)
|
|
||||||
{
|
|
||||||
if (token == WETH_ADDRESS) {
|
|
||||||
return 18;
|
|
||||||
}
|
|
||||||
bytes32 seed = keccak256(abi.encodePacked(token));
|
|
||||||
return uint8(uint256(seed) % (MAX_DECIMALS - MIN_DECIMALS)) + MIN_DECIMALS;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeterministicRate(bytes32 salt, address sellToken, address buyToken)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (uint256 rate)
|
|
||||||
{
|
|
||||||
bytes32 seed = keccak256(abi.encodePacked(salt, sellToken, buyToken));
|
|
||||||
return uint256(seed) % (MAX_RATE - MIN_RATE) + MIN_RATE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contract TestDeploymentConstants {
|
|
||||||
|
|
||||||
// solhint-disable separate-by-one-line-in-contract
|
|
||||||
|
|
||||||
// Mainnet addresses ///////////////////////////////////////////////////////
|
|
||||||
/// @dev Mainnet address of the WETH contract.
|
|
||||||
address constant private WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
||||||
|
|
||||||
/// @dev Overridable way to get the WETH address.
|
|
||||||
/// @return wethAddress The WETH address.
|
|
||||||
function _getWethAddress()
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (address wethAddress)
|
|
||||||
{
|
|
||||||
return WETH_ADDRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
contract FailTrigger {
|
|
||||||
|
|
||||||
// Give this address a balance to force operations to fail.
|
|
||||||
address payable constant public FAILURE_ADDRESS = 0xe9dB8717BC5DFB20aaf538b4a5a02B7791FF430C;
|
|
||||||
|
|
||||||
// Funds `FAILURE_ADDRESS`.
|
|
||||||
function enableFailTrigger() external payable {
|
|
||||||
FAILURE_ADDRESS.transfer(msg.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _revertIfShouldFail() internal view {
|
|
||||||
if (FAILURE_ADDRESS.balance != 0) {
|
|
||||||
revert("FAIL_TRIGGERED");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSamplerUniswapExchange is
|
|
||||||
IUniswapExchangeQuotes,
|
|
||||||
TestDeploymentConstants,
|
|
||||||
FailTrigger
|
|
||||||
{
|
|
||||||
bytes32 constant private BASE_SALT = 0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab;
|
|
||||||
|
|
||||||
address public tokenAddress;
|
|
||||||
bytes32 public salt;
|
|
||||||
|
|
||||||
constructor(address _tokenAddress) public {
|
|
||||||
tokenAddress = _tokenAddress;
|
|
||||||
salt = keccak256(abi.encodePacked(BASE_SALT, _tokenAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IUniswapExchangeQuotes.getEthToTokenInputPrice()`.
|
|
||||||
function getEthToTokenInputPrice(
|
|
||||||
uint256 ethSold
|
|
||||||
)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 tokensBought)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
|
||||||
salt,
|
|
||||||
tokenAddress,
|
|
||||||
_getWethAddress(),
|
|
||||||
ethSold
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IUniswapExchangeQuotes.getEthToTokenOutputPrice()`.
|
|
||||||
function getEthToTokenOutputPrice(
|
|
||||||
uint256 tokensBought
|
|
||||||
)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 ethSold)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
|
||||||
salt,
|
|
||||||
_getWethAddress(),
|
|
||||||
tokenAddress,
|
|
||||||
tokensBought
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IUniswapExchangeQuotes.getTokenToEthInputPrice()`.
|
|
||||||
function getTokenToEthInputPrice(
|
|
||||||
uint256 tokensSold
|
|
||||||
)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 ethBought)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
return LibDeterministicQuotes.getDeterministicSellQuote(
|
|
||||||
salt,
|
|
||||||
tokenAddress,
|
|
||||||
_getWethAddress(),
|
|
||||||
tokensSold
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IUniswapExchangeQuotes.getTokenToEthOutputPrice()`.
|
|
||||||
function getTokenToEthOutputPrice(
|
|
||||||
uint256 ethBought
|
|
||||||
)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 tokensSold)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
return LibDeterministicQuotes.getDeterministicBuyQuote(
|
|
||||||
salt,
|
|
||||||
_getWethAddress(),
|
|
||||||
tokenAddress,
|
|
||||||
ethBought
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSamplerUniswapV2Router01 is
|
|
||||||
IUniswapV2Router01,
|
|
||||||
TestDeploymentConstants,
|
|
||||||
FailTrigger
|
|
||||||
{
|
|
||||||
bytes32 constant private SALT = 0xadc7fcb33c735913b8635927e66896b356a53a912ab2ceff929e60a04b53b3c1;
|
|
||||||
|
|
||||||
// Deterministic `IUniswapV2Router01.getAmountsOut()`.
|
|
||||||
function getAmountsOut(uint256 amountIn, address[] calldata path)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
require(path.length >= 2, "PATH_TOO_SHORT");
|
|
||||||
_revertIfShouldFail();
|
|
||||||
amounts = new uint256[](path.length);
|
|
||||||
amounts[0] = amountIn;
|
|
||||||
for (uint256 i = 0; i < path.length - 1; ++i) {
|
|
||||||
amounts[i + 1] = LibDeterministicQuotes.getDeterministicSellQuote(
|
|
||||||
SALT,
|
|
||||||
path[i],
|
|
||||||
path[i + 1],
|
|
||||||
amounts[i]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IUniswapV2Router01.getAmountsInt()`.
|
|
||||||
function getAmountsIn(uint256 amountOut, address[] calldata path)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256[] memory amounts)
|
|
||||||
{
|
|
||||||
require(path.length >= 2, "PATH_TOO_SHORT");
|
|
||||||
_revertIfShouldFail();
|
|
||||||
amounts = new uint256[](path.length);
|
|
||||||
amounts[path.length - 1] = amountOut;
|
|
||||||
for (uint256 i = path.length - 1; i > 0; --i) {
|
|
||||||
amounts[i - 1] = LibDeterministicQuotes.getDeterministicBuyQuote(
|
|
||||||
SALT,
|
|
||||||
path[i - 1],
|
|
||||||
path[i],
|
|
||||||
amounts[i]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable space-after-comma
|
|
||||||
contract TestERC20BridgeSamplerKyberNetwork is
|
|
||||||
TestDeploymentConstants,
|
|
||||||
FailTrigger
|
|
||||||
{
|
|
||||||
bytes32 constant private SALT = 0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7;
|
|
||||||
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
|
||||||
|
|
||||||
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
|
|
||||||
enum ProcessWithRate {NotRequired, Required}
|
|
||||||
|
|
||||||
// IKyberHintHandler
|
|
||||||
function buildTokenToEthHint(
|
|
||||||
address tokenSrc,
|
|
||||||
TradeType /* tokenToEthType */,
|
|
||||||
bytes32[] calldata /* tokenToEthReserveIds */,
|
|
||||||
uint256[] calldata /* tokenToEthSplits */
|
|
||||||
) external view returns (bytes memory hint)
|
|
||||||
{
|
|
||||||
return abi.encode(tokenSrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEthToTokenHint(
|
|
||||||
address tokenDest,
|
|
||||||
TradeType /* ethToTokenType */,
|
|
||||||
bytes32[] calldata /* ethToTokenReserveIds */,
|
|
||||||
uint256[] calldata /* ethToTokenSplits */
|
|
||||||
) external view returns (bytes memory hint)
|
|
||||||
{
|
|
||||||
return abi.encode(tokenDest);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IKyberHintHandler
|
|
||||||
function buildTokenToTokenHint(
|
|
||||||
address tokenSrc,
|
|
||||||
TradeType /* tokenToEthType */,
|
|
||||||
bytes32[] calldata /* tokenToEthReserveIds */,
|
|
||||||
uint256[] calldata /* tokenToEthSplits */,
|
|
||||||
address /* tokenDest */,
|
|
||||||
TradeType /* EthToTokenType */,
|
|
||||||
bytes32[] calldata /* EthToTokenReserveIds */,
|
|
||||||
uint256[] calldata /* EthToTokenSplits */
|
|
||||||
) external view returns (bytes memory hint)
|
|
||||||
{
|
|
||||||
return abi.encode(tokenSrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IKyberHintHandler
|
|
||||||
function getTradingReserves(
|
|
||||||
address tokenSrc,
|
|
||||||
address tokenDest,
|
|
||||||
bool isTokenToToken,
|
|
||||||
bytes calldata hint
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (
|
|
||||||
bytes32[] memory reserveIds,
|
|
||||||
uint256[] memory splitValuesBps,
|
|
||||||
ProcessWithRate processWithRate
|
|
||||||
)
|
|
||||||
{
|
|
||||||
reserveIds = new bytes32[](1);
|
|
||||||
reserveIds[0] = bytes32(uint256(1));
|
|
||||||
splitValuesBps = new uint256[](0);
|
|
||||||
processWithRate = ProcessWithRate.NotRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IKyberNetworkProxy.getExpectedRateAfterFee()`.
|
|
||||||
function getExpectedRateAfterFee(
|
|
||||||
address fromToken,
|
|
||||||
address toToken,
|
|
||||||
uint256 /* srcQty */,
|
|
||||||
uint256 /* fee */,
|
|
||||||
bytes calldata /* hint */
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns
|
|
||||||
(uint256 expectedRate)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
|
||||||
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
|
||||||
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
|
||||||
SALT,
|
|
||||||
fromToken,
|
|
||||||
toToken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deterministic `IKyberNetworkProxy.getExpectedRate()`.
|
|
||||||
function getExpectedRate(
|
|
||||||
address fromToken,
|
|
||||||
address toToken,
|
|
||||||
uint256
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 expectedRate, uint256)
|
|
||||||
{
|
|
||||||
_revertIfShouldFail();
|
|
||||||
fromToken = fromToken == ETH_ADDRESS ? _getWethAddress() : fromToken;
|
|
||||||
toToken = toToken == ETH_ADDRESS ? _getWethAddress() : toToken;
|
|
||||||
expectedRate = LibDeterministicQuotes.getDeterministicRate(
|
|
||||||
SALT,
|
|
||||||
fromToken,
|
|
||||||
toToken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSamplerUniswapExchangeFactory is
|
|
||||||
IUniswapExchangeFactory
|
|
||||||
{
|
|
||||||
mapping (address => IUniswapExchangeQuotes) private _exchangesByToken;
|
|
||||||
|
|
||||||
// Creates Uniswap exchange contracts for tokens.
|
|
||||||
function createTokenExchanges(address[] calldata tokenAddresses)
|
|
||||||
external
|
|
||||||
{
|
|
||||||
for (uint256 i = 0; i < tokenAddresses.length; i++) {
|
|
||||||
address tokenAddress = tokenAddresses[i];
|
|
||||||
_exchangesByToken[tokenAddress] =
|
|
||||||
new TestERC20BridgeSamplerUniswapExchange(tokenAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `IUniswapExchangeFactory.getExchange()`.
|
|
||||||
function getExchange(address tokenAddress)
|
|
||||||
override
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (address)
|
|
||||||
{
|
|
||||||
return address(_exchangesByToken[tokenAddress]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
contract TestERC20BridgeSampler is
|
|
||||||
ERC20BridgeSampler,
|
|
||||||
FailTrigger
|
|
||||||
{
|
|
||||||
TestERC20BridgeSamplerUniswapExchangeFactory public uniswap;
|
|
||||||
TestERC20BridgeSamplerUniswapV2Router01 public uniswapV2Router;
|
|
||||||
TestERC20BridgeSamplerKyberNetwork public kyber;
|
|
||||||
|
|
||||||
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
|
|
||||||
|
|
||||||
constructor() public ERC20BridgeSampler() {
|
|
||||||
uniswap = new TestERC20BridgeSamplerUniswapExchangeFactory();
|
|
||||||
uniswapV2Router = new TestERC20BridgeSamplerUniswapV2Router01();
|
|
||||||
kyber = new TestERC20BridgeSamplerKyberNetwork();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates Uniswap exchange contracts for tokens.
|
|
||||||
function createTokenExchanges(address[] calldata tokenAddresses)
|
|
||||||
external
|
|
||||||
{
|
|
||||||
uniswap.createTokenExchanges(tokenAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overridden to return deterministic states.
|
|
||||||
function getLimitOrderFillableTakerAmount(
|
|
||||||
IExchange.LimitOrder memory order,
|
|
||||||
IExchange.Signature memory,
|
|
||||||
IExchange
|
|
||||||
)
|
|
||||||
override
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256 fillableTakerAmount)
|
|
||||||
{
|
|
||||||
return uint256(keccak256(abi.encode(order.salt))) % order.takerAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overriden to return deterministic decimals.
|
|
||||||
function _getTokenDecimals(address tokenAddress)
|
|
||||||
override
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint8 decimals)
|
|
||||||
{
|
|
||||||
return LibDeterministicQuotes.getDeterministicTokenDecimals(tokenAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "../src/NativeOrderSampler.sol";
|
|
||||||
import "../src/UtilitySampler.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TestNativeOrderSamplerToken {
|
|
||||||
mapping (address => uint256) public balanceOf;
|
|
||||||
mapping (address => mapping(address => uint256)) public allowance;
|
|
||||||
|
|
||||||
function setBalanceAndAllowance(
|
|
||||||
address owner,
|
|
||||||
address spender,
|
|
||||||
uint256 balance,
|
|
||||||
uint256 allowance_
|
|
||||||
)
|
|
||||||
external
|
|
||||||
{
|
|
||||||
balanceOf[owner] = balance;
|
|
||||||
allowance[owner][spender] = allowance_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contract TestNativeOrderSampler is
|
|
||||||
NativeOrderSampler,
|
|
||||||
UtilitySampler
|
|
||||||
{
|
|
||||||
uint8 private constant MAX_ORDER_STATUS = uint8(IExchange.OrderStatus.CANCELLED) + 1;
|
|
||||||
bytes32 private constant VALID_SIGNATURE_HASH = bytes32(hex"01");
|
|
||||||
|
|
||||||
function createTokens(uint256 count)
|
|
||||||
external
|
|
||||||
returns (TestNativeOrderSamplerToken[] memory tokens)
|
|
||||||
{
|
|
||||||
tokens = new TestNativeOrderSamplerToken[](count);
|
|
||||||
for (uint256 i = 0; i < count; ++i) {
|
|
||||||
tokens[i] = new TestNativeOrderSamplerToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTokenBalanceAndAllowance(
|
|
||||||
TestNativeOrderSamplerToken token,
|
|
||||||
address owner,
|
|
||||||
address spender,
|
|
||||||
uint256 balance,
|
|
||||||
uint256 allowance
|
|
||||||
)
|
|
||||||
external
|
|
||||||
{
|
|
||||||
token.setBalanceAndAllowance(owner, spender, balance, allowance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IExchange.getLimitOrderRelevantState()
|
|
||||||
function getLimitOrderRelevantState(
|
|
||||||
IExchange.LimitOrder memory order,
|
|
||||||
IExchange.Signature calldata signature
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (
|
|
||||||
IExchange.OrderInfo memory orderInfo,
|
|
||||||
uint128 actualFillableTakerTokenAmount,
|
|
||||||
bool isSignatureValid
|
|
||||||
)
|
|
||||||
{
|
|
||||||
// The order salt determines everything.
|
|
||||||
orderInfo.orderHash = keccak256(abi.encode(order.salt));
|
|
||||||
if (uint8(order.salt) == 0xFF) {
|
|
||||||
orderInfo.status = IExchange.OrderStatus.FILLED;
|
|
||||||
} else {
|
|
||||||
orderInfo.status = IExchange.OrderStatus.FILLABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSignatureValid = signature.r == VALID_SIGNATURE_HASH;
|
|
||||||
|
|
||||||
// The expiration time is the filled taker asset amount.
|
|
||||||
orderInfo.takerTokenFilledAmount = uint128(order.expiry);
|
|
||||||
|
|
||||||
// Calculate how much is fillable in maker terms given the filled taker amount
|
|
||||||
uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor(
|
|
||||||
uint256(
|
|
||||||
order.takerAmount
|
|
||||||
- orderInfo.takerTokenFilledAmount
|
|
||||||
),
|
|
||||||
uint256(order.takerAmount),
|
|
||||||
uint256(order.makerAmount)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Take the min of the balance/allowance and the fillable maker amount
|
|
||||||
fillableMakerTokenAmount = LibSafeMathV06.min256(
|
|
||||||
fillableMakerTokenAmount,
|
|
||||||
_getSpendableERC20BalanceOf(order.makerToken, order.maker)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert to taker terms
|
|
||||||
actualFillableTakerTokenAmount = LibMathV06.getPartialAmountCeil(
|
|
||||||
fillableMakerTokenAmount,
|
|
||||||
uint256(order.makerAmount),
|
|
||||||
uint256(order.takerAmount)
|
|
||||||
).safeDowncastToUint128();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getSpendableERC20BalanceOf(
|
|
||||||
IERC20TokenV06 token,
|
|
||||||
address owner
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
view
|
|
||||||
returns (uint256)
|
|
||||||
{
|
|
||||||
return LibSafeMathV06.min256(
|
|
||||||
token.allowance(owner, address(this)),
|
|
||||||
token.balanceOf(owner)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,13 +33,12 @@
|
|||||||
"generate_contract_wrappers": "abi-gen --debug --abis ${npm_package_config_abis} --output test/generated-wrappers --backend ethers",
|
"generate_contract_wrappers": "abi-gen --debug --abis ${npm_package_config_abis} --output test/generated-wrappers --backend ethers",
|
||||||
"contracts:gen": "contracts-gen generate",
|
"contracts:gen": "contracts-gen generate",
|
||||||
"contracts:copy": "contracts-gen copy",
|
"contracts:copy": "contracts-gen copy",
|
||||||
"publish:private": "yarn build && gitpkg publish",
|
"publish:private": "yarn build && gitpkg publish"
|
||||||
"sampler-size": "jq .compilerOutput.evm.deployedBytecode.object -- test/generated-artifacts/ERC20BridgeSampler.json | echo $(( $(wc -c) / 2 - 1 ))"
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
"publicInterfaceContracts": "BalanceChecker,FakeTaker",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
|
"abis": "./test/generated-artifacts/@(BalanceChecker|FakeTaker).json",
|
||||||
"postpublish": {
|
"postpublish": {
|
||||||
"assets": []
|
"assets": []
|
||||||
}
|
}
|
||||||
@@ -58,21 +57,21 @@
|
|||||||
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^3.0.31",
|
"@0x/assert": "^3.0.32",
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/contract-wrappers": "^13.19.0",
|
"@0x/contract-wrappers": "^13.19.0",
|
||||||
"@0x/contracts-erc20": "^3.3.26",
|
"@0x/contracts-erc20": "^3.3.26",
|
||||||
"@0x/contracts-zero-ex": "^0.31.0",
|
"@0x/contracts-zero-ex": "^0.31.0",
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/json-schemas": "^6.4.1",
|
"@0x/json-schemas": "^6.4.2",
|
||||||
"@0x/neon-router": "^0.3.3",
|
"@0x/neon-router": "^0.3.3",
|
||||||
"@0x/protocol-utils": "^1.11.0",
|
"@0x/protocol-utils": "^1.11.0",
|
||||||
"@0x/quote-server": "^6.0.6",
|
"@0x/quote-server": "^6.0.6",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"@balancer-labs/sor": "0.3.2",
|
"@balancer-labs/sor": "0.3.2",
|
||||||
"@bancor/sdk": "0.2.9",
|
"@bancor/sdk": "0.2.9",
|
||||||
"@ethersproject/abi": "^5.0.1",
|
"@ethersproject/abi": "^5.0.1",
|
||||||
@@ -80,11 +79,12 @@
|
|||||||
"@ethersproject/contracts": "^5.0.1",
|
"@ethersproject/contracts": "^5.0.1",
|
||||||
"@ethersproject/providers": "^5.0.4",
|
"@ethersproject/providers": "^5.0.4",
|
||||||
"@ethersproject/strings": "^5.0.10",
|
"@ethersproject/strings": "^5.0.10",
|
||||||
"axios": "^0.21.1",
|
"@open-rpc/client-js": "^1.7.1",
|
||||||
"axios-mock-adapter": "^1.19.0",
|
"axios": "^0.24.0",
|
||||||
|
"axios-mock-adapter": "^1.20.0",
|
||||||
"cream-sor": "^0.3.3",
|
"cream-sor": "^0.3.3",
|
||||||
"decimal.js": "^10.2.0",
|
"decimal.js": "^10.2.0",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethereumjs-util": "^7.0.10",
|
"ethereumjs-util": "^7.0.10",
|
||||||
"fast-abi": "^0.0.4",
|
"fast-abi": "^0.0.4",
|
||||||
"graphql": "^15.4.0",
|
"graphql": "^15.4.0",
|
||||||
@@ -93,20 +93,20 @@
|
|||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^5.7.2",
|
"@0x/abi-gen": "^5.7.3",
|
||||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||||
"@0x/contracts-exchange": "^3.2.38",
|
"@0x/contracts-exchange": "^3.2.38",
|
||||||
"@0x/contracts-exchange-libs": "^4.3.37",
|
"@0x/contracts-exchange-libs": "^4.3.37",
|
||||||
"@0x/contracts-gen": "^2.0.43",
|
"@0x/contracts-gen": "^2.0.44",
|
||||||
"@0x/contracts-test-utils": "^5.4.17",
|
"@0x/contracts-test-utils": "^5.4.17",
|
||||||
"@0x/contracts-utils": "^4.8.7",
|
"@0x/contracts-utils": "^4.8.7",
|
||||||
"@0x/mesh-rpc-client": "^9.4.2",
|
"@0x/mesh-rpc-client": "^9.4.2",
|
||||||
"@0x/migrations": "^8.1.15",
|
"@0x/migrations": "^8.1.15",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "12.12.54",
|
"@types/node": "12.12.54",
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
|
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
|
||||||
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
|
|
||||||
import * as FakeTaker from '../generated-artifacts/FakeTaker.json';
|
import * as FakeTaker from '../generated-artifacts/FakeTaker.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
|
||||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||||
FakeTaker: FakeTaker as ContractArtifact,
|
FakeTaker: FakeTaker as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ChainId } from '@0x/contract-addresses';
|
|
||||||
import { SignatureType } from '@0x/protocol-utils';
|
import { SignatureType } from '@0x/protocol-utils';
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber, logUtils } from '@0x/utils';
|
||||||
|
|
||||||
@@ -11,12 +10,9 @@ import {
|
|||||||
RfqRequestOpts,
|
RfqRequestOpts,
|
||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterOpts,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
import {
|
import {
|
||||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
|
||||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
|
||||||
} from './utils/market_operation_utils/constants';
|
} from './utils/market_operation_utils/constants';
|
||||||
|
|
||||||
const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
|
const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
|
||||||
@@ -28,6 +24,7 @@ const ONE_SECOND_MS = 1000;
|
|||||||
const ONE_MINUTE_SECS = 60;
|
const ONE_MINUTE_SECS = 60;
|
||||||
const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
|
const ONE_MINUTE_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
|
||||||
const DEFAULT_PER_PAGE = 1000;
|
const DEFAULT_PER_PAGE = 1000;
|
||||||
|
const ZERO_AMOUNT = new BigNumber(0);
|
||||||
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
|
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
|
||||||
|
|
||||||
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
||||||
@@ -42,21 +39,6 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(0);
|
|||||||
// default 50% buffer for selecting native orders to be aggregated with other sources
|
// default 50% buffer for selecting native orders to be aggregated with other sources
|
||||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
||||||
|
|
||||||
export const ZERO_AMOUNT = new BigNumber(0);
|
|
||||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
|
||||||
chainId: ChainId.Mainnet,
|
|
||||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
|
||||||
...DEFAULT_ORDER_PRUNER_OPTS,
|
|
||||||
samplerGasLimit: 500e6,
|
|
||||||
ethGasStationUrl: ETH_GAS_STATION_API_URL,
|
|
||||||
rfqt: {
|
|
||||||
integratorsWhitelist: [],
|
|
||||||
makerAssetOfferings: {},
|
|
||||||
txOriginBlacklist: new Set(),
|
|
||||||
},
|
|
||||||
tokenAdjacencyGraph: DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID[ChainId.Mainnet],
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts = {
|
||||||
isFromETH: false,
|
isFromETH: false,
|
||||||
isToETH: false,
|
isToETH: false,
|
||||||
@@ -91,8 +73,6 @@ export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) =>
|
|||||||
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r: EMPTY_BYTES32, s: EMPTY_BYTES32 };
|
export const INVALID_SIGNATURE = { signatureType: SignatureType.Invalid, v: 1, r: EMPTY_BYTES32, s: EMPTY_BYTES32 };
|
||||||
|
|
||||||
export { DEFAULT_FEE_SCHEDULE, DEFAULT_GAS_SCHEDULE } from './utils/market_operation_utils/constants';
|
|
||||||
|
|
||||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
||||||
|
|
||||||
// tslint:disable-next-line: custom-no-magic-numbers
|
// tslint:disable-next-line: custom-no-magic-numbers
|
||||||
@@ -111,8 +91,6 @@ export const constants = {
|
|||||||
ONE_AMOUNT: new BigNumber(1),
|
ONE_AMOUNT: new BigNumber(1),
|
||||||
ONE_SECOND_MS,
|
ONE_SECOND_MS,
|
||||||
ONE_MINUTE_MS,
|
ONE_MINUTE_MS,
|
||||||
DEFAULT_SWAP_QUOTER_OPTS,
|
|
||||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
|
||||||
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||||
DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS,
|
DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS,
|
||||||
DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export {
|
|||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
SwapQuoteOrdersBreakdown,
|
SwapQuoteOrdersBreakdown,
|
||||||
|
SwapQuoteMultiHopBreakdown,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterError,
|
SwapQuoterError,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
@@ -117,48 +118,24 @@ export {
|
|||||||
} from './types';
|
} from './types';
|
||||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||||
export {
|
export {
|
||||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
|
||||||
DEFAULT_GAS_SCHEDULE,
|
|
||||||
SOURCE_FLAGS,
|
SOURCE_FLAGS,
|
||||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||||
} from './utils/market_operation_utils/constants';
|
} from './utils/market_operation_utils/constants';
|
||||||
export {
|
export {
|
||||||
Parameters,
|
|
||||||
SamplerContractCall,
|
|
||||||
SamplerContractOperation,
|
|
||||||
} from './utils/market_operation_utils/sampler_contract_operation';
|
|
||||||
export {
|
|
||||||
BalancerFillData,
|
|
||||||
BancorFillData,
|
|
||||||
CollapsedFill,
|
CollapsedFill,
|
||||||
CurveFillData,
|
|
||||||
CurveFunctionSelectors,
|
CurveFunctionSelectors,
|
||||||
CurveInfo,
|
CurveInfo,
|
||||||
DexSample,
|
DexSample,
|
||||||
DODOFillData,
|
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
ExchangeProxyOverhead,
|
ExchangeProxyOverhead,
|
||||||
FeeSchedule,
|
|
||||||
Fill,
|
Fill,
|
||||||
FillData,
|
|
||||||
GetMarketOrdersRfqOpts,
|
GetMarketOrdersRfqOpts,
|
||||||
KyberFillData,
|
|
||||||
LiquidityProviderFillData,
|
|
||||||
LiquidityProviderRegistry,
|
LiquidityProviderRegistry,
|
||||||
MarketDepth,
|
MarketDepth,
|
||||||
MarketDepthSide,
|
MarketDepthSide,
|
||||||
MooniswapFillData,
|
|
||||||
MultiHopFillData,
|
|
||||||
NativeCollapsedFill,
|
|
||||||
NativeRfqOrderFillData,
|
|
||||||
NativeLimitOrderFillData,
|
|
||||||
NativeFillData,
|
|
||||||
OptimizedMarketOrder,
|
|
||||||
SourceQuoteOperation,
|
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
UniswapV2FillData,
|
|
||||||
} from './utils/market_operation_utils/types';
|
} from './utils/market_operation_utils/types';
|
||||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
export {
|
export {
|
||||||
@@ -177,7 +154,7 @@ export {
|
|||||||
PriceComparisonsReport,
|
PriceComparisonsReport,
|
||||||
} from './utils/quote_report_generator';
|
} from './utils/quote_report_generator';
|
||||||
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
||||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
export { BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||||
export type Native = ERC20BridgeSource.Native;
|
export type Native = ERC20BridgeSource.Native;
|
||||||
export type MultiHop = ERC20BridgeSource.MultiHop;
|
export type MultiHop = ERC20BridgeSource.MultiHop;
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
|
|
||||||
import { ZERO_AMOUNT } from '../constants';
|
|
||||||
export interface AaveInfo {
|
|
||||||
lendingPool: string;
|
|
||||||
aToken: string;
|
|
||||||
underlyingToken: string;
|
|
||||||
}
|
|
||||||
// tslint:disable-next-line:no-unnecessary-class
|
|
||||||
export class AaveV2Sampler {
|
|
||||||
public static sampleSellsFromAaveV2(
|
|
||||||
aaveInfo: AaveInfo,
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
takerTokenAmounts: BigNumber[],
|
|
||||||
): BigNumber[] {
|
|
||||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
|
||||||
if (
|
|
||||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
|
||||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
|
||||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
|
||||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
|
||||||
) {
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not matching the reserve return 0 results
|
|
||||||
const numSamples = takerTokenAmounts.length;
|
|
||||||
|
|
||||||
const makerTokenAmounts = new Array(numSamples);
|
|
||||||
makerTokenAmounts.fill(ZERO_AMOUNT);
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static sampleBuysFromAaveV2(
|
|
||||||
aaveInfo: AaveInfo,
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
makerTokenAmounts: BigNumber[],
|
|
||||||
): BigNumber[] {
|
|
||||||
// Deposit/Withdrawal underlying <-> aToken is always 1:1
|
|
||||||
if (
|
|
||||||
(takerToken.toLowerCase() === aaveInfo.aToken.toLowerCase() &&
|
|
||||||
makerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase()) ||
|
|
||||||
(takerToken.toLowerCase() === aaveInfo.underlyingToken.toLowerCase() &&
|
|
||||||
makerToken.toLowerCase() === aaveInfo.aToken.toLowerCase())
|
|
||||||
) {
|
|
||||||
return makerTokenAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not matching the reserve return 0 results
|
|
||||||
const numSamples = makerTokenAmounts.length;
|
|
||||||
const takerTokenAmounts = new Array(numSamples);
|
|
||||||
takerTokenAmounts.fill(ZERO_AMOUNT);
|
|
||||||
return takerTokenAmounts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,39 +12,38 @@ import {
|
|||||||
FillQuoteTransformerSide,
|
FillQuoteTransformerSide,
|
||||||
findTransformerNonce,
|
findTransformerNonce,
|
||||||
} from '@0x/protocol-utils';
|
} from '@0x/protocol-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
||||||
import {
|
import {
|
||||||
|
Address,
|
||||||
AffiliateFeeType,
|
AffiliateFeeType,
|
||||||
|
Bytes,
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
ExchangeProxyContractOpts,
|
ExchangeProxyContractOpts,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
MarketOperation,
|
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteConsumerBase,
|
SwapQuoteConsumerBase,
|
||||||
SwapQuoteConsumerOpts,
|
SwapQuoteConsumerOpts,
|
||||||
SwapQuoteExecutionOpts,
|
SwapQuoteExecutionOpts,
|
||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
|
SwapQuoteLiquidityProviderBridgeOrder,
|
||||||
|
SwapQuoteUniswapV2BridgeOrder,
|
||||||
|
SwapQuoteUniswapV3BridgeOrder,
|
||||||
|
SwapQuoteCurveBridgeOrder,
|
||||||
|
SwapQuoteMooniswapBridgeOrder,
|
||||||
|
SwapQuoteHop,
|
||||||
|
SwapQuoteGenericBridgeOrder,
|
||||||
|
SwapQuoteOrder,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { assert } from '../utils/assert';
|
import { valueByChainId } from '../utils/utils';
|
||||||
import {
|
import {
|
||||||
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
|
|
||||||
MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
|
|
||||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||||
} from '../utils/market_operation_utils/constants';
|
} from '../utils/market_operation_utils/constants';
|
||||||
import { poolEncoder } from '../utils/market_operation_utils/orders';
|
|
||||||
import {
|
import {
|
||||||
CurveFillData,
|
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
FinalUniswapV3FillData,
|
|
||||||
LiquidityProviderFillData,
|
|
||||||
MooniswapFillData,
|
|
||||||
OptimizedMarketBridgeOrder,
|
|
||||||
OptimizedMarketOrder,
|
|
||||||
UniswapV2FillData,
|
|
||||||
} from '../utils/market_operation_utils/types';
|
} from '../utils/market_operation_utils/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -53,6 +52,7 @@ import {
|
|||||||
MultiplexSubcall,
|
MultiplexSubcall,
|
||||||
multiplexTransformERC20Encoder,
|
multiplexTransformERC20Encoder,
|
||||||
multiplexUniswapEncoder,
|
multiplexUniswapEncoder,
|
||||||
|
multiplexBatchSellEncoder,
|
||||||
} from './multiplex_encoders';
|
} from './multiplex_encoders';
|
||||||
import {
|
import {
|
||||||
getFQTTransformerDataFromOptimizedOrders,
|
getFQTTransformerDataFromOptimizedOrders,
|
||||||
@@ -77,12 +77,30 @@ const PANCAKE_SWAP_FORKS = [
|
|||||||
ERC20BridgeSource.CheeseSwap,
|
ERC20BridgeSource.CheeseSwap,
|
||||||
ERC20BridgeSource.JulSwap,
|
ERC20BridgeSource.JulSwap,
|
||||||
];
|
];
|
||||||
|
|
||||||
const FAKE_PROVIDER: any = {
|
const FAKE_PROVIDER: any = {
|
||||||
sendAsync(): void {
|
sendAsync(): void {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID = valueByChainId<string>(
|
||||||
|
{
|
||||||
|
[ChainId.Mainnet]: '0x561b94454b65614ae3db0897b74303f4acf7cc75',
|
||||||
|
[ChainId.Ropsten]: '0xae241c6fc7f28f6dc0cb58b4112ba7f63fcaf5e2',
|
||||||
|
},
|
||||||
|
NULL_ADDRESS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID = valueByChainId<string>(
|
||||||
|
{
|
||||||
|
[ChainId.Mainnet]: '0xa2033d6ba88756ce6a87584d69dc87bda9a4f889',
|
||||||
|
[ChainId.Ropsten]: '0x87e0393aee0fb8c10b8653c6507c182264fe5a34',
|
||||||
|
},
|
||||||
|
NULL_ADDRESS,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||||
public readonly chainId: ChainId;
|
public readonly chainId: ChainId;
|
||||||
public readonly transformerNonces: {
|
public readonly transformerNonces: {
|
||||||
@@ -95,9 +113,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
|
|
||||||
private readonly _exchangeProxy: IZeroExContract;
|
private readonly _exchangeProxy: IZeroExContract;
|
||||||
|
|
||||||
constructor(public readonly contractAddresses: ContractAddresses, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
constructor(public readonly contractAddresses: ContractAddresses, options: SwapQuoteConsumerOpts) {
|
||||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId } = options;
|
||||||
assert.isNumber('chainId', chainId);
|
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this.contractAddresses = contractAddresses;
|
this.contractAddresses = contractAddresses;
|
||||||
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
||||||
@@ -151,15 +168,14 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
ethAmount = ethAmount.plus(sellAmount);
|
ethAmount = ethAmount.plus(sellAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
const slippedOrders = slipNonNativeOrders(quote);
|
|
||||||
|
|
||||||
// VIP routes.
|
// VIP routes.
|
||||||
if (
|
if (
|
||||||
this.chainId === ChainId.Mainnet &&
|
this.chainId === ChainId.Mainnet &&
|
||||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
|
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
|
||||||
) {
|
) {
|
||||||
const source = slippedOrders[0].source;
|
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
|
||||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
const { source } = order;
|
||||||
|
const { fillData } = order;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToUniswap(
|
.sellToUniswap(
|
||||||
@@ -188,19 +204,20 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
this.chainId === ChainId.Mainnet &&
|
this.chainId === ChainId.Mainnet &&
|
||||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV3])
|
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV3])
|
||||||
) {
|
) {
|
||||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV3BridgeOrder;
|
||||||
|
const { fillData } = order;
|
||||||
let _calldataHexString;
|
let _calldataHexString;
|
||||||
if (isFromETH) {
|
if (isFromETH) {
|
||||||
_calldataHexString = this._exchangeProxy
|
_calldataHexString = this._exchangeProxy
|
||||||
.sellEthForTokenToUniswapV3(fillData.uniswapPath, minBuyAmount, NULL_ADDRESS)
|
.sellEthForTokenToUniswapV3(fillData.encodedPath, minBuyAmount, NULL_ADDRESS)
|
||||||
.getABIEncodedTransactionData();
|
.getABIEncodedTransactionData();
|
||||||
} else if (isToETH) {
|
} else if (isToETH) {
|
||||||
_calldataHexString = this._exchangeProxy
|
_calldataHexString = this._exchangeProxy
|
||||||
.sellTokenForEthToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
.sellTokenForEthToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||||
.getABIEncodedTransactionData();
|
.getABIEncodedTransactionData();
|
||||||
} else {
|
} else {
|
||||||
_calldataHexString = this._exchangeProxy
|
_calldataHexString = this._exchangeProxy
|
||||||
.sellTokenForTokenToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
.sellTokenForTokenToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||||
.getABIEncodedTransactionData();
|
.getABIEncodedTransactionData();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -225,8 +242,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
ERC20BridgeSource.JulSwap,
|
ERC20BridgeSource.JulSwap,
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
const source = slippedOrders[0].source;
|
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
|
||||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
const { source, fillData } = order;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToPancakeSwap(
|
.sellToPancakeSwap(
|
||||||
@@ -255,14 +272,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
[ChainId.Mainnet, ChainId.BSC].includes(this.chainId) &&
|
[ChainId.Mainnet, ChainId.BSC].includes(this.chainId) &&
|
||||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
|
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
|
||||||
) {
|
) {
|
||||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
const { fillData } = quote.hops[0].orders[0] as SwapQuoteLiquidityProviderBridgeOrder;
|
||||||
const target = fillData.poolAddress;
|
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToLiquidityProvider(
|
.sellToLiquidityProvider(
|
||||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||||
target,
|
fillData.poolAddress,
|
||||||
NULL_ADDRESS,
|
NULL_ADDRESS,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
@@ -284,7 +300,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
// ETH buy/sell is supported
|
// ETH buy/sell is supported
|
||||||
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
||||||
) {
|
) {
|
||||||
const fillData = slippedOrders[0].fills[0].fillData as CurveFillData;
|
const { fillData } = quote.hops[0].orders[0] as SwapQuoteCurveBridgeOrder;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToLiquidityProvider(
|
.sellToLiquidityProvider(
|
||||||
@@ -295,8 +311,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
sellAmount,
|
sellAmount,
|
||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
encodeCurveLiquidityProviderData({
|
encodeCurveLiquidityProviderData({
|
||||||
curveAddress: fillData.pool.poolAddress,
|
curveAddress: fillData.poolAddress,
|
||||||
exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector,
|
exchangeFunctionSelector: fillData.exchangeFunctionSelector,
|
||||||
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
|
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
|
||||||
toCoinIdx: new BigNumber(fillData.toTokenIdx),
|
toCoinIdx: new BigNumber(fillData.toTokenIdx),
|
||||||
}),
|
}),
|
||||||
@@ -313,7 +329,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
this.chainId === ChainId.Mainnet &&
|
this.chainId === ChainId.Mainnet &&
|
||||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
||||||
) {
|
) {
|
||||||
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
|
const { fillData } = quote.hops[0].orders[0] as SwapQuoteMooniswapBridgeOrder;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToLiquidityProvider(
|
.sellToLiquidityProvider(
|
||||||
@@ -323,7 +339,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
NULL_ADDRESS,
|
NULL_ADDRESS,
|
||||||
sellAmount,
|
sellAmount,
|
||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
poolEncoder.encode([fillData.poolAddress]),
|
encodeAddress(fillData.poolAddress),
|
||||||
)
|
)
|
||||||
.getABIEncodedTransactionData(),
|
.getABIEncodedTransactionData(),
|
||||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
@@ -336,7 +352,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||||
{ ...quote, orders: slippedOrders },
|
quote.hops[0],
|
||||||
optsWithDefaults,
|
optsWithDefaults,
|
||||||
),
|
),
|
||||||
ethAmount,
|
ethAmount,
|
||||||
@@ -345,10 +361,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
gasOverhead: ZERO_AMOUNT,
|
gasOverhead: ZERO_AMOUNT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort hops so they always flow taker -> maker
|
||||||
|
const orderedHops = isBuyQuote(quote) ? quote.hops.slice().reverse() : quote.hops;
|
||||||
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
|
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
|
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
|
||||||
{ ...quote, orders: slippedOrders },
|
orderedHops,
|
||||||
optsWithDefaults,
|
optsWithDefaults,
|
||||||
),
|
),
|
||||||
ethAmount,
|
ethAmount,
|
||||||
@@ -372,45 +391,26 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's two hop we have an intermediate token this is needed to encode the individual FQT
|
for (const [i, hop] of orderedHops.entries()) {
|
||||||
// and we also want to ensure no dust amount is left in the flash wallet
|
let fillAmount = !isBuyQuote(quote)
|
||||||
const intermediateToken = quote.isTwoHop ? slippedOrders[0].makerToken : NULL_ADDRESS;
|
? shouldSellEntireBalance ? MAX_UINT256 : hop.takerAmount
|
||||||
// This transformer will fill the quote.
|
: hop.makerAmount;
|
||||||
if (quote.isTwoHop) {
|
let side = !isBuyQuote(quote) ? FillQuoteTransformerSide.Sell : FillQuoteTransformerSide.Buy;
|
||||||
const [firstHopOrder, secondHopOrder] = slippedOrders;
|
if (orderedHops.length > 1) { // Multi-hop.
|
||||||
|
// Multi-hop is always a sell.
|
||||||
|
side = FillQuoteTransformerSide.Sell;
|
||||||
|
// Subsequent multi-hops always sell entire balance.
|
||||||
|
fillAmount = i > 0 ? MAX_UINT256 : hop.takerAmount;
|
||||||
|
}
|
||||||
transforms.push({
|
transforms.push({
|
||||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||||
data: encodeFillQuoteTransformerData({
|
data: encodeFillQuoteTransformerData({
|
||||||
side: FillQuoteTransformerSide.Sell,
|
side,
|
||||||
sellToken,
|
fillAmount,
|
||||||
buyToken: intermediateToken,
|
sellToken: hop.takerToken,
|
||||||
...getFQTTransformerDataFromOptimizedOrders([firstHopOrder]),
|
buyToken: hop.makerToken,
|
||||||
|
...getFQTTransformerDataFromOptimizedOrders(hop.orders),
|
||||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
refundReceiver: refundReceiver || NULL_ADDRESS,
|
||||||
fillAmount: shouldSellEntireBalance ? MAX_UINT256 : firstHopOrder.takerAmount,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
transforms.push({
|
|
||||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
|
||||||
data: encodeFillQuoteTransformerData({
|
|
||||||
side: FillQuoteTransformerSide.Sell,
|
|
||||||
buyToken,
|
|
||||||
sellToken: intermediateToken,
|
|
||||||
...getFQTTransformerDataFromOptimizedOrders([secondHopOrder]),
|
|
||||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
|
||||||
fillAmount: MAX_UINT256,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const fillAmount = isBuyQuote(quote) ? quote.makerTokenFillAmount : quote.takerTokenFillAmount;
|
|
||||||
transforms.push({
|
|
||||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
|
||||||
data: encodeFillQuoteTransformerData({
|
|
||||||
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
|
|
||||||
sellToken,
|
|
||||||
buyToken,
|
|
||||||
...getFQTTransformerDataFromOptimizedOrders(slippedOrders),
|
|
||||||
refundReceiver: refundReceiver || NULL_ADDRESS,
|
|
||||||
fillAmount: !isBuyQuote(quote) && shouldSellEntireBalance ? MAX_UINT256 : fillAmount,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -476,10 +476,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
|
|
||||||
// Return any unspent sell tokens.
|
// Return any unspent sell tokens.
|
||||||
const payTakerTokens = [sellToken];
|
const payTakerTokens = [sellToken];
|
||||||
// Return any unspent intermediate tokens for two-hop swaps.
|
|
||||||
if (quote.isTwoHop) {
|
|
||||||
payTakerTokens.push(intermediateToken);
|
|
||||||
}
|
|
||||||
// Return any unspent ETH. If ETH is the buy token, it will
|
// Return any unspent ETH. If ETH is the buy token, it will
|
||||||
// be returned in TransformERC20Feature rather than PayTakerTransformer.
|
// be returned in TransformERC20Feature rather than PayTakerTransformer.
|
||||||
if (!isToETH) {
|
if (!isToETH) {
|
||||||
@@ -521,9 +517,108 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
throw new Error('Execution not supported for Exchange Proxy quotes');
|
throw new Error('Execution not supported for Exchange Proxy quotes');
|
||||||
}
|
}
|
||||||
|
|
||||||
private _encodeMultiplexBatchFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
private _encodeMultiplexBatchFillCalldata(hop: SwapQuoteHop, opts: ExchangeProxyContractOpts): string {
|
||||||
|
const subcalls = this._getMultiplexBatchSellSubcalls(hop.orders);
|
||||||
|
if (opts.isFromETH) {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexBatchSellEthForToken(hop.makerToken, subcalls, hop.minMakerAmount)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
} else if (opts.isToETH) {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexBatchSellTokenForEth(
|
||||||
|
hop.takerToken,
|
||||||
|
subcalls,
|
||||||
|
hop.maxTakerAmount,
|
||||||
|
hop.minMakerAmount,
|
||||||
|
)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
} else {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexBatchSellTokenForToken(
|
||||||
|
hop.takerToken,
|
||||||
|
hop.makerToken,
|
||||||
|
subcalls,
|
||||||
|
hop.maxTakerAmount,
|
||||||
|
hop.minMakerAmount,
|
||||||
|
)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _encodeMultiplexMultiHopFillCalldata(hops: SwapQuoteHop[], opts: ExchangeProxyContractOpts): string {
|
||||||
const subcalls = [];
|
const subcalls = [];
|
||||||
for_loop: for (const [i, order] of quote.orders.entries()) {
|
for (const hop of hops) {
|
||||||
|
if (hop.orders.length !== 1) {
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.BatchSell,
|
||||||
|
data: multiplexBatchSellEncoder.encode({ subcalls: this._getMultiplexBatchSellSubcalls(hop.orders) }),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const order = hop.orders[0] as SwapQuoteGenericBridgeOrder;
|
||||||
|
switch (order.source) {
|
||||||
|
case ERC20BridgeSource.UniswapV2:
|
||||||
|
case ERC20BridgeSource.SushiSwap:
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.UniswapV2,
|
||||||
|
data: multiplexUniswapEncoder.encode({
|
||||||
|
tokens: (order as SwapQuoteUniswapV2BridgeOrder).fillData.tokenAddressPath,
|
||||||
|
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ERC20BridgeSource.LiquidityProvider:
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.LiquidityProvider,
|
||||||
|
data: multiplexPlpEncoder.encode({
|
||||||
|
provider: (order as SwapQuoteLiquidityProviderBridgeOrder).fillData.poolAddress,
|
||||||
|
auxiliaryData: NULL_BYTES,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ERC20BridgeSource.UniswapV3:
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.UniswapV3,
|
||||||
|
data: (order as SwapQuoteUniswapV3BridgeOrder).fillData.encodedPath,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
|
||||||
|
// before calling this function.
|
||||||
|
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tokenPath = getTokenPathFromHops(hops);
|
||||||
|
const firstHop = hops[0];
|
||||||
|
const lastHop = hops[hops.length - 1];
|
||||||
|
if (opts.isFromETH) {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexMultiHopSellEthForToken(tokenPath, subcalls, lastHop.minMakerAmount)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
} else if (opts.isToETH) {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexMultiHopSellTokenForEth(
|
||||||
|
tokenPath,
|
||||||
|
subcalls,
|
||||||
|
firstHop.maxTakerAmount,
|
||||||
|
lastHop.minMakerAmount,
|
||||||
|
)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
} else {
|
||||||
|
return this._exchangeProxy
|
||||||
|
.multiplexMultiHopSellTokenForToken(
|
||||||
|
tokenPath,
|
||||||
|
subcalls,
|
||||||
|
firstHop.maxTakerAmount,
|
||||||
|
lastHop.minMakerAmount,
|
||||||
|
)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getMultiplexBatchSellSubcalls(orders: SwapQuoteOrder[]): any[] {
|
||||||
|
const subcalls = [];
|
||||||
|
for_loop: for (const [i, order] of orders.entries()) {
|
||||||
switch_statement: switch (order.source) {
|
switch_statement: switch (order.source) {
|
||||||
case ERC20BridgeSource.Native:
|
case ERC20BridgeSource.Native:
|
||||||
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
|
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
|
||||||
@@ -544,9 +639,9 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
case ERC20BridgeSource.SushiSwap:
|
case ERC20BridgeSource.SushiSwap:
|
||||||
subcalls.push({
|
subcalls.push({
|
||||||
id: MultiplexSubcall.UniswapV2,
|
id: MultiplexSubcall.UniswapV2,
|
||||||
sellAmount: order.takerAmount,
|
sellAmount: (order as SwapQuoteUniswapV2BridgeOrder).maxTakerAmount,
|
||||||
data: multiplexUniswapEncoder.encode({
|
data: multiplexUniswapEncoder.encode({
|
||||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
tokens: (order as SwapQuoteUniswapV2BridgeOrder).fillData.tokenAddressPath,
|
||||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -554,43 +649,46 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
case ERC20BridgeSource.LiquidityProvider:
|
case ERC20BridgeSource.LiquidityProvider:
|
||||||
subcalls.push({
|
subcalls.push({
|
||||||
id: MultiplexSubcall.LiquidityProvider,
|
id: MultiplexSubcall.LiquidityProvider,
|
||||||
sellAmount: order.takerAmount,
|
sellAmount: (order as SwapQuoteLiquidityProviderBridgeOrder).maxTakerAmount,
|
||||||
data: multiplexPlpEncoder.encode({
|
data: multiplexPlpEncoder.encode({
|
||||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
provider: (order as SwapQuoteLiquidityProviderBridgeOrder).fillData.poolAddress,
|
||||||
auxiliaryData: NULL_BYTES,
|
auxiliaryData: NULL_BYTES,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
break switch_statement;
|
break switch_statement;
|
||||||
case ERC20BridgeSource.UniswapV3:
|
case ERC20BridgeSource.UniswapV3:
|
||||||
const fillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
|
||||||
subcalls.push({
|
subcalls.push({
|
||||||
id: MultiplexSubcall.UniswapV3,
|
id: MultiplexSubcall.UniswapV3,
|
||||||
sellAmount: order.takerAmount,
|
sellAmount: (order as SwapQuoteUniswapV3BridgeOrder).maxTakerAmount,
|
||||||
data: fillData.uniswapPath,
|
data: (order as SwapQuoteUniswapV3BridgeOrder).fillData.encodedPath,
|
||||||
});
|
});
|
||||||
break switch_statement;
|
break switch_statement;
|
||||||
default:
|
default:
|
||||||
const fqtData = encodeFillQuoteTransformerData({
|
const fqtData = encodeFillQuoteTransformerData({
|
||||||
side: FillQuoteTransformerSide.Sell,
|
side: FillQuoteTransformerSide.Sell,
|
||||||
sellToken: quote.takerToken,
|
sellToken: order.takerToken,
|
||||||
buyToken: quote.makerToken,
|
buyToken: order.makerToken,
|
||||||
...getFQTTransformerDataFromOptimizedOrders(quote.orders.slice(i)),
|
...getFQTTransformerDataFromOptimizedOrders(orders.slice(i)),
|
||||||
refundReceiver: NULL_ADDRESS,
|
refundReceiver: NULL_ADDRESS,
|
||||||
fillAmount: MAX_UINT256,
|
fillAmount: MAX_UINT256,
|
||||||
});
|
});
|
||||||
const transformations = [
|
const transformations = [
|
||||||
{ deploymentNonce: this.transformerNonces.fillQuoteTransformer, data: fqtData },
|
{ deploymentNonce: this.transformerNonces.fillQuoteTransformer, data: fqtData },
|
||||||
{
|
// TODO(lawrence): needed?
|
||||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
// {
|
||||||
data: encodePayTakerTransformerData({
|
// deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||||
tokens: [quote.takerToken],
|
// data: encodePayTakerTransformerData({
|
||||||
amounts: [],
|
// tokens: [hop.takerToken],
|
||||||
}),
|
// amounts: [],
|
||||||
},
|
// }),
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
subcalls.push({
|
subcalls.push({
|
||||||
id: MultiplexSubcall.TransformERC20,
|
id: MultiplexSubcall.TransformERC20,
|
||||||
sellAmount: BigNumber.sum(...quote.orders.slice(i).map(o => o.takerAmount)),
|
sellAmount: BigNumber.sum(
|
||||||
|
...orders.slice(i)
|
||||||
|
.map(o => (o as SwapQuoteGenericBridgeOrder).maxTakerAmount),
|
||||||
|
),
|
||||||
data: multiplexTransformERC20Encoder.encode({
|
data: multiplexTransformERC20Encoder.encode({
|
||||||
transformations,
|
transformations,
|
||||||
}),
|
}),
|
||||||
@@ -598,123 +696,21 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
break for_loop;
|
break for_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (opts.isFromETH) {
|
return subcalls;
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexBatchSellEthForToken(quote.makerToken, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
} else if (opts.isToETH) {
|
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexBatchSellTokenForEth(
|
|
||||||
quote.takerToken,
|
|
||||||
subcalls,
|
|
||||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
|
||||||
quote.worstCaseQuoteInfo.makerAmount,
|
|
||||||
)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
} else {
|
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexBatchSellTokenForToken(
|
|
||||||
quote.takerToken,
|
|
||||||
quote.makerToken,
|
|
||||||
subcalls,
|
|
||||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
|
||||||
quote.worstCaseQuoteInfo.makerAmount,
|
|
||||||
)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _encodeMultiplexMultiHopFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
|
||||||
const subcalls = [];
|
|
||||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
|
||||||
const intermediateToken = firstHopOrder.makerToken;
|
|
||||||
const tokens = [quote.takerToken, intermediateToken, quote.makerToken];
|
|
||||||
|
|
||||||
for (const order of [firstHopOrder, secondHopOrder]) {
|
|
||||||
switch (order.source) {
|
|
||||||
case ERC20BridgeSource.UniswapV2:
|
|
||||||
case ERC20BridgeSource.SushiSwap:
|
|
||||||
subcalls.push({
|
|
||||||
id: MultiplexSubcall.UniswapV2,
|
|
||||||
data: multiplexUniswapEncoder.encode({
|
|
||||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
|
||||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.LiquidityProvider:
|
|
||||||
subcalls.push({
|
|
||||||
id: MultiplexSubcall.LiquidityProvider,
|
|
||||||
data: multiplexPlpEncoder.encode({
|
|
||||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
|
||||||
auxiliaryData: NULL_BYTES,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.UniswapV3:
|
|
||||||
subcalls.push({
|
|
||||||
id: MultiplexSubcall.UniswapV3,
|
|
||||||
data: (order.fillData as FinalUniswapV3FillData).uniswapPath,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Should never happen because we check `isMultiplexMultiHopFillCompatible`
|
|
||||||
// before calling this function.
|
|
||||||
throw new Error(`Multiplex multi-hop unsupported source: ${order.source}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts.isFromETH) {
|
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexMultiHopSellEthForToken(tokens, subcalls, quote.worstCaseQuoteInfo.makerAmount)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
} else if (opts.isToETH) {
|
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexMultiHopSellTokenForEth(
|
|
||||||
tokens,
|
|
||||||
subcalls,
|
|
||||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
|
||||||
quote.worstCaseQuoteInfo.makerAmount,
|
|
||||||
)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
} else {
|
|
||||||
return this._exchangeProxy
|
|
||||||
.multiplexMultiHopSellTokenForToken(
|
|
||||||
tokens,
|
|
||||||
subcalls,
|
|
||||||
quote.worstCaseQuoteInfo.totalTakerAmount,
|
|
||||||
quote.worstCaseQuoteInfo.makerAmount,
|
|
||||||
)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
|
function getTokenPathFromHops(hops: SwapQuoteHop[]): Address[] {
|
||||||
const slippage = getMaxQuoteSlippageRate(quote);
|
const path = [];
|
||||||
if (slippage === 0) {
|
for (const [i, hop] of hops.entries()) {
|
||||||
return quote.orders;
|
path.push(hop.takerToken);
|
||||||
}
|
if (i === hops.length - 1) {
|
||||||
return quote.orders.map(o => {
|
path.push(hop.makerToken);
|
||||||
if (o.source === ERC20BridgeSource.Native) {
|
|
||||||
return o;
|
|
||||||
}
|
}
|
||||||
return {
|
}
|
||||||
...o,
|
return path;
|
||||||
...(quote.type === MarketOperation.Sell
|
|
||||||
? {
|
|
||||||
makerAmount: o.makerAmount.eq(MAX_UINT256)
|
|
||||||
? MAX_UINT256
|
|
||||||
: o.makerAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
takerAmount: o.takerAmount.eq(MAX_UINT256)
|
|
||||||
? MAX_UINT256
|
|
||||||
: o.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
|
function encodeAddress(address: Address): Bytes {
|
||||||
return quote.worstCaseQuoteInfo.slippage;
|
return hexUtils.leftPad(hexUtils.slice(address, 0, 20));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export enum MultiplexSubcall {
|
|||||||
BatchSell,
|
BatchSell,
|
||||||
MultiHopSell,
|
MultiHopSell,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||||
{
|
{
|
||||||
name: 'transformations',
|
name: 'transformations',
|
||||||
@@ -22,15 +23,30 @@ export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const multiplexRfqEncoder = AbiEncoder.create([
|
export const multiplexRfqEncoder = AbiEncoder.create([
|
||||||
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||||
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const multiplexUniswapEncoder = AbiEncoder.create([
|
export const multiplexUniswapEncoder = AbiEncoder.create([
|
||||||
{ name: 'tokens', type: 'address[]' },
|
{ name: 'tokens', type: 'address[]' },
|
||||||
{ name: 'isSushi', type: 'bool' },
|
{ name: 'isSushi', type: 'bool' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const multiplexPlpEncoder = AbiEncoder.create([
|
export const multiplexPlpEncoder = AbiEncoder.create([
|
||||||
{ name: 'provider', type: 'address' },
|
{ name: 'provider', type: 'address' },
|
||||||
{ name: 'auxiliaryData', type: 'bytes' },
|
{ name: 'auxiliaryData', type: 'bytes' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const multiplexBatchSellEncoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'subcalls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{ name: 'id', type: 'uint8' },
|
||||||
|
{ name: 'sellAmount', type: 'uint256' },
|
||||||
|
{ name: 'data', type: 'bytes' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { FillQuoteTransformerData, FillQuoteTransformerOrderType } from '@0x/pro
|
|||||||
|
|
||||||
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
|
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
|
||||||
import {
|
import {
|
||||||
createBridgeDataForBridgeOrder,
|
|
||||||
getErc20BridgeSourceToBridgeSource,
|
getErc20BridgeSourceToBridgeSource,
|
||||||
} from '../utils/market_operation_utils/orders';
|
} from '../utils/market_operation_utils/orders';
|
||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
NativeLimitOrderFillData,
|
|
||||||
NativeRfqOrderFillData,
|
|
||||||
OptimizedMarketBridgeOrder,
|
|
||||||
OptimizedMarketOrder,
|
|
||||||
OptimizedMarketOrderBase,
|
|
||||||
} from '../utils/market_operation_utils/types';
|
} from '../utils/market_operation_utils/types';
|
||||||
|
import {
|
||||||
|
SwapQuoteGenericBridgeOrder,
|
||||||
|
SwapQuoteOrder,
|
||||||
|
SwapQuoteLimitOrder,
|
||||||
|
SwapQuoteRfqOrder,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
const MULTIPLEX_BATCH_FILL_SOURCES = [
|
const MULTIPLEX_BATCH_FILL_SOURCES = [
|
||||||
ERC20BridgeSource.UniswapV2,
|
ERC20BridgeSource.UniswapV2,
|
||||||
@@ -29,16 +29,19 @@ export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeP
|
|||||||
if (requiresTransformERC20(opts)) {
|
if (requiresTransformERC20(opts)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (quote.isTwoHop) {
|
// Must not be multi-hop.
|
||||||
|
if (quote.hops.length > 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (quote.orders.map(o => o.type).includes(FillQuoteTransformerOrderType.Limit)) {
|
// Must not contain limit orders.
|
||||||
|
const allOrderTypes = quote.hops.map(h => h.orders.map(o => o.type)).flat(2);
|
||||||
|
if (allOrderTypes.includes(FillQuoteTransformerOrderType.Limit)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Use Multiplex if the non-fallback sources are a subset of
|
// Use Multiplex if the non-fallback sources are a subset of
|
||||||
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
|
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
|
||||||
const nonFallbackSources = Object.keys(quote.sourceBreakdown);
|
const nonFallbackSources = quote.hops.map(h => h.orders.filter(o => !o.isFallback).map(o => o.source)).flat(2);
|
||||||
return nonFallbackSources.every(source => MULTIPLEX_BATCH_FILL_SOURCES.includes(source as ERC20BridgeSource));
|
return nonFallbackSources.every(s => MULTIPLEX_BATCH_FILL_SOURCES.includes(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
|
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
|
||||||
@@ -55,14 +58,12 @@ export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: Exchan
|
|||||||
if (requiresTransformERC20(opts)) {
|
if (requiresTransformERC20(opts)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!quote.isTwoHop) {
|
// Must be multi-hop.
|
||||||
|
if (quote.hops.length < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
const sources = quote.hops.map(h => h.orders.map(o => o.source)).flat(2);
|
||||||
return (
|
return sources.every(s => MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(s));
|
||||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(firstHopOrder.source) &&
|
|
||||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(secondHopOrder.source)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,11 +78,11 @@ export function isDirectSwapCompatible(
|
|||||||
if (requiresTransformERC20(opts)) {
|
if (requiresTransformERC20(opts)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Must be a single order.
|
// Must be a single hop with a single order.
|
||||||
if (quote.orders.length !== 1) {
|
if (quote.hops.length !== 1 || quote.hops[0].orders.length !== 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const order = quote.orders[0];
|
const order = quote.hops[0].orders[0];
|
||||||
if (!directSources.includes(order.source)) {
|
if (!directSources.includes(order.source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -95,24 +96,24 @@ export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
|||||||
return quote.type === MarketOperation.Buy;
|
return quote.type === MarketOperation.Buy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptimizedBridgeOrder(x: OptimizedMarketOrder): x is OptimizedMarketBridgeOrder {
|
function isBridgeOrder(x: SwapQuoteOrder): x is SwapQuoteGenericBridgeOrder {
|
||||||
return x.type === FillQuoteTransformerOrderType.Bridge;
|
return x.type === FillQuoteTransformerOrderType.Bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
// function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||||
return x.type === FillQuoteTransformerOrderType.Limit;
|
// return x.type === FillQuoteTransformerOrderType.Limit;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
// function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||||
return x.type === FillQuoteTransformerOrderType.Rfq;
|
// return x.type === FillQuoteTransformerOrderType.Rfq;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
||||||
* FillQuoteTransformer.
|
* FillQuoteTransformer.
|
||||||
*/
|
*/
|
||||||
export function getFQTTransformerDataFromOptimizedOrders(
|
export function getFQTTransformerDataFromOptimizedOrders(
|
||||||
orders: OptimizedMarketOrder[],
|
orders: SwapQuoteOrder[],
|
||||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
||||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
||||||
bridgeOrders: [],
|
bridgeOrders: [],
|
||||||
@@ -122,25 +123,25 @@ export function getFQTTransformerDataFromOptimizedOrders(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const order of orders) {
|
for (const order of orders) {
|
||||||
if (isOptimizedBridgeOrder(order)) {
|
if (isBridgeOrder(order)) {
|
||||||
fqtData.bridgeOrders.push({
|
fqtData.bridgeOrders.push({
|
||||||
bridgeData: createBridgeDataForBridgeOrder(order),
|
bridgeData: order.fillData.encodedFillData,
|
||||||
makerTokenAmount: order.makerAmount,
|
makerTokenAmount: order.minMakerAmount,
|
||||||
takerTokenAmount: order.takerAmount,
|
takerTokenAmount: order.maxTakerAmount,
|
||||||
source: getErc20BridgeSourceToBridgeSource(order.source),
|
source: getErc20BridgeSourceToBridgeSource(order.source),
|
||||||
});
|
});
|
||||||
} else if (isOptimizedLimitOrder(order)) {
|
// } else if (isOptimizedLimitOrder(order)) {
|
||||||
fqtData.limitOrders.push({
|
// fqtData.limitOrders.push({
|
||||||
order: order.fillData.order,
|
// order: order.fillData.order,
|
||||||
signature: order.fillData.signature,
|
// signature: order.fillData.signature,
|
||||||
maxTakerTokenFillAmount: order.takerAmount,
|
// maxTakerTokenFillAmount: order.takerAmount,
|
||||||
});
|
// });
|
||||||
} else if (isOptimizedRfqOrder(order)) {
|
// } else if (isOptimizedRfqOrder(order)) {
|
||||||
fqtData.rfqOrders.push({
|
// fqtData.rfqOrders.push({
|
||||||
order: order.fillData.order,
|
// order: order.fillData.order,
|
||||||
signature: order.fillData.signature,
|
// signature: order.fillData.signature,
|
||||||
maxTakerTokenFillAmount: order.takerAmount,
|
// maxTakerTokenFillAmount: order.takerAmount,
|
||||||
});
|
// });
|
||||||
} else {
|
} else {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
throw new Error('Unknown Order type');
|
throw new Error('Unknown Order type');
|
||||||
|
|||||||
@@ -20,13 +20,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
private readonly _contractAddresses: ContractAddresses;
|
private readonly _contractAddresses: ContractAddresses;
|
||||||
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
|
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
|
||||||
|
|
||||||
public static getSwapQuoteConsumer(options: Partial<SwapQuoteConsumerOpts> = {}): SwapQuoteConsumer {
|
public static getSwapQuoteConsumer(options: SwapQuoteConsumerOpts): SwapQuoteConsumer {
|
||||||
return new SwapQuoteConsumer(options);
|
return new SwapQuoteConsumer(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: Partial<SwapQuoteConsumerOpts> = {}) {
|
constructor(options: SwapQuoteConsumerOpts) {
|
||||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId } = options;
|
||||||
assert.isNumber('chainId', chainId);
|
|
||||||
|
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
this._contractAddresses = options.contractAddresses || getContractAddressesForChainOrThrow(chainId);
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
import { FillQuoteTransformerOrderType, LimitOrder } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType, LimitOrder } from '@0x/protocol-utils';
|
||||||
import { BigNumber, providerUtils } from '@0x/utils';
|
import { BigNumber, providerUtils } from '@0x/utils';
|
||||||
import Axios, { AxiosInstance } from 'axios';
|
import Axios, { AxiosInstance } from 'axios';
|
||||||
import { BlockParamLiteral, MethodAbi, SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||||
import { FastABI } from 'fast-abi';
|
|
||||||
import { Agent as HttpAgent } from 'http';
|
import { Agent as HttpAgent } from 'http';
|
||||||
import { Agent as HttpsAgent } from 'https';
|
import { Agent as HttpsAgent } from 'https';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts } from './artifacts';
|
|
||||||
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
|
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
|
||||||
import {
|
import {
|
||||||
|
Address,
|
||||||
AssetSwapperContractAddresses,
|
AssetSwapperContractAddresses,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
MarketOperation,
|
MarketOperation,
|
||||||
@@ -19,6 +18,10 @@ import {
|
|||||||
SignedNativeOrder,
|
SignedNativeOrder,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
|
SwapQuoteHop,
|
||||||
|
SwapQuoteOrder,
|
||||||
|
SwapQuoteGenericBridgeOrder,
|
||||||
|
SwapQuoteNativeOrder,
|
||||||
SwapQuoteOrdersBreakdown,
|
SwapQuoteOrdersBreakdown,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
@@ -26,25 +29,26 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { assert } from './utils/assert';
|
import { assert } from './utils/assert';
|
||||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||||
import { BancorService } from './utils/market_operation_utils/bancor_service';
|
import { ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
||||||
import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
import { SamplerClient } from './utils/market_operation_utils/sampler';
|
||||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
|
||||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
FeeSchedule,
|
|
||||||
FillData,
|
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
MarketDepth,
|
MarketDepth,
|
||||||
MarketDepthSide,
|
MarketDepthSide,
|
||||||
MarketSideLiquidity,
|
MarketSideLiquidity,
|
||||||
OptimizedMarketOrder,
|
OptimizedHop,
|
||||||
|
OptimizedOrder,
|
||||||
|
OptimizedBridgeOrder,
|
||||||
|
OptimizedLimitOrder,
|
||||||
|
OptimizedRfqOrder,
|
||||||
|
OptimizedGenericBridgeOrder,
|
||||||
OptimizerResultWithReport,
|
OptimizerResultWithReport,
|
||||||
} from './utils/market_operation_utils/types';
|
} from './utils/market_operation_utils/types';
|
||||||
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
import { QuoteRequestor } from './utils/quote_requestor';
|
import { QuoteRequestor } from './utils/quote_requestor';
|
||||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './utils/quote_simulation';
|
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './utils/quote_simulation';
|
||||||
import { ERC20BridgeSamplerContract } from './wrappers';
|
|
||||||
|
|
||||||
export abstract class Orderbook {
|
export abstract class Orderbook {
|
||||||
public abstract getOrdersAsync(
|
public abstract getOrdersAsync(
|
||||||
@@ -85,20 +89,15 @@ export class SwapQuoter {
|
|||||||
*
|
*
|
||||||
* @return An instance of SwapQuoter
|
* @return An instance of SwapQuoter
|
||||||
*/
|
*/
|
||||||
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
|
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: SwapQuoterOpts) {
|
||||||
const {
|
const {
|
||||||
chainId,
|
chainId,
|
||||||
expiryBufferMs,
|
expiryBufferMs,
|
||||||
permittedOrderFeeTypes,
|
permittedOrderFeeTypes,
|
||||||
samplerGasLimit,
|
|
||||||
rfqt,
|
rfqt,
|
||||||
tokenAdjacencyGraph,
|
} = options;
|
||||||
liquidityProviderRegistry,
|
|
||||||
} = { ...constants.DEFAULT_SWAP_QUOTER_OPTS, ...options };
|
|
||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
assert.isValidOrderbook('orderbook', orderbook);
|
assert.isValidOrderbook('orderbook', orderbook);
|
||||||
assert.isNumber('chainId', chainId);
|
|
||||||
assert.isNumber('expiryBufferMs', expiryBufferMs);
|
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.orderbook = orderbook;
|
this.orderbook = orderbook;
|
||||||
@@ -113,51 +112,12 @@ export class SwapQuoter {
|
|||||||
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||||
options.ethGasStationUrl,
|
options.ethGasStationUrl,
|
||||||
);
|
);
|
||||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
|
||||||
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
|
||||||
// Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work
|
|
||||||
const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS;
|
|
||||||
const defaultCodeOverrides = samplerBytecode
|
|
||||||
? {
|
|
||||||
[samplerAddress]: { code: samplerBytecode },
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
const samplerOverrides = _.assign(
|
|
||||||
{ block: BlockParamLiteral.Latest, overrides: defaultCodeOverrides },
|
|
||||||
options.samplerOverrides,
|
|
||||||
);
|
|
||||||
const fastAbi = new FastABI(ERC20BridgeSamplerContract.ABI() as MethodAbi[], { BigNumber });
|
|
||||||
const samplerContract = new ERC20BridgeSamplerContract(
|
|
||||||
samplerAddress,
|
|
||||||
this.provider,
|
|
||||||
{
|
|
||||||
gas: samplerGasLimit,
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
encodeInput: (fnName: string, values: any) => fastAbi.encodeInput(fnName, values),
|
|
||||||
decodeOutput: (fnName: string, data: string) => fastAbi.decodeOutput(fnName, data),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this._marketOperationUtils = new MarketOperationUtils(
|
this._marketOperationUtils = new MarketOperationUtils(
|
||||||
new DexOrderSampler(
|
SamplerClient.createFromChainIdAndEndpoint(
|
||||||
this.chainId,
|
this.chainId,
|
||||||
samplerContract,
|
options.samplerServiceUrl,
|
||||||
samplerOverrides,
|
|
||||||
undefined, // pools caches for balancer and cream
|
|
||||||
tokenAdjacencyGraph,
|
|
||||||
liquidityProviderRegistry,
|
|
||||||
this.chainId === ChainId.Mainnet // Enable Bancor only on Mainnet
|
|
||||||
? async () => BancorService.createAsync(provider)
|
|
||||||
: async () => undefined,
|
|
||||||
),
|
),
|
||||||
this._contractAddresses,
|
|
||||||
{
|
|
||||||
chainId,
|
|
||||||
exchangeAddress: this._contractAddresses.exchange,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this._quoteRequestorHttpClient = Axios.create({
|
this._quoteRequestorHttpClient = Axios.create({
|
||||||
@@ -216,7 +176,6 @@ export class SwapQuoter {
|
|||||||
MarketOperation.Buy,
|
MarketOperation.Buy,
|
||||||
makerTokenBuyAmounts[i],
|
makerTokenBuyAmounts[i],
|
||||||
gasPrice,
|
gasPrice,
|
||||||
opts.gasSchedule,
|
|
||||||
opts.bridgeSlippage,
|
opts.bridgeSlippage,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -243,49 +202,50 @@ export class SwapQuoter {
|
|||||||
takerAssetAmount: BigNumber,
|
takerAssetAmount: BigNumber,
|
||||||
options: Partial<SwapQuoteRequestOpts> = {},
|
options: Partial<SwapQuoteRequestOpts> = {},
|
||||||
): Promise<MarketDepth> {
|
): Promise<MarketDepth> {
|
||||||
assert.isString('makerToken', makerToken);
|
throw new Error(`Not implemented`);
|
||||||
assert.isString('takerToken', takerToken);
|
// assert.isString('makerToken', makerToken);
|
||||||
const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
// assert.isString('takerToken', takerToken);
|
||||||
|
// const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
||||||
let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
//
|
||||||
? [[], []]
|
// let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||||
: await Promise.all([
|
// ? [[], []]
|
||||||
this.orderbook.getOrdersAsync(makerToken, takerToken),
|
// : await Promise.all([
|
||||||
this.orderbook.getOrdersAsync(takerToken, makerToken),
|
// this.orderbook.getOrdersAsync(makerToken, takerToken),
|
||||||
]);
|
// this.orderbook.getOrdersAsync(takerToken, makerToken),
|
||||||
if (!sellOrders || sellOrders.length === 0) {
|
// ]);
|
||||||
sellOrders = [createDummyOrder(makerToken, takerToken)];
|
// if (!sellOrders || sellOrders.length === 0) {
|
||||||
}
|
// sellOrders = [createDummyOrder(makerToken, takerToken)];
|
||||||
if (!buyOrders || buyOrders.length === 0) {
|
// }
|
||||||
buyOrders = [createDummyOrder(takerToken, makerToken)];
|
// if (!buyOrders || buyOrders.length === 0) {
|
||||||
}
|
// buyOrders = [createDummyOrder(takerToken, makerToken)];
|
||||||
|
// }
|
||||||
const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
|
//
|
||||||
const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
|
// const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
|
||||||
const { side } = marketSideLiquidity;
|
// const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
|
||||||
|
// const { side } = marketSideLiquidity;
|
||||||
return [
|
//
|
||||||
...dexQuotes,
|
// return [
|
||||||
nativeOrders.map(o => {
|
// ...dexQuotes,
|
||||||
return {
|
// nativeOrders.map(o => {
|
||||||
input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
|
// return {
|
||||||
output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
// input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
|
||||||
fillData: o,
|
// output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
||||||
source: ERC20BridgeSource.Native,
|
// fillData: o,
|
||||||
};
|
// source: ERC20BridgeSource.Native,
|
||||||
}),
|
// };
|
||||||
];
|
// }),
|
||||||
};
|
// ];
|
||||||
const [bids, asks] = await Promise.all([
|
// };
|
||||||
this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
|
// const [bids, asks] = await Promise.all([
|
||||||
this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
|
// this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
|
||||||
]);
|
// this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
|
||||||
return {
|
// ]);
|
||||||
bids: getMarketDepthSide(bids),
|
// return {
|
||||||
asks: getMarketDepthSide(asks),
|
// bids: getMarketDepthSide(bids),
|
||||||
makerTokenDecimals: asks.makerTokenDecimals,
|
// asks: getMarketDepthSide(asks),
|
||||||
takerTokenDecimals: asks.takerTokenDecimals,
|
// makerTokenDecimals: asks.makerTokenDecimals,
|
||||||
};
|
// takerTokenDecimals: asks.takerTokenDecimals,
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -364,9 +324,6 @@ export class SwapQuoter {
|
|||||||
const calcOpts: GetMarketOrdersOpts = {
|
const calcOpts: GetMarketOrdersOpts = {
|
||||||
...cloneOpts,
|
...cloneOpts,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
|
||||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
|
||||||
),
|
|
||||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||||
};
|
};
|
||||||
// pass the QuoteRequestor on if rfqt enabled
|
// pass the QuoteRequestor on if rfqt enabled
|
||||||
@@ -397,12 +354,13 @@ export class SwapQuoter {
|
|||||||
marketOperation,
|
marketOperation,
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
opts.gasSchedule,
|
|
||||||
opts.bridgeSlippage,
|
opts.bridgeSlippage,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use the raw gas, not scaled by gas price
|
// Use the raw gas, not scaled by gas price
|
||||||
const exchangeProxyOverhead = opts.exchangeProxyOverhead(result.sourceFlags).toNumber();
|
const exchangeProxyOverhead = BigNumber.sum(
|
||||||
|
...result.hops.map(h => opts.exchangeProxyOverhead(h.sourceFlags)),
|
||||||
|
).toNumber();
|
||||||
swapQuote.bestCaseQuoteInfo.gas += exchangeProxyOverhead;
|
swapQuote.bestCaseQuoteInfo.gas += exchangeProxyOverhead;
|
||||||
swapQuote.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
|
swapQuote.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
|
||||||
|
|
||||||
@@ -496,27 +454,23 @@ function createSwapQuote(
|
|||||||
optimizerResult: OptimizerResultWithReport,
|
optimizerResult: OptimizerResultWithReport,
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
operation: MarketOperation,
|
side: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
gasPrice: BigNumber,
|
gasPrice: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): SwapQuote {
|
): SwapQuote {
|
||||||
const {
|
const {
|
||||||
optimizedOrders,
|
hops,
|
||||||
quoteReport,
|
quoteReport,
|
||||||
extendedQuoteReportSources,
|
extendedQuoteReportSources,
|
||||||
sourceFlags,
|
|
||||||
takerAmountPerEth,
|
takerAmountPerEth,
|
||||||
makerAmountPerEth,
|
makerAmountPerEth,
|
||||||
priceComparisonsReport,
|
priceComparisonsReport,
|
||||||
} = optimizerResult;
|
} = optimizerResult;
|
||||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
|
||||||
|
|
||||||
// Calculate quote info
|
const quoteHops = hops.map(hop => toSwapQuoteHop(hop, side, slippage));
|
||||||
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop
|
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } =
|
||||||
? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage)
|
calculateQuoteInfo(quoteHops, side, assetFillAmount, gasPrice, slippage);
|
||||||
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage);
|
|
||||||
|
|
||||||
// Put together the swap quote
|
// Put together the swap quote
|
||||||
const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity;
|
const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity;
|
||||||
@@ -524,7 +478,6 @@ function createSwapQuote(
|
|||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
orders: optimizedOrders,
|
|
||||||
bestCaseQuoteInfo,
|
bestCaseQuoteInfo,
|
||||||
worstCaseQuoteInfo,
|
worstCaseQuoteInfo,
|
||||||
sourceBreakdown,
|
sourceBreakdown,
|
||||||
@@ -534,124 +487,229 @@ function createSwapQuote(
|
|||||||
makerAmountPerEth,
|
makerAmountPerEth,
|
||||||
quoteReport,
|
quoteReport,
|
||||||
extendedQuoteReportSources,
|
extendedQuoteReportSources,
|
||||||
isTwoHop,
|
|
||||||
priceComparisonsReport,
|
priceComparisonsReport,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (operation === MarketOperation.Buy) {
|
if (side === MarketOperation.Buy) {
|
||||||
return {
|
return {
|
||||||
...swapQuote,
|
...swapQuote,
|
||||||
type: MarketOperation.Buy,
|
type: MarketOperation.Buy,
|
||||||
makerTokenFillAmount: assetFillAmount,
|
makerTokenFillAmount: assetFillAmount,
|
||||||
|
maxSlippage: slippage,
|
||||||
|
hops: quoteHops,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...swapQuote,
|
...swapQuote,
|
||||||
type: MarketOperation.Sell,
|
type: MarketOperation.Sell,
|
||||||
takerTokenFillAmount: assetFillAmount,
|
takerTokenFillAmount: assetFillAmount,
|
||||||
|
maxSlippage: slippage,
|
||||||
|
hops: quoteHops,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toSwapQuoteHop(hop: OptimizedHop, side: MarketOperation, slippage: number): SwapQuoteHop {
|
||||||
|
const orders = hop.orders.map(o => toSwapQuoteOrder(o, side, slippage));
|
||||||
|
const takerAmount = side === MarketOperation.Sell ? hop.inputAmount : hop.outputAmount;
|
||||||
|
const makerAmount = side === MarketOperation.Sell ? hop.outputAmount : hop.inputAmount;
|
||||||
|
return {
|
||||||
|
orders,
|
||||||
|
makerAmount: roundMakerAmount(side, makerAmount),
|
||||||
|
takerAmount: roundTakerAmount(side, takerAmount),
|
||||||
|
makerToken: side === MarketOperation.Sell ? hop.outputToken : hop.inputToken,
|
||||||
|
takerToken: side === MarketOperation.Sell ? hop.inputToken : hop.outputToken,
|
||||||
|
minMakerAmount: slipMakerAmount(side, makerAmount, slippage),
|
||||||
|
maxTakerAmount: slipTakerAmount(side, takerAmount, slippage),
|
||||||
|
sourceFlags: hop.sourceFlags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundMakerAmount(side: MarketOperation, makerAmount: BigNumber): BigNumber {
|
||||||
|
const rm = side === MarketOperation.Sell ? BigNumber.ROUND_DOWN : BigNumber.ROUND_UP;
|
||||||
|
return makerAmount.integerValue(rm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundTakerAmount(side: MarketOperation, takerAmount: BigNumber): BigNumber {
|
||||||
|
const rm = side === MarketOperation.Sell ? BigNumber.ROUND_UP : BigNumber.ROUND_UP;
|
||||||
|
return takerAmount.integerValue(rm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function slipMakerAmount(side: MarketOperation, makerAmount: BigNumber, slippage: number): BigNumber {
|
||||||
|
return roundMakerAmount(
|
||||||
|
side,
|
||||||
|
side === MarketOperation.Sell ? makerAmount.times(1 - slippage) : makerAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function slipTakerAmount(side: MarketOperation, takerAmount: BigNumber, slippage: number): BigNumber {
|
||||||
|
return roundTakerAmount(
|
||||||
|
side,
|
||||||
|
side === MarketOperation.Sell ? takerAmount : takerAmount.times(1 + slippage),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSwapQuoteOrder(order: OptimizedOrder, side: MarketOperation, slippage: number): SwapQuoteGenericBridgeOrder | SwapQuoteNativeOrder {
|
||||||
|
const { inputToken, outputToken, inputAmount, outputAmount, ...rest } = order;
|
||||||
|
const common = {
|
||||||
|
...rest,
|
||||||
|
takerToken: side === MarketOperation.Sell ? inputToken : outputToken,
|
||||||
|
makerToken: side === MarketOperation.Sell ? outputToken : inputToken,
|
||||||
|
takerAmount: side === MarketOperation.Sell ? inputAmount : outputAmount,
|
||||||
|
makerAmount: side === MarketOperation.Sell ? outputAmount : inputAmount,
|
||||||
|
};
|
||||||
|
if (isBridgeOrder(order)) {
|
||||||
|
return {
|
||||||
|
...common,
|
||||||
|
minMakerAmount: slipMakerAmount(
|
||||||
|
side,
|
||||||
|
side === MarketOperation.Sell
|
||||||
|
? order.outputAmount
|
||||||
|
: order.inputAmount,
|
||||||
|
slippage,
|
||||||
|
),
|
||||||
|
maxTakerAmount: slipTakerAmount(
|
||||||
|
side,
|
||||||
|
side === MarketOperation.Sell
|
||||||
|
? order.inputAmount
|
||||||
|
: order.outputAmount,
|
||||||
|
slippage,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return common as SwapQuoteNativeOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBridgeOrder(order: OptimizedOrder): order is OptimizedGenericBridgeOrder {
|
||||||
|
return order.type === FillQuoteTransformerOrderType.Bridge;
|
||||||
|
}
|
||||||
|
|
||||||
function calculateQuoteInfo(
|
function calculateQuoteInfo(
|
||||||
optimizedOrders: OptimizedMarketOrder[],
|
hops: SwapQuoteHop[],
|
||||||
operation: MarketOperation,
|
side: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
fillAmount: BigNumber,
|
||||||
gasPrice: BigNumber,
|
gasPrice: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||||
const bestCaseFillResult = simulateBestCaseFill({
|
const getNextFillAmount = (fillResults: QuoteFillResult[]) => {
|
||||||
gasPrice,
|
if (fillResults.length === 0) {
|
||||||
orders: optimizedOrders,
|
return fillAmount;
|
||||||
side: operation,
|
|
||||||
fillAmount: assetFillAmount,
|
|
||||||
opts: { gasSchedule },
|
|
||||||
});
|
|
||||||
|
|
||||||
const worstCaseFillResult = simulateWorstCaseFill({
|
|
||||||
gasPrice,
|
|
||||||
orders: optimizedOrders,
|
|
||||||
side: operation,
|
|
||||||
fillAmount: assetFillAmount,
|
|
||||||
opts: { gasSchedule, slippage },
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult, 0),
|
|
||||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult, slippage),
|
|
||||||
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateTwoHopQuoteInfo(
|
|
||||||
optimizedOrders: OptimizedMarketOrder[],
|
|
||||||
operation: MarketOperation,
|
|
||||||
gasSchedule: FeeSchedule,
|
|
||||||
slippage: number,
|
|
||||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
|
||||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
|
||||||
const [firstHopFill] = firstHopOrder.fills;
|
|
||||||
const [secondHopFill] = secondHopOrder.fills;
|
|
||||||
const gas = new BigNumber(
|
|
||||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
|
||||||
firstHopSource: _.pick(firstHopFill, 'source', 'fillData'),
|
|
||||||
secondHopSource: _.pick(secondHopFill, 'source', 'fillData'),
|
|
||||||
}),
|
|
||||||
).toNumber();
|
|
||||||
const isSell = operation === MarketOperation.Sell;
|
|
||||||
|
|
||||||
return {
|
|
||||||
bestCaseQuoteInfo: {
|
|
||||||
makerAmount: isSell ? secondHopFill.output : secondHopFill.input,
|
|
||||||
takerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
|
||||||
totalTakerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
|
||||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
|
||||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
|
||||||
gas,
|
|
||||||
slippage: 0,
|
|
||||||
},
|
|
||||||
// TODO jacob consolidate this with quote simulation worstCase
|
|
||||||
worstCaseQuoteInfo: {
|
|
||||||
makerAmount: isSell
|
|
||||||
? secondHopOrder.makerAmount.times(1 - slippage).integerValue()
|
|
||||||
: secondHopOrder.makerAmount,
|
|
||||||
takerAmount: isSell
|
|
||||||
? firstHopOrder.takerAmount
|
|
||||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
|
||||||
totalTakerAmount: isSell
|
|
||||||
? firstHopOrder.takerAmount
|
|
||||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP),
|
|
||||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
|
||||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
|
||||||
gas,
|
|
||||||
slippage,
|
|
||||||
},
|
|
||||||
sourceBreakdown: {
|
|
||||||
[ERC20BridgeSource.MultiHop]: {
|
|
||||||
proportion: new BigNumber(1),
|
|
||||||
intermediateToken: secondHopOrder.takerToken,
|
|
||||||
hops: [firstHopFill.source, secondHopFill.source],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSwapQuoteOrdersBreakdown(fillAmountBySource: { [source: string]: BigNumber }): SwapQuoteOrdersBreakdown {
|
|
||||||
const totalFillAmount = BigNumber.sum(...Object.values(fillAmountBySource));
|
|
||||||
const breakdown: SwapQuoteOrdersBreakdown = {};
|
|
||||||
Object.entries(fillAmountBySource).forEach(([s, fillAmount]) => {
|
|
||||||
const source = s as keyof SwapQuoteOrdersBreakdown;
|
|
||||||
if (source === ERC20BridgeSource.MultiHop) {
|
|
||||||
// TODO jacob has a different breakdown
|
|
||||||
} else {
|
|
||||||
breakdown[source] = fillAmount.div(totalFillAmount);
|
|
||||||
}
|
}
|
||||||
|
const lastFillResult = fillResults[fillResults.length - 1];
|
||||||
|
const { totalTakerAssetAmount, makerAssetAmount } = lastFillResult;
|
||||||
|
return side === MarketOperation.Sell
|
||||||
|
? makerAssetAmount : totalTakerAssetAmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bestCaseFillResults = [];
|
||||||
|
const worstCaseFillResults = [];
|
||||||
|
const tokenPath = [];
|
||||||
|
for (const [i, hop] of hops.entries()) {
|
||||||
|
if (i === 0 || i < hops.length - 1) {
|
||||||
|
if (side == MarketOperation.Sell) {
|
||||||
|
tokenPath.push(hop.takerToken);
|
||||||
|
} else {
|
||||||
|
tokenPath.unshift(hop.makerToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i === tokenPath.length - 1) {
|
||||||
|
if (side === MarketOperation.Sell) {
|
||||||
|
tokenPath.push(hop.makerToken);
|
||||||
|
} else {
|
||||||
|
tokenPath.unshift(hop.takerToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bestCaseFillResult = simulateBestCaseFill({
|
||||||
|
gasPrice,
|
||||||
|
side,
|
||||||
|
orders: hop.orders,
|
||||||
|
fillAmount: getNextFillAmount(bestCaseFillResults),
|
||||||
|
opts: {},
|
||||||
|
});
|
||||||
|
bestCaseFillResults.push(bestCaseFillResult);
|
||||||
|
|
||||||
|
const worstCaseFillResult = simulateWorstCaseFill({
|
||||||
|
gasPrice,
|
||||||
|
side,
|
||||||
|
orders: hop.orders,
|
||||||
|
fillAmount: getNextFillAmount(worstCaseFillResults),
|
||||||
|
opts: { slippage },
|
||||||
|
});
|
||||||
|
worstCaseFillResults.push(worstCaseFillResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedBestCaseFillResult = combineQuoteFillResults(side, bestCaseFillResults);
|
||||||
|
const combinedWorstCaseFillResult = combineQuoteFillResults(side, worstCaseFillResults);
|
||||||
|
const sourceBreakdown = getSwapQuoteOrdersBreakdown(side, tokenPath, bestCaseFillResults);
|
||||||
|
return {
|
||||||
|
sourceBreakdown,
|
||||||
|
bestCaseQuoteInfo: fillResultsToQuoteInfo(combinedBestCaseFillResult),
|
||||||
|
worstCaseQuoteInfo: fillResultsToQuoteInfo(combinedWorstCaseFillResult),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function combineQuoteFillResults(side: MarketOperation, fillResults: QuoteFillResult[]): QuoteFillResult {
|
||||||
|
if (fillResults.length === 0) {
|
||||||
|
throw new Error(`Empty fillResults array`);
|
||||||
|
}
|
||||||
|
const orderedFillResults = side === MarketOperation.Sell ? fillResults : fillResults.slice().reverse();
|
||||||
|
const lastResult = orderedFillResults[orderedFillResults.length - 1];
|
||||||
|
const r = {
|
||||||
|
...orderedFillResults[0],
|
||||||
|
makerAssetAmount: lastResult.makerAssetAmount,
|
||||||
|
totalMakerAssetAmount: lastResult.totalMakerAssetAmount,
|
||||||
|
};
|
||||||
|
for (const fr of orderedFillResults.slice(1)) {
|
||||||
|
r.gas += fr.gas + 30e3;
|
||||||
|
r.protocolFeeAmount = r.protocolFeeAmount.plus(fr.protocolFeeAmount);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSwapQuoteOrdersBreakdown(side: MarketOperation, tokenPath: Address[], hopFillResults: QuoteFillResult[]): SwapQuoteOrdersBreakdown {
|
||||||
|
const cumulativeFillRatioBySource: Partial<{ [key in ERC20BridgeSource]: number }> = {};
|
||||||
|
for (const hop of hopFillResults) {
|
||||||
|
const hopTotalFillAmount = side === MarketOperation.Sell
|
||||||
|
? hop.totalTakerAssetAmount
|
||||||
|
: hop.totalMakerAssetAmount;
|
||||||
|
for (const [source, sourceFillAmount] of Object.entries(hop.fillAmountBySource)) {
|
||||||
|
cumulativeFillRatioBySource[source as ERC20BridgeSource] =
|
||||||
|
(cumulativeFillRatioBySource[source as ERC20BridgeSource] || 0)
|
||||||
|
+ sourceFillAmount.div(hopTotalFillAmount).toNumber();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const globalFillRatiosSum = Object.values(cumulativeFillRatioBySource).reduce((a, v) => a! + v!, 0);
|
||||||
|
if (!globalFillRatiosSum) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const breakdown: SwapQuoteOrdersBreakdown = {};
|
||||||
|
for (const [source, fillRatio] of Object.entries(cumulativeFillRatioBySource)) {
|
||||||
|
(breakdown as any)[source] = fillRatio! / globalFillRatiosSum;
|
||||||
|
}
|
||||||
|
const hopBreakdowns = hopFillResults.map(hop => {
|
||||||
|
const hopTotalFillAmount = side === MarketOperation.Sell
|
||||||
|
? hop.totalTakerAssetAmount
|
||||||
|
: hop.totalMakerAssetAmount;
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
...Object.entries(hop.fillAmountBySource).map(([source, sourceFillAmount]) => ({
|
||||||
|
[source as ERC20BridgeSource]: sourceFillAmount.div(hopTotalFillAmount).toNumber(),
|
||||||
|
})),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
if (hopFillResults.length > 1) {
|
||||||
|
return {
|
||||||
|
[ERC20BridgeSource.MultiHop]: {
|
||||||
|
proportion: 1,
|
||||||
|
tokenPath: tokenPath,
|
||||||
|
breakdowns: side === MarketOperation.Sell ? hopBreakdowns : hopBreakdowns.reverse(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return breakdown;
|
return breakdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillResultsToQuoteInfo(fr: QuoteFillResult, slippage: number): SwapQuoteInfo {
|
function fillResultsToQuoteInfo(fr: QuoteFillResult): SwapQuoteInfo {
|
||||||
return {
|
return {
|
||||||
makerAmount: fr.totalMakerAssetAmount,
|
makerAmount: fr.totalMakerAssetAmount,
|
||||||
takerAmount: fr.takerAssetAmount,
|
takerAmount: fr.takerAssetAmount,
|
||||||
@@ -659,7 +717,6 @@ function fillResultsToQuoteInfo(fr: QuoteFillResult, slippage: number): SwapQuot
|
|||||||
feeTakerTokenAmount: fr.takerFeeTakerAssetAmount,
|
feeTakerTokenAmount: fr.takerFeeTakerAssetAmount,
|
||||||
protocolFeeInWeiAmount: fr.protocolFeeAmount,
|
protocolFeeInWeiAmount: fr.protocolFeeAmount,
|
||||||
gas: fr.gas,
|
gas: fr.gas,
|
||||||
slippage,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { ChainId } from '@0x/contract-addresses';
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-wrappers';
|
import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-wrappers';
|
||||||
import {
|
import {
|
||||||
|
FillQuoteTransformerLimitOrderInfo,
|
||||||
FillQuoteTransformerOrderType,
|
FillQuoteTransformerOrderType,
|
||||||
|
FillQuoteTransformerRfqOrderInfo,
|
||||||
LimitOrderFields,
|
LimitOrderFields,
|
||||||
RfqOrder,
|
RfqOrder,
|
||||||
RfqOrderFields,
|
RfqOrderFields,
|
||||||
@@ -16,13 +18,22 @@ import {
|
|||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
LiquidityProviderRegistry,
|
LiquidityProviderRegistry,
|
||||||
OptimizedMarketOrder,
|
LiquidityProviderFillData,
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
|
BridgeFillData,
|
||||||
|
CurveFillData,
|
||||||
|
UniswapV2FillData,
|
||||||
|
UniswapV3FillData,
|
||||||
|
NativeOrderFillData,
|
||||||
|
MooniswapFillData,
|
||||||
} from './utils/market_operation_utils/types';
|
} from './utils/market_operation_utils/types';
|
||||||
export { SamplerMetrics } from './utils/market_operation_utils/types';
|
export { SamplerMetrics } from './utils/market_operation_utils/types';
|
||||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||||
import { MetricsProxy } from './utils/quote_requestor';
|
import { MetricsProxy } from './utils/quote_requestor';
|
||||||
|
|
||||||
|
export type Address = string;
|
||||||
|
export type Bytes = string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||||
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
|
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
|
||||||
@@ -38,19 +49,9 @@ export interface SignedOrder<T> {
|
|||||||
signature: Signature;
|
signature: Signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignedNativeOrder = SignedOrder<LimitOrderFields> | SignedOrder<RfqOrderFields>;
|
export type SignedRfqOrder = SignedOrder<RfqOrderFields>;
|
||||||
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields;
|
export type SignedLimitOrder = SignedOrder<LimitOrderFields>;
|
||||||
|
export type SignedNativeOrder = SignedLimitOrder | SignedRfqOrder;
|
||||||
/**
|
|
||||||
* fillableMakerAmount: Amount of makerAsset that is fillable
|
|
||||||
* fillableTakerAmount: Amount of takerAsset that is fillable
|
|
||||||
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAmount
|
|
||||||
*/
|
|
||||||
export interface NativeOrderFillableAmountFields {
|
|
||||||
fillableMakerAmount: BigNumber;
|
|
||||||
fillableTakerAmount: BigNumber;
|
|
||||||
fillableTakerFeeAmount: BigNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the metadata to call a smart contract with calldata.
|
* Represents the metadata to call a smart contract with calldata.
|
||||||
@@ -167,20 +168,73 @@ export interface SwapQuoteBase {
|
|||||||
takerToken: string;
|
takerToken: string;
|
||||||
makerToken: string;
|
makerToken: string;
|
||||||
gasPrice: BigNumber;
|
gasPrice: BigNumber;
|
||||||
orders: OptimizedMarketOrder[];
|
hops: SwapQuoteHop[];
|
||||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||||
quoteReport?: QuoteReport;
|
quoteReport?: QuoteReport;
|
||||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||||
priceComparisonsReport?: PriceComparisonsReport;
|
priceComparisonsReport?: PriceComparisonsReport;
|
||||||
isTwoHop: boolean;
|
|
||||||
makerTokenDecimals: number;
|
makerTokenDecimals: number;
|
||||||
takerTokenDecimals: number;
|
takerTokenDecimals: number;
|
||||||
takerAmountPerEth: BigNumber;
|
takerAmountPerEth: BigNumber;
|
||||||
makerAmountPerEth: BigNumber;
|
makerAmountPerEth: BigNumber;
|
||||||
|
maxSlippage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SwapQuoteHop {
|
||||||
|
takerToken: Address;
|
||||||
|
makerToken: Address;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
minMakerAmount: BigNumber;
|
||||||
|
maxTakerAmount: BigNumber;
|
||||||
|
sourceFlags: bigint;
|
||||||
|
orders: SwapQuoteOrder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwapQuoteOrder {
|
||||||
|
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
||||||
|
source: ERC20BridgeSource;
|
||||||
|
makerToken: string;
|
||||||
|
takerToken: string;
|
||||||
|
gasCost: number;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
isFallback: boolean;
|
||||||
|
fillData?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwapQuoteBridgeOrder<TFillData extends BridgeFillData> extends SwapQuoteOrder {
|
||||||
|
fillData: TFillData;
|
||||||
|
minMakerAmount: BigNumber;
|
||||||
|
maxTakerAmount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwapQuoteGenericBridgeOrder extends SwapQuoteBridgeOrder<BridgeFillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteUniswapV2BridgeOrder extends SwapQuoteBridgeOrder<UniswapV2FillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteUniswapV3BridgeOrder extends SwapQuoteBridgeOrder<UniswapV3FillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteLiquidityProviderBridgeOrder extends SwapQuoteBridgeOrder<LiquidityProviderFillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteMooniswapBridgeOrder extends SwapQuoteBridgeOrder<MooniswapFillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteCurveBridgeOrder extends SwapQuoteBridgeOrder<CurveFillData> {}
|
||||||
|
|
||||||
|
export interface SwapQuoteLimitOrder extends SwapQuoteOrder {
|
||||||
|
type: FillQuoteTransformerOrderType.Limit;
|
||||||
|
fillData: NativeOrderFillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwapQuoteRfqOrder extends SwapQuoteOrder {
|
||||||
|
type: FillQuoteTransformerOrderType.Rfq;
|
||||||
|
fillData: NativeOrderFillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwapQuoteNativeOrder = SwapQuoteLimitOrder | SwapQuoteRfqOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takerAssetFillAmount: The amount of takerAsset sold for makerAsset.
|
* takerAssetFillAmount: The amount of takerAsset sold for makerAsset.
|
||||||
* type: Specified MarketOperation the SwapQuote is provided for
|
* type: Specified MarketOperation the SwapQuote is provided for
|
||||||
@@ -217,22 +271,23 @@ export interface SwapQuoteInfo {
|
|||||||
makerAmount: BigNumber;
|
makerAmount: BigNumber;
|
||||||
protocolFeeInWeiAmount: BigNumber;
|
protocolFeeInWeiAmount: BigNumber;
|
||||||
gas: number;
|
gas: number;
|
||||||
slippage: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* percentage breakdown of each liquidity source used in quote
|
* percentage breakdown of each liquidity source used in quote
|
||||||
*/
|
*/
|
||||||
export type SwapQuoteOrdersBreakdown = Partial<
|
export type SwapQuoteOrdersBreakdown = Partial<
|
||||||
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: BigNumber } & {
|
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: number } & {
|
||||||
[ERC20BridgeSource.MultiHop]: {
|
[ERC20BridgeSource.MultiHop]: SwapQuoteMultiHopBreakdown;
|
||||||
proportion: BigNumber;
|
|
||||||
intermediateToken: string;
|
|
||||||
hops: ERC20BridgeSource[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface SwapQuoteMultiHopBreakdown {
|
||||||
|
proportion: number;
|
||||||
|
tokenPath: Address[];
|
||||||
|
breakdowns: Partial<{ [key in ERC20BridgeSource]: number }>[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nativeExclusivelyRFQ: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
|
* nativeExclusivelyRFQ: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
|
||||||
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
||||||
@@ -330,15 +385,13 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
|||||||
chainId: ChainId;
|
chainId: ChainId;
|
||||||
orderRefreshIntervalMs: number;
|
orderRefreshIntervalMs: number;
|
||||||
expiryBufferMs: number;
|
expiryBufferMs: number;
|
||||||
ethereumRpcUrl?: string;
|
// ethereumRpcUrl?: string;
|
||||||
contractAddresses?: AssetSwapperContractAddresses;
|
contractAddresses?: AssetSwapperContractAddresses;
|
||||||
samplerGasLimit?: number;
|
samplerGasLimit?: number;
|
||||||
multiBridgeAddress?: string;
|
// multiBridgeAddress?: string;
|
||||||
ethGasStationUrl?: string;
|
ethGasStationUrl?: string;
|
||||||
rfqt?: SwapQuoterRfqOpts;
|
rfqt?: SwapQuoterRfqOpts;
|
||||||
samplerOverrides?: SamplerOverrides;
|
samplerServiceUrl: string;
|
||||||
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
|
||||||
liquidityProviderRegistry?: LiquidityProviderRegistry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,8 +467,6 @@ export interface SamplerCallResult {
|
|||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
||||||
|
|
||||||
export enum AltQuoteModel {
|
export enum AltQuoteModel {
|
||||||
Firm = 'firm',
|
Firm = 'firm',
|
||||||
Indicative = 'indicative',
|
Indicative = 'indicative',
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { SupportedProvider } from '@0x/dev-utils';
|
|
||||||
import { SDK } from '@bancor/sdk';
|
|
||||||
import { Ethereum } from '@bancor/sdk/dist/blockchains/ethereum';
|
|
||||||
import { BlockchainType } from '@bancor/sdk/dist/types';
|
|
||||||
|
|
||||||
import { MAINNET_TOKENS } from './constants';
|
|
||||||
|
|
||||||
const findToken = (tokenAddress: string, graph: object): string =>
|
|
||||||
// If we're looking for WETH it is stored by Bancor as the 0xeee address
|
|
||||||
tokenAddress.toLowerCase() === MAINNET_TOKENS.WETH.toLowerCase()
|
|
||||||
? '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
|
|
||||||
: Object.keys(graph).filter(k => k.toLowerCase() === tokenAddress.toLowerCase())[0];
|
|
||||||
|
|
||||||
export class BancorService {
|
|
||||||
public static async createAsync(provider: SupportedProvider): Promise<BancorService> {
|
|
||||||
const sdk = await SDK.create({ ethereumNodeEndpoint: provider });
|
|
||||||
const service = new BancorService(sdk);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public sdk: SDK) {}
|
|
||||||
public getPaths(_fromToken: string, _toToken: string): string[][] {
|
|
||||||
// HACK: We reach into the blockchain object and pull in it's cache of tokens
|
|
||||||
// and we use it's internal non-async getPathsFunc
|
|
||||||
try {
|
|
||||||
const blockchain = this.sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum;
|
|
||||||
const fromToken = findToken(_fromToken, blockchain.graph);
|
|
||||||
const toToken = findToken(_toToken, blockchain.graph);
|
|
||||||
return blockchain.getPathsFunc.bind(blockchain)(fromToken, toToken);
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,637 +0,0 @@
|
|||||||
import { ChainId } from '@0x/contract-addresses';
|
|
||||||
import { BigNumber, NULL_BYTES } from '@0x/utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ACRYPTOS_BSC_INFOS,
|
|
||||||
APESWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
BAKERYSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
BELT_BSC_INFOS,
|
|
||||||
CAFESWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
CHEESESWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
COMETHSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
COMPONENT_POOLS_BY_CHAIN_ID,
|
|
||||||
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
|
||||||
CURVE_AVALANCHE_INFOS,
|
|
||||||
CURVE_FANTOM_INFOS,
|
|
||||||
CURVE_MAINNET_INFOS,
|
|
||||||
CURVE_OPTIMISM_INFOS,
|
|
||||||
CURVE_POLYGON_INFOS,
|
|
||||||
CURVE_V2_AVALANCHE_INFOS,
|
|
||||||
CURVE_V2_FANTOM_INFOS,
|
|
||||||
CURVE_V2_MAINNET_INFOS,
|
|
||||||
CURVE_V2_POLYGON_INFOS,
|
|
||||||
DFYN_ROUTER_BY_CHAIN_ID,
|
|
||||||
ELLIPSIS_BSC_INFOS,
|
|
||||||
FIREBIRDONESWAP_BSC_INFOS,
|
|
||||||
FIREBIRDONESWAP_POLYGON_INFOS,
|
|
||||||
IRONSWAP_POLYGON_INFOS,
|
|
||||||
JETSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
JULSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
KYBER_BANNED_RESERVES,
|
|
||||||
KYBER_BRIDGED_LIQUIDITY_PREFIX,
|
|
||||||
MAX_DODOV2_POOLS_QUERIED,
|
|
||||||
MAX_KYBER_RESERVES_QUERIED,
|
|
||||||
MORPHEUSSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
MSTABLE_POOLS_BY_CHAIN_ID,
|
|
||||||
NERVE_BSC_INFOS,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
PANCAKESWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
PANCAKESWAPV2_ROUTER_BY_CHAIN_ID,
|
|
||||||
PANGOLIN_ROUTER_BY_CHAIN_ID,
|
|
||||||
POLYDEX_ROUTER_BY_CHAIN_ID,
|
|
||||||
QUICKSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
SADDLE_MAINNET_INFOS,
|
|
||||||
SHELL_POOLS_BY_CHAIN_ID,
|
|
||||||
SHIBASWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
SMOOTHY_BSC_INFOS,
|
|
||||||
SMOOTHY_MAINNET_INFOS,
|
|
||||||
SNOWSWAP_MAINNET_INFOS,
|
|
||||||
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
SWERVE_MAINNET_INFOS,
|
|
||||||
SYNAPSE_AVALANCHE_INFOS,
|
|
||||||
SYNAPSE_BSC_INFOS,
|
|
||||||
SYNAPSE_FANTOM_INFOS,
|
|
||||||
SYNAPSE_MAINNET_INFOS,
|
|
||||||
SYNAPSE_OPTIMISM_INFOS,
|
|
||||||
SYNAPSE_POLYGON_INFOS,
|
|
||||||
TRADER_JOE_ROUTER_BY_CHAIN_ID,
|
|
||||||
UBESWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
UNISWAPV2_ROUTER_BY_CHAIN_ID,
|
|
||||||
WAULTSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
XSIGMA_MAINNET_INFOS,
|
|
||||||
} from './constants';
|
|
||||||
import { CurveInfo, ERC20BridgeSource } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter Kyber reserves which should not be used (0xbb bridged reserves)
|
|
||||||
* @param reserveId Kyber reserveId
|
|
||||||
*/
|
|
||||||
export function isAllowedKyberReserveId(reserveId: string): boolean {
|
|
||||||
return (
|
|
||||||
reserveId !== NULL_BYTES &&
|
|
||||||
!reserveId.startsWith(KYBER_BRIDGED_LIQUIDITY_PREFIX) &&
|
|
||||||
!KYBER_BANNED_RESERVES.includes(reserveId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line: completed-docs ban-types
|
|
||||||
export function isValidAddress(address: string | String): address is string {
|
|
||||||
return (typeof address === 'string' || address instanceof String) && address.toString() !== NULL_ADDRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the offsets to be used to discover Kyber reserves
|
|
||||||
*/
|
|
||||||
export function getKyberOffsets(): BigNumber[] {
|
|
||||||
return Array(MAX_KYBER_RESERVES_QUERIED)
|
|
||||||
.fill(0)
|
|
||||||
.map((_v, i) => new BigNumber(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getDodoV2Offsets(): BigNumber[] {
|
|
||||||
return Array(MAX_DODOV2_POOLS_QUERIED)
|
|
||||||
.fill(0)
|
|
||||||
.map((_v, i) => new BigNumber(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getShellsForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(SHELL_POOLS_BY_CHAIN_ID[chainId])
|
|
||||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
|
||||||
.map(i => i.poolAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getComponentForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(COMPONENT_POOLS_BY_CHAIN_ID[chainId])
|
|
||||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
|
||||||
.map(i => i.poolAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getMStableForPair(chainId: ChainId, takerToken: string, makerToken: string): string[] {
|
|
||||||
if (chainId !== ChainId.Mainnet && chainId !== ChainId.Polygon) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(MSTABLE_POOLS_BY_CHAIN_ID[chainId])
|
|
||||||
.filter(c => [makerToken, takerToken].every(t => c.tokens.includes(t)))
|
|
||||||
.map(i => i.poolAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getCurveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
switch (chainId) {
|
|
||||||
case ChainId.Mainnet:
|
|
||||||
return Object.values(CURVE_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Polygon:
|
|
||||||
return Object.values(CURVE_POLYGON_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Fantom:
|
|
||||||
return Object.values(CURVE_FANTOM_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Avalanche:
|
|
||||||
return Object.values(CURVE_AVALANCHE_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Optimism:
|
|
||||||
return Object.values(CURVE_OPTIMISM_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
|
||||||
export function getCurveV2InfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
switch (chainId) {
|
|
||||||
case ChainId.Mainnet:
|
|
||||||
return Object.values(CURVE_V2_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Polygon:
|
|
||||||
return Object.values(CURVE_V2_POLYGON_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Fantom:
|
|
||||||
return Object.values(CURVE_V2_FANTOM_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Avalanche:
|
|
||||||
return Object.values(CURVE_V2_AVALANCHE_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSwerveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(SWERVE_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSnowSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(SNOWSWAP_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNerveInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.BSC) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(NERVE_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSynapseInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
switch (chainId) {
|
|
||||||
case ChainId.Mainnet:
|
|
||||||
return Object.values(SYNAPSE_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Optimism:
|
|
||||||
return Object.values(SYNAPSE_OPTIMISM_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.BSC:
|
|
||||||
return Object.values(SYNAPSE_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Polygon:
|
|
||||||
return Object.values(SYNAPSE_POLYGON_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Fantom:
|
|
||||||
return Object.values(SYNAPSE_FANTOM_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ChainId.Avalanche:
|
|
||||||
return Object.values(SYNAPSE_AVALANCHE_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFirebirdOneSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId === ChainId.BSC) {
|
|
||||||
return Object.values(FIREBIRDONESWAP_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (chainId === ChainId.Polygon) {
|
|
||||||
return Object.values(FIREBIRDONESWAP_POLYGON_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBeltInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.BSC) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(BELT_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEllipsisInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.BSC) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(ELLIPSIS_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSmoothyInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId === ChainId.BSC) {
|
|
||||||
return Object.values(SMOOTHY_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (chainId === ChainId.Mainnet) {
|
|
||||||
return Object.values(SMOOTHY_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(SADDLE_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getIronSwapInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.Polygon) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(IRONSWAP_POLYGON_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getXSigmaInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.Mainnet) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(XSIGMA_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAcryptosInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId !== ChainId.BSC) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Object.values(ACRYPTOS_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) && [makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShellLikeInfosForPair(
|
|
||||||
chainId: ChainId,
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
source: ERC20BridgeSource.Shell | ERC20BridgeSource.Component | ERC20BridgeSource.MStable,
|
|
||||||
): string[] {
|
|
||||||
switch (source) {
|
|
||||||
case ERC20BridgeSource.Shell:
|
|
||||||
return getShellsForPair(chainId, takerToken, makerToken);
|
|
||||||
case ERC20BridgeSource.Component:
|
|
||||||
return getComponentForPair(chainId, takerToken, makerToken);
|
|
||||||
case ERC20BridgeSource.MStable:
|
|
||||||
return getMStableForPair(chainId, takerToken, makerToken);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown Shell like source ${source}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CurveDetailedInfo extends CurveInfo {
|
|
||||||
makerTokenIdx: number;
|
|
||||||
takerTokenIdx: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurveLikeInfosForPair(
|
|
||||||
chainId: ChainId,
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
source:
|
|
||||||
| ERC20BridgeSource.Curve
|
|
||||||
| ERC20BridgeSource.CurveV2
|
|
||||||
| ERC20BridgeSource.Swerve
|
|
||||||
| ERC20BridgeSource.SnowSwap
|
|
||||||
| ERC20BridgeSource.Nerve
|
|
||||||
| ERC20BridgeSource.Synapse
|
|
||||||
| ERC20BridgeSource.Belt
|
|
||||||
| ERC20BridgeSource.Ellipsis
|
|
||||||
| ERC20BridgeSource.Smoothy
|
|
||||||
| ERC20BridgeSource.Saddle
|
|
||||||
| ERC20BridgeSource.IronSwap
|
|
||||||
| ERC20BridgeSource.XSigma
|
|
||||||
| ERC20BridgeSource.FirebirdOneSwap
|
|
||||||
| ERC20BridgeSource.ACryptos,
|
|
||||||
): CurveDetailedInfo[] {
|
|
||||||
let pools: CurveInfo[] = [];
|
|
||||||
switch (source) {
|
|
||||||
case ERC20BridgeSource.Curve:
|
|
||||||
pools = getCurveInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.CurveV2:
|
|
||||||
pools = getCurveV2InfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Swerve:
|
|
||||||
pools = getSwerveInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.SnowSwap:
|
|
||||||
pools = getSnowSwapInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Nerve:
|
|
||||||
pools = getNerveInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Synapse:
|
|
||||||
pools = getSynapseInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Belt:
|
|
||||||
pools = getBeltInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Ellipsis:
|
|
||||||
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
pools = getSmoothyInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Saddle:
|
|
||||||
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.XSigma:
|
|
||||||
pools = getXSigmaInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.FirebirdOneSwap:
|
|
||||||
pools = getFirebirdOneSwapInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.IronSwap:
|
|
||||||
pools = getIronSwapInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.ACryptos:
|
|
||||||
pools = getAcryptosInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown Curve like source ${source}`);
|
|
||||||
}
|
|
||||||
return pools.map(pool => ({
|
|
||||||
...pool,
|
|
||||||
makerTokenIdx: pool.tokens.indexOf(makerToken),
|
|
||||||
takerTokenIdx: pool.tokens.indexOf(takerToken),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uniswapV2LikeRouterAddress(
|
|
||||||
chainId: ChainId,
|
|
||||||
source:
|
|
||||||
| ERC20BridgeSource.UniswapV2
|
|
||||||
| ERC20BridgeSource.SushiSwap
|
|
||||||
| ERC20BridgeSource.CryptoCom
|
|
||||||
| ERC20BridgeSource.PancakeSwap
|
|
||||||
| ERC20BridgeSource.PancakeSwapV2
|
|
||||||
| ERC20BridgeSource.BakerySwap
|
|
||||||
| ERC20BridgeSource.ApeSwap
|
|
||||||
| ERC20BridgeSource.CafeSwap
|
|
||||||
| ERC20BridgeSource.CheeseSwap
|
|
||||||
| ERC20BridgeSource.JulSwap
|
|
||||||
| ERC20BridgeSource.QuickSwap
|
|
||||||
| ERC20BridgeSource.ComethSwap
|
|
||||||
| ERC20BridgeSource.Dfyn
|
|
||||||
| ERC20BridgeSource.WaultSwap
|
|
||||||
| ERC20BridgeSource.Polydex
|
|
||||||
| ERC20BridgeSource.ShibaSwap
|
|
||||||
| ERC20BridgeSource.JetSwap
|
|
||||||
| ERC20BridgeSource.TraderJoe
|
|
||||||
| ERC20BridgeSource.Pangolin
|
|
||||||
| ERC20BridgeSource.UbeSwap
|
|
||||||
| ERC20BridgeSource.MorpheusSwap
|
|
||||||
| ERC20BridgeSource.SpookySwap
|
|
||||||
| ERC20BridgeSource.SpiritSwap,
|
|
||||||
): string {
|
|
||||||
switch (source) {
|
|
||||||
case ERC20BridgeSource.UniswapV2:
|
|
||||||
return UNISWAPV2_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.SushiSwap:
|
|
||||||
return SUSHISWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.CryptoCom:
|
|
||||||
return CRYPTO_COM_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.PancakeSwap:
|
|
||||||
return PANCAKESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.PancakeSwapV2:
|
|
||||||
return PANCAKESWAPV2_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.BakerySwap:
|
|
||||||
return BAKERYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.ApeSwap:
|
|
||||||
return APESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.CafeSwap:
|
|
||||||
return CAFESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.CheeseSwap:
|
|
||||||
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.JulSwap:
|
|
||||||
return JULSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.QuickSwap:
|
|
||||||
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
return COMETHSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
|
||||||
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.WaultSwap:
|
|
||||||
return WAULTSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.Polydex:
|
|
||||||
return POLYDEX_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.ShibaSwap:
|
|
||||||
return SHIBASWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.JetSwap:
|
|
||||||
return JETSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.Pangolin:
|
|
||||||
return PANGOLIN_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.TraderJoe:
|
|
||||||
return TRADER_JOE_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.UbeSwap:
|
|
||||||
return UBESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.MorpheusSwap:
|
|
||||||
return MORPHEUSSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.SpookySwap:
|
|
||||||
return SPOOKYSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.SpiritSwap:
|
|
||||||
return SPIRITSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown UniswapV2 like source ${source}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const BAD_TOKENS_BY_SOURCE: Partial<{ [key in ERC20BridgeSource]: string[] }> = {
|
|
||||||
[ERC20BridgeSource.Uniswap]: [
|
|
||||||
'0xb8c77482e45f1f44de1745f52c74426c631bdd52', // BNB
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isBadTokenForSource(token: string, source: ERC20BridgeSource): boolean {
|
|
||||||
return (BAD_TOKENS_BY_SOURCE[source] || []).includes(token.toLowerCase());
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Web3Wrapper } from '@0x/dev-utils';
|
import { Web3Wrapper } from '@0x/dev-utils';
|
||||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
import { BigNumber, logUtils } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
@@ -8,10 +7,6 @@ import { MarketOperation } from '../../types';
|
|||||||
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
|
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
|
||||||
import {
|
import {
|
||||||
ComparisonPrice,
|
ComparisonPrice,
|
||||||
ERC20BridgeSource,
|
|
||||||
ExchangeProxyOverhead,
|
|
||||||
FeeEstimate,
|
|
||||||
FeeSchedule,
|
|
||||||
MarketSideLiquidity,
|
MarketSideLiquidity,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@@ -29,41 +24,20 @@ export function getComparisonPrices(
|
|||||||
adjustedRate: BigNumber,
|
adjustedRate: BigNumber,
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
marketSideLiquidity: MarketSideLiquidity,
|
marketSideLiquidity: MarketSideLiquidity,
|
||||||
feeSchedule: FeeSchedule,
|
gasPrice: BigNumber,
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead,
|
|
||||||
): ComparisonPrice {
|
): ComparisonPrice {
|
||||||
let wholeOrder: BigNumber | undefined;
|
let wholeOrder: BigNumber | undefined;
|
||||||
let feeInEth: BigNumber | number;
|
let feeInEth = gasPrice.times(100e3);
|
||||||
|
|
||||||
// HACK: get the fee penalty of a single 0x native order
|
|
||||||
// The FeeSchedule function takes in a `FillData` object and returns a fee estimate in ETH
|
|
||||||
// We don't have fill data here, we just want the cost of a single native order, so we pass in undefined
|
|
||||||
// This works because the feeSchedule returns a constant for Native orders, this will need
|
|
||||||
// to be tweaked if the feeSchedule for native orders uses the fillData passed in
|
|
||||||
// 2 potential issues: there is no native fee schedule or the fee schedule depends on fill data
|
|
||||||
if (feeSchedule[ERC20BridgeSource.Native] === undefined) {
|
|
||||||
logUtils.warn('ComparisonPrice function did not find native order fee schedule');
|
|
||||||
|
|
||||||
return { wholeOrder };
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const fillFeeInEth = new BigNumber(
|
|
||||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }),
|
|
||||||
);
|
|
||||||
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
|
||||||
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
|
||||||
} catch {
|
|
||||||
logUtils.warn('Native order fee schedule requires fill data');
|
|
||||||
|
|
||||||
return { wholeOrder };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const [inputAmountPerEth, outputAmountPerEth] = [
|
||||||
|
marketSideLiquidity.tokenAmountPerEth[marketSideLiquidity.inputToken],
|
||||||
|
marketSideLiquidity.tokenAmountPerEth[marketSideLiquidity.outputToken],
|
||||||
|
];
|
||||||
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
// Calc native order fee penalty in output unit (maker units for sells, taker unit for buys)
|
||||||
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
const feePenalty = !outputAmountPerEth.isZero()
|
||||||
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
|
? outputAmountPerEth.times(feeInEth)
|
||||||
: // if it's a sell, the input token is the taker token
|
: // if it's a sell, the input token is the taker token
|
||||||
marketSideLiquidity.inputAmountPerEth
|
inputAmountPerEth
|
||||||
.times(feeInEth)
|
.times(feeInEth)
|
||||||
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
|||||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils } from '@0x/utils';
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
|
|
||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
import { NativeOrderWithFillableAmounts } from '../native_orders';
|
||||||
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
import { DexSample, ERC20BridgeSource, Fill, GenericBridgeFill, NativeOrderFill } from './types';
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||||
|
|
||||||
@@ -18,12 +19,9 @@ export function createFills(opts: {
|
|||||||
targetInput?: BigNumber;
|
targetInput?: BigNumber;
|
||||||
outputAmountPerEth?: BigNumber;
|
outputAmountPerEth?: BigNumber;
|
||||||
inputAmountPerEth?: BigNumber;
|
inputAmountPerEth?: BigNumber;
|
||||||
excludedSources?: ERC20BridgeSource[];
|
gasPrice: BigNumber;
|
||||||
feeSchedule?: FeeSchedule;
|
|
||||||
}): Fill[][] {
|
}): Fill[][] {
|
||||||
const { side } = opts;
|
const { side } = opts;
|
||||||
const excludedSources = opts.excludedSources || [];
|
|
||||||
const feeSchedule = opts.feeSchedule || {};
|
|
||||||
const orders = opts.orders || [];
|
const orders = opts.orders || [];
|
||||||
const dexQuotes = opts.dexQuotes || [];
|
const dexQuotes = opts.dexQuotes || [];
|
||||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||||
@@ -35,15 +33,15 @@ export function createFills(opts: {
|
|||||||
opts.targetInput,
|
opts.targetInput,
|
||||||
outputAmountPerEth,
|
outputAmountPerEth,
|
||||||
inputAmountPerEth,
|
inputAmountPerEth,
|
||||||
feeSchedule,
|
opts.gasPrice,
|
||||||
);
|
);
|
||||||
// Create DEX fills.
|
// Create DEX fills.
|
||||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, opts.gasPrice),
|
||||||
);
|
);
|
||||||
return [...dexFills, nativeFills]
|
return [...dexFills, nativeFills]
|
||||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
.map(p => clipFillsToInput(p, opts.targetInput))
|
||||||
.filter(fills => hasLiquidity(fills) && !excludedSources.includes(fills[0].source));
|
.filter(fills => hasLiquidity(fills));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
|
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
|
||||||
@@ -95,25 +93,35 @@ export function nativeOrdersToFills(
|
|||||||
targetInput: BigNumber = POSITIVE_INF,
|
targetInput: BigNumber = POSITIVE_INF,
|
||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
inputAmountPerEth: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
gasPrice: BigNumber,
|
||||||
filterNegativeAdjustedRateOrders: boolean = true,
|
filterNegativeAdjustedRateOrders: boolean = true,
|
||||||
): Fill[] {
|
): NativeOrderFill[] {
|
||||||
|
if (orders.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
// Create a single path from all orders.
|
// Create a single path from all orders.
|
||||||
let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
|
let fills: Array<NativeOrderFill & { adjustedRate: BigNumber }> = [];
|
||||||
for (const o of orders) {
|
for (const o of orders) {
|
||||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
|
const { fillableTakerAmount, fillableMakerAmount, type } = o;
|
||||||
const makerAmount = fillableMakerAmount;
|
// TODO(lawrence): handle taker fees.
|
||||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
if (o.fillableTakerFeeAmount.gt(0)) {
|
||||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
continue;
|
||||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
}
|
||||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
let input, output;
|
||||||
|
if (side === MarketOperation.Sell) {
|
||||||
|
input = fillableTakerAmount;
|
||||||
|
output = fillableMakerAmount;
|
||||||
|
} else {
|
||||||
|
input = fillableMakerAmount;
|
||||||
|
output = fillableTakerAmount;
|
||||||
|
}
|
||||||
const outputPenalty = ethToOutputAmount({
|
const outputPenalty = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
inputAmountPerEth,
|
inputAmountPerEth,
|
||||||
outputAmountPerEth,
|
outputAmountPerEth,
|
||||||
ethAmount: fee,
|
ethAmount: gasPrice.times(o.gasCost),
|
||||||
});
|
});
|
||||||
// targetInput can be less than the order size
|
// targetInput can be less than the order size
|
||||||
// whilst the penalty is constant, it affects the adjusted output
|
// whilst the penalty is constant, it affects the adjusted output
|
||||||
@@ -132,17 +140,22 @@ export function nativeOrdersToFills(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fills.push({
|
fills.push({
|
||||||
|
type,
|
||||||
sourcePathId,
|
sourcePathId,
|
||||||
adjustedRate,
|
|
||||||
adjustedOutput,
|
adjustedOutput,
|
||||||
|
adjustedRate,
|
||||||
input: clippedInput,
|
input: clippedInput,
|
||||||
output: clippedOutput,
|
output: clippedOutput,
|
||||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||||
index: 0, // TBD
|
index: 0, // TBD
|
||||||
parent: undefined, // TBD
|
parent: undefined, // TBD
|
||||||
source: ERC20BridgeSource.Native,
|
source: ERC20BridgeSource.Native,
|
||||||
type,
|
gasCost: o.gasCost,
|
||||||
fillData: { ...o },
|
data: {
|
||||||
|
order: o.order,
|
||||||
|
signature: o.signature,
|
||||||
|
fillableTakerAmount: o.fillableTakerAmount,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Sort by descending adjusted rate.
|
// Sort by descending adjusted rate.
|
||||||
@@ -160,10 +173,10 @@ export function dexSamplesToFills(
|
|||||||
samples: DexSample[],
|
samples: DexSample[],
|
||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
inputAmountPerEth: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
gasPrice: BigNumber,
|
||||||
): Fill[] {
|
): GenericBridgeFill[] {
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
const fills: Fill[] = [];
|
const fills: GenericBridgeFill[] = [];
|
||||||
// Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
|
// Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
|
||||||
// We need not worry about Kyber fills going to UniswapReserve as the input amount
|
// We need not worry about Kyber fills going to UniswapReserve as the input amount
|
||||||
// we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
|
// we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
|
||||||
@@ -172,10 +185,10 @@ export function dexSamplesToFills(
|
|||||||
for (let i = 0; i < nonzeroSamples.length; i++) {
|
for (let i = 0; i < nonzeroSamples.length; i++) {
|
||||||
const sample = nonzeroSamples[i];
|
const sample = nonzeroSamples[i];
|
||||||
const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
|
const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
|
||||||
const { source, fillData } = sample;
|
const { source, encodedFillData, metadata } = sample;
|
||||||
const input = sample.input.minus(prevSample ? prevSample.input : 0);
|
const input = sample.input.minus(prevSample ? prevSample.input : 0);
|
||||||
const output = sample.output.minus(prevSample ? prevSample.output : 0);
|
const output = sample.output.minus(prevSample ? prevSample.output : 0);
|
||||||
const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0;
|
const fee = gasPrice.times(sample.gasCost);
|
||||||
let penalty = ZERO_AMOUNT;
|
let penalty = ZERO_AMOUNT;
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
// Only the first fill in a DEX path incurs a penalty.
|
// Only the first fill in a DEX path incurs a penalty.
|
||||||
@@ -195,11 +208,15 @@ export function dexSamplesToFills(
|
|||||||
output,
|
output,
|
||||||
adjustedOutput,
|
adjustedOutput,
|
||||||
source,
|
source,
|
||||||
fillData,
|
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
gasCost: sample.gasCost,
|
||||||
index: i,
|
index: i,
|
||||||
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
||||||
flags: SOURCE_FLAGS[source],
|
flags: SOURCE_FLAGS[source],
|
||||||
|
data: {
|
||||||
|
...metadata,
|
||||||
|
encodedFillData,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return fills;
|
return fills;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,6 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { Omit } from '../../types';
|
|
||||||
|
|
||||||
import { ZERO_AMOUNT } from './constants';
|
|
||||||
import { getTwoHopAdjustedRate } from './rate_utils';
|
|
||||||
import {
|
import {
|
||||||
DexSample,
|
|
||||||
ExchangeProxyOverhead,
|
|
||||||
FeeSchedule,
|
|
||||||
MarketSideLiquidity,
|
|
||||||
MultiHopFillData,
|
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@@ -30,49 +20,3 @@ export function getIntermediateTokens(
|
|||||||
token => token.toLowerCase() !== makerToken.toLowerCase() && token.toLowerCase() !== takerToken.toLowerCase(),
|
token => token.toLowerCase() !== makerToken.toLowerCase() && token.toLowerCase() !== takerToken.toLowerCase(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the best two-hop quote and the fee-adjusted rate of that quote.
|
|
||||||
*/
|
|
||||||
export function getBestTwoHopQuote(
|
|
||||||
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
|
||||||
feeSchedule?: FeeSchedule,
|
|
||||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
|
||||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
|
||||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
|
||||||
const { twoHopQuotes } = quotes;
|
|
||||||
// Ensure the expected data we require exists. In the case where all hops reverted
|
|
||||||
// or there were no sources included that allowed for multi hop,
|
|
||||||
// we can end up with empty, but not undefined, fill data
|
|
||||||
const filteredQuotes = twoHopQuotes.filter(
|
|
||||||
quote =>
|
|
||||||
quote &&
|
|
||||||
quote.fillData &&
|
|
||||||
quote.fillData.firstHopSource &&
|
|
||||||
quote.fillData.secondHopSource &&
|
|
||||||
quote.output.isGreaterThan(ZERO_AMOUNT),
|
|
||||||
);
|
|
||||||
if (filteredQuotes.length === 0) {
|
|
||||||
return { quote: undefined, adjustedRate: ZERO_AMOUNT };
|
|
||||||
}
|
|
||||||
const best = filteredQuotes
|
|
||||||
.map(quote =>
|
|
||||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
|
||||||
)
|
|
||||||
.reduce(
|
|
||||||
(prev, curr, i) =>
|
|
||||||
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: filteredQuotes[i] } : prev,
|
|
||||||
{
|
|
||||||
adjustedRate: getTwoHopAdjustedRate(
|
|
||||||
side,
|
|
||||||
filteredQuotes[0],
|
|
||||||
inputAmount,
|
|
||||||
outputAmountPerEth,
|
|
||||||
feeSchedule,
|
|
||||||
exchangeProxyOverhead,
|
|
||||||
),
|
|
||||||
quote: filteredQuotes[0],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,84 +1,19 @@
|
|||||||
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
|
||||||
|
|
||||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
import { Address, MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
|
||||||
import {
|
import {
|
||||||
AaveV2FillData,
|
|
||||||
AggregationError,
|
AggregationError,
|
||||||
BalancerFillData,
|
CollapsedGenericBridgeFill,
|
||||||
BalancerV2FillData,
|
|
||||||
BancorFillData,
|
|
||||||
CollapsedFill,
|
|
||||||
CompoundFillData,
|
|
||||||
CurveFillData,
|
|
||||||
DexSample,
|
|
||||||
DODOFillData,
|
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
FillData,
|
CollapsedNativeOrderFill,
|
||||||
FinalUniswapV3FillData,
|
OptimizedGenericBridgeOrder,
|
||||||
GenericRouterFillData,
|
OptimizedLimitOrder,
|
||||||
KyberDmmFillData,
|
OptimizedRfqOrder,
|
||||||
KyberFillData,
|
|
||||||
LidoFillData,
|
|
||||||
LiquidityProviderFillData,
|
|
||||||
MakerPsmFillData,
|
|
||||||
MooniswapFillData,
|
|
||||||
MultiHopFillData,
|
|
||||||
NativeCollapsedFill,
|
|
||||||
NativeLimitOrderFillData,
|
|
||||||
NativeRfqOrderFillData,
|
|
||||||
OptimizedMarketBridgeOrder,
|
|
||||||
OptimizedMarketOrder,
|
|
||||||
OptimizedMarketOrderBase,
|
|
||||||
OrderDomain,
|
|
||||||
ShellFillData,
|
|
||||||
UniswapV2FillData,
|
|
||||||
UniswapV3FillData,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// tslint:disable completed-docs
|
// tslint:disable completed-docs
|
||||||
|
|
||||||
export interface CreateOrderFromPathOpts {
|
|
||||||
side: MarketOperation;
|
|
||||||
inputToken: string;
|
|
||||||
outputToken: string;
|
|
||||||
orderDomain: OrderDomain;
|
|
||||||
contractAddresses: AssetSwapperContractAddresses;
|
|
||||||
bridgeSlippage: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createOrdersFromTwoHopSample(
|
|
||||||
sample: DexSample<MultiHopFillData>,
|
|
||||||
opts: CreateOrderFromPathOpts,
|
|
||||||
): OptimizedMarketOrder[] {
|
|
||||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
|
||||||
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
|
|
||||||
const firstHopFill: CollapsedFill = {
|
|
||||||
sourcePathId: '',
|
|
||||||
source: firstHopSource.source,
|
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
|
||||||
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
|
||||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
|
||||||
subFills: [],
|
|
||||||
fillData: firstHopSource.fillData,
|
|
||||||
};
|
|
||||||
const secondHopFill: CollapsedFill = {
|
|
||||||
sourcePathId: '',
|
|
||||||
source: secondHopSource.source,
|
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
|
||||||
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
|
||||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
|
||||||
subFills: [],
|
|
||||||
fillData: secondHopSource.fillData,
|
|
||||||
};
|
|
||||||
return [
|
|
||||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
|
||||||
createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts.side),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): string {
|
export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): string {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
@@ -207,352 +142,51 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder): string {
|
|
||||||
let bridgeData: string;
|
|
||||||
if (
|
|
||||||
order.source === ERC20BridgeSource.MultiHop ||
|
|
||||||
order.source === ERC20BridgeSource.MultiBridge ||
|
|
||||||
order.source === ERC20BridgeSource.Native
|
|
||||||
) {
|
|
||||||
throw new Error('Invalid order to encode for Bridge Data');
|
|
||||||
}
|
|
||||||
const encoder = BRIDGE_ENCODERS[order.source];
|
|
||||||
|
|
||||||
if (!encoder) {
|
|
||||||
throw new Error(AggregationError.NoBridgeForSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (order.source) {
|
|
||||||
case ERC20BridgeSource.Curve:
|
|
||||||
case ERC20BridgeSource.CurveV2:
|
|
||||||
case ERC20BridgeSource.Swerve:
|
|
||||||
case ERC20BridgeSource.SnowSwap:
|
|
||||||
case ERC20BridgeSource.Nerve:
|
|
||||||
case ERC20BridgeSource.Synapse:
|
|
||||||
case ERC20BridgeSource.Belt:
|
|
||||||
case ERC20BridgeSource.Ellipsis:
|
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
case ERC20BridgeSource.Saddle:
|
|
||||||
case ERC20BridgeSource.XSigma:
|
|
||||||
case ERC20BridgeSource.FirebirdOneSwap:
|
|
||||||
case ERC20BridgeSource.IronSwap:
|
|
||||||
case ERC20BridgeSource.ACryptos:
|
|
||||||
const curveFillData = (order as OptimizedMarketBridgeOrder<CurveFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([
|
|
||||||
curveFillData.pool.poolAddress,
|
|
||||||
curveFillData.pool.exchangeFunctionSelector,
|
|
||||||
curveFillData.fromTokenIdx,
|
|
||||||
curveFillData.toTokenIdx,
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Balancer:
|
|
||||||
case ERC20BridgeSource.Cream:
|
|
||||||
const balancerFillData = (order as OptimizedMarketBridgeOrder<BalancerFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([balancerFillData.poolAddress]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.BalancerV2:
|
|
||||||
case ERC20BridgeSource.Beethovenx:
|
|
||||||
const balancerV2FillData = (order as OptimizedMarketBridgeOrder<BalancerV2FillData>).fillData;
|
|
||||||
const { vault, poolId } = balancerV2FillData;
|
|
||||||
bridgeData = encoder.encode([vault, poolId]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Bancor:
|
|
||||||
const bancorFillData = (order as OptimizedMarketBridgeOrder<BancorFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([bancorFillData.networkAddress, bancorFillData.path]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.UniswapV2:
|
|
||||||
case ERC20BridgeSource.SushiSwap:
|
|
||||||
case ERC20BridgeSource.CryptoCom:
|
|
||||||
case ERC20BridgeSource.Linkswap:
|
|
||||||
case ERC20BridgeSource.PancakeSwap:
|
|
||||||
case ERC20BridgeSource.PancakeSwapV2:
|
|
||||||
case ERC20BridgeSource.BakerySwap:
|
|
||||||
case ERC20BridgeSource.ApeSwap:
|
|
||||||
case ERC20BridgeSource.CafeSwap:
|
|
||||||
case ERC20BridgeSource.CheeseSwap:
|
|
||||||
case ERC20BridgeSource.JulSwap:
|
|
||||||
case ERC20BridgeSource.QuickSwap:
|
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
|
||||||
case ERC20BridgeSource.WaultSwap:
|
|
||||||
case ERC20BridgeSource.Polydex:
|
|
||||||
case ERC20BridgeSource.ShibaSwap:
|
|
||||||
case ERC20BridgeSource.JetSwap:
|
|
||||||
case ERC20BridgeSource.Pangolin:
|
|
||||||
case ERC20BridgeSource.TraderJoe:
|
|
||||||
case ERC20BridgeSource.UbeSwap:
|
|
||||||
case ERC20BridgeSource.SpiritSwap:
|
|
||||||
case ERC20BridgeSource.SpookySwap:
|
|
||||||
case ERC20BridgeSource.MorpheusSwap:
|
|
||||||
const uniswapV2FillData = (order as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([uniswapV2FillData.router, uniswapV2FillData.tokenAddressPath]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Kyber:
|
|
||||||
const kyberFillData = (order as OptimizedMarketBridgeOrder<KyberFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([kyberFillData.networkProxy, kyberFillData.hint]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Mooniswap:
|
|
||||||
const mooniswapFillData = (order as OptimizedMarketBridgeOrder<MooniswapFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([mooniswapFillData.poolAddress]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Dodo:
|
|
||||||
const dodoFillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([
|
|
||||||
dodoFillData.helperAddress,
|
|
||||||
dodoFillData.poolAddress,
|
|
||||||
dodoFillData.isSellBase,
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.DodoV2:
|
|
||||||
const dodoV2FillData = (order as OptimizedMarketBridgeOrder<DODOFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([dodoV2FillData.poolAddress, dodoV2FillData.isSellBase]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Shell:
|
|
||||||
case ERC20BridgeSource.Component:
|
|
||||||
const shellFillData = (order as OptimizedMarketBridgeOrder<ShellFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([shellFillData.poolAddress]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.LiquidityProvider:
|
|
||||||
const lpFillData = (order as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([lpFillData.poolAddress, tokenAddressEncoder.encode([order.takerToken])]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Uniswap:
|
|
||||||
const uniFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([uniFillData.router]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Eth2Dai:
|
|
||||||
const oasisFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([oasisFillData.router]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.MStable:
|
|
||||||
const mStableFillData = (order as OptimizedMarketBridgeOrder<GenericRouterFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([mStableFillData.router]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.MakerPsm:
|
|
||||||
const psmFillData = (order as OptimizedMarketBridgeOrder<MakerPsmFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([psmFillData.psmAddress, psmFillData.gemTokenAddress]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.UniswapV3:
|
|
||||||
const uniswapV3FillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([uniswapV3FillData.router, uniswapV3FillData.uniswapPath]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.KyberDmm:
|
|
||||||
const kyberDmmFillData = (order as OptimizedMarketBridgeOrder<KyberDmmFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([
|
|
||||||
kyberDmmFillData.router,
|
|
||||||
kyberDmmFillData.poolsPath,
|
|
||||||
kyberDmmFillData.tokenAddressPath,
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Lido:
|
|
||||||
const lidoFillData = (order as OptimizedMarketBridgeOrder<LidoFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([lidoFillData.stEthTokenAddress]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.AaveV2:
|
|
||||||
const aaveFillData = (order as OptimizedMarketBridgeOrder<AaveV2FillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([aaveFillData.lendingPool, aaveFillData.aToken]);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Compound:
|
|
||||||
const compoundFillData = (order as OptimizedMarketBridgeOrder<CompoundFillData>).fillData;
|
|
||||||
bridgeData = encoder.encode([compoundFillData.cToken]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(AggregationError.NoBridgeForSource);
|
|
||||||
}
|
|
||||||
return bridgeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createBridgeOrder(
|
export function createBridgeOrder(
|
||||||
fill: CollapsedFill,
|
fill: CollapsedGenericBridgeFill,
|
||||||
makerToken: string,
|
inputToken: Address,
|
||||||
takerToken: string,
|
outputToken: Address,
|
||||||
side: MarketOperation,
|
): OptimizedGenericBridgeOrder {
|
||||||
): OptimizedMarketBridgeOrder {
|
|
||||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
|
||||||
return {
|
return {
|
||||||
makerToken,
|
inputToken,
|
||||||
takerToken,
|
outputToken,
|
||||||
makerAmount,
|
inputAmount: fill.input,
|
||||||
takerAmount,
|
outputAmount: fill.output,
|
||||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
fillData: fill.data,
|
||||||
source: fill.source,
|
source: fill.source,
|
||||||
sourcePathId: fill.sourcePathId,
|
sourcePathId: fill.sourcePathId,
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
fills: [fill],
|
fills: [fill],
|
||||||
|
gasCost: fill.gasCost,
|
||||||
|
isFallback: fill.isFallback,
|
||||||
|
...((fill as any).metadata !== undefined ? { metadata: (fill as any).metadata } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData {
|
export function getMakerTakerTokens(side: MarketOperation, inputToken: Address, outputToken: Address): [Address, Address] {
|
||||||
switch (fill.source) {
|
const makerToken = side === MarketOperation.Sell ? outputToken : inputToken;
|
||||||
case ERC20BridgeSource.UniswapV3: {
|
const takerToken = side === MarketOperation.Sell ? inputToken : outputToken;
|
||||||
const fd = fill.fillData as UniswapV3FillData;
|
|
||||||
return {
|
|
||||||
router: fd.router,
|
|
||||||
tokenAddressPath: fd.tokenAddressPath,
|
|
||||||
uniswapPath: getBestUniswapV3PathForInputAmount(fd, fill.input),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return fill.fillData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBestUniswapV3PathForInputAmount(fillData: UniswapV3FillData, inputAmount: BigNumber): string {
|
|
||||||
if (fillData.pathAmounts.length === 0) {
|
|
||||||
throw new Error(`No Uniswap V3 paths`);
|
|
||||||
}
|
|
||||||
// Find the best path that can satisfy `inputAmount`.
|
|
||||||
// Assumes `fillData.pathAmounts` is sorted ascending.
|
|
||||||
for (const { inputAmount: pathInputAmount, uniswapPath } of fillData.pathAmounts) {
|
|
||||||
if (pathInputAmount.gte(inputAmount)) {
|
|
||||||
return uniswapPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fillData.pathAmounts[fillData.pathAmounts.length - 1].uniswapPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
|
||||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
|
||||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
|
||||||
return [makerToken, takerToken];
|
return [makerToken, takerToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
|
||||||
const curveEncoder = AbiEncoder.create([
|
|
||||||
{ name: 'curveAddress', type: 'address' },
|
|
||||||
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
|
|
||||||
{ name: 'fromTokenIdx', type: 'int128' },
|
|
||||||
{ name: 'toTokenIdx', type: 'int128' },
|
|
||||||
]);
|
|
||||||
const makerPsmEncoder = AbiEncoder.create([
|
|
||||||
{ name: 'psmAddress', type: 'address' },
|
|
||||||
{ name: 'gemTokenAddress', type: 'address' },
|
|
||||||
]);
|
|
||||||
const balancerV2Encoder = AbiEncoder.create([
|
|
||||||
{ name: 'vault', type: 'address' },
|
|
||||||
{ name: 'poolId', type: 'bytes32' },
|
|
||||||
]);
|
|
||||||
const routerAddressPathEncoder = AbiEncoder.create('(address,address[])');
|
|
||||||
const tokenAddressEncoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
|
|
||||||
|
|
||||||
export const BRIDGE_ENCODERS: {
|
|
||||||
[key in Exclude<
|
|
||||||
ERC20BridgeSource,
|
|
||||||
ERC20BridgeSource.Native | ERC20BridgeSource.MultiHop | ERC20BridgeSource.MultiBridge
|
|
||||||
>]: AbiEncoder.DataType;
|
|
||||||
} = {
|
|
||||||
[ERC20BridgeSource.LiquidityProvider]: AbiEncoder.create([
|
|
||||||
{ name: 'provider', type: 'address' },
|
|
||||||
{ name: 'data', type: 'bytes' },
|
|
||||||
]),
|
|
||||||
[ERC20BridgeSource.Kyber]: AbiEncoder.create([
|
|
||||||
{ name: 'kyberNetworkProxy', type: 'address' },
|
|
||||||
{ name: 'hint', type: 'bytes' },
|
|
||||||
]),
|
|
||||||
[ERC20BridgeSource.Dodo]: AbiEncoder.create([
|
|
||||||
{ name: 'helper', type: 'address' },
|
|
||||||
{ name: 'poolAddress', type: 'address' },
|
|
||||||
{ name: 'isSellBase', type: 'bool' },
|
|
||||||
]),
|
|
||||||
[ERC20BridgeSource.DodoV2]: AbiEncoder.create([
|
|
||||||
{ name: 'poolAddress', type: 'address' },
|
|
||||||
{ name: 'isSellBase', type: 'bool' },
|
|
||||||
]),
|
|
||||||
// Curve like
|
|
||||||
[ERC20BridgeSource.Curve]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.CurveV2]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Swerve]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.SnowSwap]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Nerve]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Synapse]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Belt]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Ellipsis]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Smoothy]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Saddle]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.XSigma]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.IronSwap]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.ACryptos]: curveEncoder,
|
|
||||||
// UniswapV2 like, (router, address[])
|
|
||||||
[ERC20BridgeSource.Bancor]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.UniswapV2]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.SushiSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.CryptoCom]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.Linkswap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.ShibaSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.Pangolin]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.TraderJoe]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.SpiritSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.SpookySwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.MorpheusSwap]: routerAddressPathEncoder,
|
|
||||||
// Celo
|
|
||||||
[ERC20BridgeSource.UbeSwap]: routerAddressPathEncoder,
|
|
||||||
// BSC
|
|
||||||
[ERC20BridgeSource.PancakeSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.PancakeSwapV2]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.BakerySwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.ApeSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.CafeSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.CheeseSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.JulSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
|
|
||||||
// Polygon
|
|
||||||
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.ComethSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.Polydex]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.JetSwap]: routerAddressPathEncoder,
|
|
||||||
// Generic pools
|
|
||||||
[ERC20BridgeSource.Shell]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Component]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Mooniswap]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Eth2Dai]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.MStable]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Balancer]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Cream]: poolEncoder,
|
|
||||||
[ERC20BridgeSource.Uniswap]: poolEncoder,
|
|
||||||
// Custom integrations
|
|
||||||
[ERC20BridgeSource.MakerPsm]: makerPsmEncoder,
|
|
||||||
[ERC20BridgeSource.BalancerV2]: balancerV2Encoder,
|
|
||||||
[ERC20BridgeSource.Beethovenx]: balancerV2Encoder,
|
|
||||||
[ERC20BridgeSource.UniswapV3]: AbiEncoder.create([
|
|
||||||
{ name: 'router', type: 'address' },
|
|
||||||
{ name: 'path', type: 'bytes' },
|
|
||||||
]),
|
|
||||||
[ERC20BridgeSource.KyberDmm]: AbiEncoder.create('(address,address[],address[])'),
|
|
||||||
[ERC20BridgeSource.Lido]: AbiEncoder.create('(address)'),
|
|
||||||
[ERC20BridgeSource.AaveV2]: AbiEncoder.create('(address,address)'),
|
|
||||||
[ERC20BridgeSource.Compound]: AbiEncoder.create('(address)'),
|
|
||||||
};
|
|
||||||
|
|
||||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
|
||||||
return [
|
|
||||||
// Maker asset amount.
|
|
||||||
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
|
|
||||||
// Taker asset amount.
|
|
||||||
side === MarketOperation.Sell ? fill.input : fill.output.integerValue(BigNumber.ROUND_UP),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createNativeOptimizedOrder(
|
export function createNativeOptimizedOrder(
|
||||||
fill: NativeCollapsedFill,
|
fill: CollapsedNativeOrderFill,
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
): OptimizedLimitOrder | OptimizedRfqOrder {
|
||||||
const fillData = fill.fillData;
|
throw new Error(`No implementado`);
|
||||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
// const fillData = fill.fillData;
|
||||||
const base = {
|
// const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||||
type: fill.type,
|
// const base = {
|
||||||
source: ERC20BridgeSource.Native,
|
// type: fill.type,
|
||||||
makerToken: fillData.order.makerToken,
|
// source: ERC20BridgeSource.Native,
|
||||||
takerToken: fillData.order.takerToken,
|
// makerToken: fillData.order.makerToken,
|
||||||
makerAmount,
|
// takerToken: fillData.order.takerToken,
|
||||||
takerAmount,
|
// makerAmount,
|
||||||
fills: [fill],
|
// takerAmount,
|
||||||
fillData,
|
// fills: [fill],
|
||||||
};
|
// fillData,
|
||||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
// };
|
||||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
// return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
// ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||||
|
// : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { Address, MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
||||||
import { ethToOutputAmount } from './fills';
|
import { ethToOutputAmount } from './fills';
|
||||||
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
|
import { createBridgeOrder, createNativeOptimizedOrder } from './orders';
|
||||||
import { getCompleteRate, getRate } from './rate_utils';
|
import { getCompleteRate, getRate } from './rate_utils';
|
||||||
import {
|
import {
|
||||||
|
CollapsedGenericBridgeFill,
|
||||||
CollapsedFill,
|
CollapsedFill,
|
||||||
|
CollapsedNativeOrderFill,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
ExchangeProxyOverhead,
|
ExchangeProxyOverhead,
|
||||||
Fill,
|
Fill,
|
||||||
NativeCollapsedFill,
|
OptimizedOrder,
|
||||||
OptimizedMarketOrder,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||||
@@ -38,10 +39,11 @@ export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
|||||||
|
|
||||||
export class Path {
|
export class Path {
|
||||||
public collapsedFills?: ReadonlyArray<CollapsedFill>;
|
public collapsedFills?: ReadonlyArray<CollapsedFill>;
|
||||||
public orders?: OptimizedMarketOrder[];
|
public orders?: OptimizedOrder[];
|
||||||
public sourceFlags: bigint = BigInt(0);
|
public sourceFlags: bigint = BigInt(0);
|
||||||
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||||
protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||||
|
private _fallbackFillsStartIndex: number = 0;
|
||||||
|
|
||||||
public static create(
|
public static create(
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
@@ -107,33 +109,25 @@ export class Path {
|
|||||||
// Add the fills to the end that aren't already included
|
// Add the fills to the end that aren't already included
|
||||||
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
|
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
|
||||||
];
|
];
|
||||||
|
this._fallbackFillsStartIndex = nativeFills.length + otherFills.length;
|
||||||
// Recompute the source flags
|
// Recompute the source flags
|
||||||
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
|
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
|
public collapse(opts: { side: MarketOperation, inputToken: Address; outputToken: Address; }): CollapsedPath {
|
||||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
|
||||||
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
|
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
|
||||||
this.orders = [];
|
this.orders = [];
|
||||||
for (let i = 0; i < collapsedFills.length; ) {
|
for (let i = 0; i < collapsedFills.length; ++i) {
|
||||||
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
|
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
|
||||||
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as NativeCollapsedFill, opts.side));
|
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as CollapsedNativeOrderFill, opts.side));
|
||||||
++i;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If there are contiguous bridge orders, we can batch them together.
|
this.orders.push(createBridgeOrder(
|
||||||
// TODO jacob pretty sure this is from DFB and we can remove
|
collapsedFills[i] as CollapsedGenericBridgeFill,
|
||||||
const contiguousBridgeFills = [collapsedFills[i]];
|
opts.inputToken,
|
||||||
for (let j = i + 1; j < collapsedFills.length; ++j) {
|
opts.outputToken,
|
||||||
if (collapsedFills[j].source === ERC20BridgeSource.Native) {
|
));
|
||||||
break;
|
|
||||||
}
|
|
||||||
contiguousBridgeFills.push(collapsedFills[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts.side));
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
return this as CollapsedPath;
|
return this as CollapsedPath;
|
||||||
}
|
}
|
||||||
@@ -159,7 +153,7 @@ export class Path {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public adjustedCompleteRate(): BigNumber {
|
public adjustedCompleteMakerToTakerRate(): BigNumber {
|
||||||
const { input, output } = this.adjustedSize();
|
const { input, output } = this.adjustedSize();
|
||||||
return getCompleteRate(this.side, input, output, this.targetInput);
|
return getCompleteRate(this.side, input, output, this.targetInput);
|
||||||
}
|
}
|
||||||
@@ -199,7 +193,7 @@ export class Path {
|
|||||||
if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
|
if (input.isLessThan(targetInput) || otherInput.isLessThan(targetInput)) {
|
||||||
return input.isGreaterThan(otherInput);
|
return input.isGreaterThan(otherInput);
|
||||||
} else {
|
} else {
|
||||||
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
return this.adjustedCompleteMakerToTakerRate().isGreaterThan(other.adjustedCompleteMakerToTakerRate());
|
||||||
}
|
}
|
||||||
// if (otherInput.isLessThan(targetInput)) {
|
// if (otherInput.isLessThan(targetInput)) {
|
||||||
// return input.isGreaterThan(otherInput);
|
// return input.isGreaterThan(otherInput);
|
||||||
@@ -214,7 +208,7 @@ export class Path {
|
|||||||
return input.gte(this.targetInput);
|
return input.gte(this.targetInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isValid(skipDuplicateCheck: boolean = false): boolean {
|
public isValid(quick: boolean = false): boolean {
|
||||||
for (let i = 0; i < this.fills.length; ++i) {
|
for (let i = 0; i < this.fills.length; ++i) {
|
||||||
// Fill must immediately follow its parent.
|
// Fill must immediately follow its parent.
|
||||||
if (this.fills[i].parent) {
|
if (this.fills[i].parent) {
|
||||||
@@ -222,8 +216,9 @@ export class Path {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!skipDuplicateCheck) {
|
if (!quick) {
|
||||||
// Fill must not be duplicated.
|
// Fill must not be duplicated.
|
||||||
|
// Fills must all have the same input and output tokens.
|
||||||
for (let j = 0; j < i; ++j) {
|
for (let j = 0; j < i; ++j) {
|
||||||
if (this.fills[i] === this.fills[j]) {
|
if (this.fills[i] === this.fills[j]) {
|
||||||
return false;
|
return false;
|
||||||
@@ -249,7 +244,7 @@ export class Path {
|
|||||||
|
|
||||||
private _collapseFills(): ReadonlyArray<CollapsedFill> {
|
private _collapseFills(): ReadonlyArray<CollapsedFill> {
|
||||||
this.collapsedFills = [];
|
this.collapsedFills = [];
|
||||||
for (const fill of this.fills) {
|
for (const [i, fill] of this.fills.entries()) {
|
||||||
const source = fill.source;
|
const source = fill.source;
|
||||||
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
|
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
|
||||||
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
|
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
|
||||||
@@ -257,8 +252,9 @@ export class Path {
|
|||||||
if (prevFill.sourcePathId === fill.sourcePathId) {
|
if (prevFill.sourcePathId === fill.sourcePathId) {
|
||||||
prevFill.input = prevFill.input.plus(fill.input);
|
prevFill.input = prevFill.input.plus(fill.input);
|
||||||
prevFill.output = prevFill.output.plus(fill.output);
|
prevFill.output = prevFill.output.plus(fill.output);
|
||||||
prevFill.fillData = fill.fillData;
|
prevFill.data = fill.data;
|
||||||
prevFill.subFills.push(fill);
|
prevFill.subFills.push(fill);
|
||||||
|
prevFill.gasCost;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,10 +262,12 @@ export class Path {
|
|||||||
sourcePathId: fill.sourcePathId,
|
sourcePathId: fill.sourcePathId,
|
||||||
source: fill.source,
|
source: fill.source,
|
||||||
type: fill.type,
|
type: fill.type,
|
||||||
fillData: fill.fillData,
|
data: fill.data,
|
||||||
input: fill.input,
|
input: fill.input,
|
||||||
output: fill.output,
|
output: fill.output,
|
||||||
subFills: [fill],
|
subFills: [fill],
|
||||||
|
gasCost: fill.gasCost,
|
||||||
|
isFallback: this._fallbackFillsStartIndex > 0 ? i >= this._fallbackFillsStartIndex : false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.collapsedFills;
|
return this.collapsedFills;
|
||||||
@@ -296,5 +294,5 @@ export class Path {
|
|||||||
|
|
||||||
export interface CollapsedPath extends Path {
|
export interface CollapsedPath extends Path {
|
||||||
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
||||||
readonly orders: OptimizedMarketOrder[];
|
readonly orders: OptimizedOrder[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import * as _ from 'lodash';
|
|||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
|
||||||
import { DEFAULT_WARNING_LOGGER } from '../../constants';
|
import { DEFAULT_WARNING_LOGGER } from '../../constants';
|
||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
import { NativeOrderWithFillableAmounts } from '../native_orders';
|
||||||
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
||||||
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
||||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
||||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
|
import { DexSample, ERC20BridgeSource, Fill, SamplerMetrics } from './types';
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
||||||
|
|
||||||
@@ -41,11 +42,11 @@ function calculateOuputFee(
|
|||||||
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
|
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
|
||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
inputAmountPerEth: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
gasPrice: BigNumber,
|
||||||
): BigNumber {
|
): BigNumber {
|
||||||
if (isDexSample(sampleOrNativeOrder)) {
|
if (isDexSample(sampleOrNativeOrder)) {
|
||||||
const { input, output, source, fillData } = sampleOrNativeOrder;
|
const { input, output } = sampleOrNativeOrder;
|
||||||
const fee = fees[source]?.(fillData) || 0;
|
const fee = gasPrice.times(sampleOrNativeOrder.gasCost);
|
||||||
const outputFee = ethToOutputAmount({
|
const outputFee = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -56,13 +57,12 @@ function calculateOuputFee(
|
|||||||
return outputFee;
|
return outputFee;
|
||||||
} else {
|
} else {
|
||||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
|
|
||||||
const outputFee = ethToOutputAmount({
|
const outputFee = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
inputAmountPerEth,
|
inputAmountPerEth,
|
||||||
outputAmountPerEth,
|
outputAmountPerEth,
|
||||||
ethAmount: fee,
|
ethAmount: gasPrice.times((sampleOrNativeOrder as NativeOrderWithFillableAmounts).gasCost),
|
||||||
});
|
});
|
||||||
return outputFee;
|
return outputFee;
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,8 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||||
input: BigNumber,
|
input: BigNumber,
|
||||||
opts: PathPenaltyOpts,
|
opts: PathPenaltyOpts,
|
||||||
fees: FeeSchedule,
|
|
||||||
neonRouterNumSamples: number,
|
neonRouterNumSamples: number,
|
||||||
|
gasPrice: BigNumber,
|
||||||
): Path | undefined {
|
): Path | undefined {
|
||||||
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
||||||
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
||||||
@@ -85,7 +85,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createFill = (sample: DexSample): Fill | undefined => {
|
const createFill = (sample: DexSample): Fill | undefined => {
|
||||||
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice);
|
||||||
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
|
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
|
||||||
if (fills.length === 0) {
|
if (fills.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -124,7 +124,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
memo.inputs.push(sample.input.integerValue().toNumber());
|
memo.inputs.push(sample.input.integerValue().toNumber());
|
||||||
memo.outputs.push(sample.output.integerValue().toNumber());
|
memo.outputs.push(sample.output.integerValue().toNumber());
|
||||||
memo.outputFees.push(
|
memo.outputFees.push(
|
||||||
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
|
||||||
.integerValue()
|
.integerValue()
|
||||||
.toNumber(),
|
.toNumber(),
|
||||||
);
|
);
|
||||||
@@ -155,7 +155,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
if (normalizedOrderInput.isLessThanOrEqualTo(0) || normalizedOrderOutput.isLessThanOrEqualTo(0)) {
|
if (normalizedOrderInput.isLessThanOrEqualTo(0) || normalizedOrderOutput.isLessThanOrEqualTo(0)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
|
||||||
.integerValue()
|
.integerValue()
|
||||||
.toNumber();
|
.toNumber();
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
rustInputAdjusted,
|
rustInputAdjusted,
|
||||||
opts.outputAmountPerEth,
|
opts.outputAmountPerEth,
|
||||||
opts.inputAmountPerEth,
|
opts.inputAmountPerEth,
|
||||||
fees,
|
gasPrice,
|
||||||
false,
|
false,
|
||||||
)[0] as Fill | undefined;
|
)[0] as Fill | undefined;
|
||||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be skipped
|
// Note: If the order has an adjusted rate of less than or equal to 0 it will be skipped
|
||||||
@@ -273,7 +273,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
if (!fill) {
|
if (!fill) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
|
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample>;
|
||||||
// Descend to approach a closer fill for fillData which may not be consistent
|
// Descend to approach a closer fill for fillData which may not be consistent
|
||||||
// throughout the path (UniswapV3) and for a closer guesstimate at
|
// throughout the path (UniswapV3) and for a closer guesstimate at
|
||||||
// gas used
|
// gas used
|
||||||
@@ -343,7 +343,7 @@ export function findOptimalRustPathFromSamples(
|
|||||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||||
input: BigNumber,
|
input: BigNumber,
|
||||||
opts: PathPenaltyOpts,
|
opts: PathPenaltyOpts,
|
||||||
fees: FeeSchedule,
|
gasPrice: BigNumber,
|
||||||
chainId: ChainId,
|
chainId: ChainId,
|
||||||
neonRouterNumSamples: number,
|
neonRouterNumSamples: number,
|
||||||
samplerMetrics?: SamplerMetrics,
|
samplerMetrics?: SamplerMetrics,
|
||||||
@@ -356,8 +356,8 @@ export function findOptimalRustPathFromSamples(
|
|||||||
nativeOrders,
|
nativeOrders,
|
||||||
input,
|
input,
|
||||||
opts,
|
opts,
|
||||||
fees,
|
|
||||||
neonRouterNumSamples,
|
neonRouterNumSamples,
|
||||||
|
gasPrice,
|
||||||
);
|
);
|
||||||
// tslint:disable-next-line: no-unused-expression
|
// tslint:disable-next-line: no-unused-expression
|
||||||
samplerMetrics &&
|
samplerMetrics &&
|
||||||
@@ -386,8 +386,8 @@ export function findOptimalRustPathFromSamples(
|
|||||||
[],
|
[],
|
||||||
input,
|
input,
|
||||||
opts,
|
opts,
|
||||||
fees,
|
|
||||||
neonRouterNumSamples,
|
neonRouterNumSamples,
|
||||||
|
gasPrice,
|
||||||
);
|
);
|
||||||
// tslint:disable-next-line: no-unused-expression
|
// tslint:disable-next-line: no-unused-expression
|
||||||
samplerMetrics &&
|
samplerMetrics &&
|
||||||
@@ -428,7 +428,7 @@ export async function findOptimalPathJSAsync(
|
|||||||
const beforeTimeMs = performance.now();
|
const beforeTimeMs = performance.now();
|
||||||
// Sort fill arrays by descending adjusted completed rate.
|
// Sort fill arrays by descending adjusted completed rate.
|
||||||
// Remove any paths which cannot impact the optimal path
|
// Remove any paths which cannot impact the optimal path
|
||||||
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
|
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts));
|
||||||
if (sortedPaths.length === 0) {
|
if (sortedPaths.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -459,8 +459,8 @@ export function fillsToSortedPaths(
|
|||||||
): Path[] {
|
): Path[] {
|
||||||
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
|
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
|
||||||
const sortedPaths = paths.sort((a, b) => {
|
const sortedPaths = paths.sort((a, b) => {
|
||||||
const aRate = a.adjustedCompleteRate();
|
const aRate = a.adjustedCompleteMakerToTakerRate();
|
||||||
const bRate = b.adjustedCompleteRate();
|
const bRate = b.adjustedCompleteMakerToTakerRate();
|
||||||
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
|
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
|
||||||
// resulting in a NaN div by 0 (output)
|
// resulting in a NaN div by 0 (output)
|
||||||
if (bRate.isNaN()) {
|
if (bRate.isNaN()) {
|
||||||
@@ -475,7 +475,7 @@ export function fillsToSortedPaths(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove paths which have no impact on the optimal path
|
// Remove paths which have no impact on the optimal path
|
||||||
export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[] {
|
export function reducePaths(sortedPaths: Path[]): Path[] {
|
||||||
// Any path which has a min rate that is less than the best adjusted completed rate has no chance of improving
|
// Any path which has a min rate that is less than the best adjusted completed rate has no chance of improving
|
||||||
// the overall route.
|
// the overall route.
|
||||||
const bestNonNativeCompletePath = sortedPaths.filter(
|
const bestNonNativeCompletePath = sortedPaths.filter(
|
||||||
@@ -488,7 +488,7 @@ export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[]
|
|||||||
if (!bestNonNativeCompletePath) {
|
if (!bestNonNativeCompletePath) {
|
||||||
return sortedPaths;
|
return sortedPaths;
|
||||||
}
|
}
|
||||||
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteRate();
|
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteMakerToTakerRate();
|
||||||
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
|
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
|
||||||
return sortedPaths;
|
return sortedPaths;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
/**
|
|
||||||
* This has been copied from https://github.com/balancer-labs/balancer-sor/blob/john/rc2/src/helpers.ts.
|
|
||||||
* Still awaiting V2 support for @balancer-labs/sor, once full V2 support is shipped we can upgrade sor and delete this file
|
|
||||||
*/
|
|
||||||
export const parsePoolData = (
|
|
||||||
directPools: SubGraphPoolDictionary,
|
|
||||||
tokenIn: string,
|
|
||||||
tokenOut: string,
|
|
||||||
mostLiquidPoolsFirstHop: SubGraphPool[] = [],
|
|
||||||
mostLiquidPoolsSecondHop: SubGraphPool[] = [],
|
|
||||||
hopTokens: string[] = [],
|
|
||||||
): [SubGraphPoolDictionary, Path[]] => {
|
|
||||||
const pathDataList: Path[] = [];
|
|
||||||
const pools: SubGraphPoolDictionary = {};
|
|
||||||
|
|
||||||
// First add direct pair paths
|
|
||||||
// tslint:disable-next-line:forin
|
|
||||||
for (const idKey in directPools) {
|
|
||||||
const p: SubGraphPool = directPools[idKey];
|
|
||||||
// Add pool to the set with all pools (only adds if it's still not present in dict)
|
|
||||||
pools[idKey] = p;
|
|
||||||
|
|
||||||
const swap: Swap = {
|
|
||||||
pool: p.id,
|
|
||||||
tokenIn,
|
|
||||||
tokenOut,
|
|
||||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
|
||||||
tokenOutDecimals: 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
const path: Path = {
|
|
||||||
id: p.id,
|
|
||||||
swaps: [swap],
|
|
||||||
};
|
|
||||||
pathDataList.push(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now add multi-hop paths.
|
|
||||||
// mostLiquidPoolsFirstHop and mostLiquidPoolsSecondHop always has the same
|
|
||||||
// lengh of hopTokens
|
|
||||||
for (let i = 0; i < hopTokens.length; i++) {
|
|
||||||
// Add pools to the set with all pools (only adds if it's still not present in dict)
|
|
||||||
pools[mostLiquidPoolsFirstHop[i].id] = mostLiquidPoolsFirstHop[i];
|
|
||||||
pools[mostLiquidPoolsSecondHop[i].id] = mostLiquidPoolsSecondHop[i];
|
|
||||||
|
|
||||||
const swap1: Swap = {
|
|
||||||
pool: mostLiquidPoolsFirstHop[i].id,
|
|
||||||
tokenIn,
|
|
||||||
tokenOut: hopTokens[i],
|
|
||||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
|
||||||
tokenOutDecimals: 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
const swap2: Swap = {
|
|
||||||
pool: mostLiquidPoolsSecondHop[i].id,
|
|
||||||
tokenIn: hopTokens[i],
|
|
||||||
tokenOut,
|
|
||||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
|
||||||
tokenOutDecimals: 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
const path: Path = {
|
|
||||||
id: mostLiquidPoolsFirstHop[i].id + mostLiquidPoolsSecondHop[i].id, // Path id is the concatenation of the ids of poolFirstHop and poolSecondHop
|
|
||||||
swaps: [swap1, swap2],
|
|
||||||
};
|
|
||||||
pathDataList.push(path);
|
|
||||||
}
|
|
||||||
return [pools, pathDataList];
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SubGraphPool {
|
|
||||||
id: string;
|
|
||||||
swapFee: string;
|
|
||||||
totalWeight: string;
|
|
||||||
totalShares: string;
|
|
||||||
tokens: SubGraphToken[];
|
|
||||||
tokensList: string[];
|
|
||||||
poolType?: string;
|
|
||||||
|
|
||||||
// Only for stable pools
|
|
||||||
amp: string;
|
|
||||||
|
|
||||||
// Only for element pools
|
|
||||||
lpShares?: BigNumber;
|
|
||||||
time?: BigNumber;
|
|
||||||
principalToken?: string;
|
|
||||||
baseToken?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubGraphPoolDictionary {
|
|
||||||
[poolId: string]: SubGraphPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubGraphToken {
|
|
||||||
address: string;
|
|
||||||
balance: string;
|
|
||||||
decimals: string | number;
|
|
||||||
// Stable & Element field
|
|
||||||
weight?: string;
|
|
||||||
}
|
|
||||||
interface Path {
|
|
||||||
id: string; // pool address if direct path, contactenation of pool addresses if multihop
|
|
||||||
swaps: Swap[];
|
|
||||||
poolPairData?: PoolPairData[];
|
|
||||||
limitAmount?: BigNumber;
|
|
||||||
filterEffectivePrice?: BigNumber; // TODO: This is just used for filtering, maybe there is a better way to filter?
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Swap {
|
|
||||||
pool: string;
|
|
||||||
tokenIn: string;
|
|
||||||
tokenOut: string;
|
|
||||||
swapAmount?: string;
|
|
||||||
limitReturnAmount?: string;
|
|
||||||
maxPrice?: string;
|
|
||||||
tokenInDecimals: number;
|
|
||||||
tokenOutDecimals: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PoolPairData {
|
|
||||||
id: string;
|
|
||||||
poolType?: string; // Todo: make this a mandatory field?
|
|
||||||
pairType?: string; // Todo: make this a mandatory field?
|
|
||||||
tokenIn: string;
|
|
||||||
tokenOut: string;
|
|
||||||
balanceIn?: BigNumber;
|
|
||||||
balanceOut?: BigNumber;
|
|
||||||
decimalsIn: number;
|
|
||||||
decimalsOut: number;
|
|
||||||
swapFee: BigNumber;
|
|
||||||
|
|
||||||
// For weighted & element pools
|
|
||||||
weightIn?: BigNumber;
|
|
||||||
weightOut?: BigNumber;
|
|
||||||
|
|
||||||
// Only for stable pools
|
|
||||||
allBalances: BigNumber[];
|
|
||||||
invariant?: BigNumber;
|
|
||||||
amp?: BigNumber;
|
|
||||||
tokenIndexIn?: number;
|
|
||||||
tokenIndexOut?: number;
|
|
||||||
|
|
||||||
// Only for element pools
|
|
||||||
lpShares?: BigNumber;
|
|
||||||
time?: BigNumber;
|
|
||||||
principalToken?: string;
|
|
||||||
baseToken?: string;
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
|
||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
|
||||||
import { gql, request } from 'graphql-request';
|
|
||||||
|
|
||||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
|
||||||
|
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
|
||||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
||||||
// tslint:enable:custom-no-magic-numbers
|
|
||||||
|
|
||||||
interface BalancerPoolResponse {
|
|
||||||
id: string;
|
|
||||||
swapFee: string;
|
|
||||||
tokens: Array<{ address: string; decimals: number; balance: string }>;
|
|
||||||
tokensList: string[];
|
|
||||||
totalWeight: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BalancerPoolsCache extends PoolsCache {
|
|
||||||
constructor(
|
|
||||||
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
|
||||||
cache: { [key: string]: CacheValue } = {},
|
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
|
||||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
|
||||||
) {
|
|
||||||
super(cache);
|
|
||||||
void this._loadTopPoolsAsync();
|
|
||||||
// Reload the top pools every 12 hours
|
|
||||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
|
||||||
try {
|
|
||||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
|
||||||
// Sort by maker token balance (descending)
|
|
||||||
const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
|
|
||||||
b.balanceOut.minus(a.balanceOut).toNumber(),
|
|
||||||
);
|
|
||||||
return pools.length > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
|
||||||
const fromToPools: {
|
|
||||||
[from: string]: { [to: string]: Pool[] };
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
const pools = await this._fetchTopPoolsAsync();
|
|
||||||
for (const pool of pools) {
|
|
||||||
const { tokensList } = pool;
|
|
||||||
for (const from of tokensList) {
|
|
||||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
|
||||||
fromToPools[from] = fromToPools[from] || {};
|
|
||||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
|
||||||
const poolData = parsePoolData([pool], from, to);
|
|
||||||
fromToPools[from][to].push(poolData[0]);
|
|
||||||
// Cache this as we progress through
|
|
||||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
|
||||||
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
|
|
||||||
} catch {
|
|
||||||
// soldier on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
|
||||||
const query = gql`
|
|
||||||
query fetchTopPools($topPoolsFetched: Int!) {
|
|
||||||
pools(
|
|
||||||
first: $topPoolsFetched
|
|
||||||
where: { publicSwap: true, liquidity_gt: 0 }
|
|
||||||
orderBy: swapsCount
|
|
||||||
orderDirection: desc
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
publicSwap
|
|
||||||
swapFee
|
|
||||||
totalWeight
|
|
||||||
tokensList
|
|
||||||
tokens {
|
|
||||||
id
|
|
||||||
address
|
|
||||||
balance
|
|
||||||
decimals
|
|
||||||
symbol
|
|
||||||
denormWeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
try {
|
|
||||||
const { pools } = await request(this._subgraphUrl, query, { topPoolsFetched: this._topPoolsFetched });
|
|
||||||
return pools;
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import { ChainId } from '@0x/contract-addresses';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
// import { parsePoolData } from '@balancer-labs'; // TODO - upgrade to v2
|
|
||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
|
||||||
import { gql, request } from 'graphql-request';
|
|
||||||
|
|
||||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
|
||||||
import { LogFunction } from '../../../types';
|
|
||||||
import {
|
|
||||||
BALANCER_MAX_POOLS_FETCHED,
|
|
||||||
BALANCER_TOP_POOLS_FETCHED,
|
|
||||||
BALANCER_V2_SUBGRAPH_URL_BY_CHAIN,
|
|
||||||
} from '../constants';
|
|
||||||
|
|
||||||
import { parsePoolData } from './balancer_sor_v2';
|
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
|
||||||
|
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
|
||||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
interface BalancerPoolResponse {
|
|
||||||
id: string;
|
|
||||||
swapFee: string;
|
|
||||||
tokens: Array<{ address: string; decimals: number; balance: string; weight: string; symbol: string }>;
|
|
||||||
tokensList: string[];
|
|
||||||
totalWeight: string;
|
|
||||||
totalShares: string;
|
|
||||||
amp: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BalancerV2PoolsCache extends PoolsCache {
|
|
||||||
private static _parseSubgraphPoolData(pool: any, takerToken: string, makerToken: string): Pool {
|
|
||||||
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
|
|
||||||
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
|
|
||||||
const swap = pool.swaps && pool.swaps[0];
|
|
||||||
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
|
|
||||||
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
|
|
||||||
const spotPrice =
|
|
||||||
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: pool.id,
|
|
||||||
balanceIn: new BigNumber(tToken.balance),
|
|
||||||
balanceOut: new BigNumber(mToken.balance),
|
|
||||||
weightIn: new BigNumber(tToken.weight),
|
|
||||||
weightOut: new BigNumber(mToken.weight),
|
|
||||||
swapFee: new BigNumber(pool.swapFee),
|
|
||||||
spotPrice,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
chainId: ChainId,
|
|
||||||
private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL_BY_CHAIN[chainId],
|
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
|
||||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
|
||||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
|
||||||
cache: { [key: string]: CacheValue } = {},
|
|
||||||
) {
|
|
||||||
super(cache);
|
|
||||||
void this._loadTopPoolsAsync();
|
|
||||||
// Reload the top pools every 12 hours
|
|
||||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
|
||||||
// try {
|
|
||||||
// const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
|
||||||
// // Sort by maker token balance (descending)
|
|
||||||
// const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
|
|
||||||
// b.balanceOut.minus(a.balanceOut).toNumber(),
|
|
||||||
// );
|
|
||||||
// return pools.length > this.maxPoolsFetched ? pools.slice(0, this.maxPoolsFetched) : pools;
|
|
||||||
// } catch (err) {
|
|
||||||
// return [];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
|
||||||
const query = gql`
|
|
||||||
query fetchTopPools($topPoolsFetched: Int!) {
|
|
||||||
pools(
|
|
||||||
first: $topPoolsFetched
|
|
||||||
where: { totalLiquidity_gt: 0 }
|
|
||||||
orderBy: swapsCount
|
|
||||||
orderDirection: desc
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
swapFee
|
|
||||||
totalWeight
|
|
||||||
tokensList
|
|
||||||
amp
|
|
||||||
totalShares
|
|
||||||
tokens {
|
|
||||||
id
|
|
||||||
address
|
|
||||||
balance
|
|
||||||
decimals
|
|
||||||
symbol
|
|
||||||
weight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const { pools } = await request<{ pools: BalancerPoolResponse[] }>(this.subgraphUrl, query, {
|
|
||||||
topPoolsFetched: this._topPoolsFetched,
|
|
||||||
});
|
|
||||||
|
|
||||||
return pools;
|
|
||||||
}
|
|
||||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
|
||||||
const fromToPools: {
|
|
||||||
[from: string]: { [to: string]: Pool[] };
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
const pools = await this._fetchTopPoolsAsync();
|
|
||||||
for (const pool of pools) {
|
|
||||||
const { tokensList } = pool;
|
|
||||||
for (const from of tokensList) {
|
|
||||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
|
||||||
fromToPools[from] = fromToPools[from] || {};
|
|
||||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
|
||||||
const [poolData] = parsePoolData({ [pool.id]: pool as any }, from, to);
|
|
||||||
fromToPools[from][to].push(
|
|
||||||
BalancerV2PoolsCache._parseSubgraphPoolData(poolData[pool.id], from, to),
|
|
||||||
);
|
|
||||||
// Cache this as we progress through
|
|
||||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
|
||||||
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
|
|
||||||
} catch (err) {
|
|
||||||
this._warningLogger(err, `Failed to load Balancer V2 top pools`);
|
|
||||||
// soldier on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
|
||||||
const query = gql`
|
|
||||||
query getPools {
|
|
||||||
pools(
|
|
||||||
first: ${this.maxPoolsFetched},
|
|
||||||
where: {
|
|
||||||
tokensList_contains: ["${takerToken}", "${makerToken}"]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
tokens {
|
|
||||||
address
|
|
||||||
balance
|
|
||||||
weight
|
|
||||||
}
|
|
||||||
swapFee
|
|
||||||
swaps(
|
|
||||||
orderBy: timestamp, orderDirection: desc, first: 1,
|
|
||||||
where:{
|
|
||||||
tokenIn: "${takerToken}",
|
|
||||||
tokenOut: "${makerToken}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
tokenAmountIn
|
|
||||||
tokenAmountOut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
try {
|
|
||||||
const { pools } = await request(this.subgraphUrl, query);
|
|
||||||
return pools.map((pool: any) => BalancerV2PoolsCache._parseSubgraphPoolData(pool, takerToken, makerToken));
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
|
||||||
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
|
|
||||||
|
|
||||||
import { BALANCER_MAX_POOLS_FETCHED } from '../constants';
|
|
||||||
|
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
|
||||||
|
|
||||||
export class CreamPoolsCache extends PoolsCache {
|
|
||||||
constructor(
|
|
||||||
_cache: { [key: string]: CacheValue } = {},
|
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
|
||||||
) {
|
|
||||||
super(_cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
|
||||||
try {
|
|
||||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
|
||||||
// Sort by maker token balance (descending)
|
|
||||||
const pools = parsePoolData(poolData, takerToken, makerToken).sort((a, b) =>
|
|
||||||
b.balanceOut.minus(a.balanceOut).toNumber(),
|
|
||||||
);
|
|
||||||
return pools.slice(0, this.maxPoolsFetched);
|
|
||||||
} catch (err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export { BalancerPoolsCache } from './balancer_utils';
|
|
||||||
export { BalancerV2PoolsCache } from './balancer_v2_utils';
|
|
||||||
export { CreamPoolsCache } from './cream_utils';
|
|
||||||
export { PoolsCache } from './pools_cache';
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
|
||||||
|
|
||||||
import { ONE_HOUR_IN_SECONDS, ONE_SECOND_MS } from '../constants';
|
|
||||||
export { Pool };
|
|
||||||
export interface CacheValue {
|
|
||||||
expiresAt: number;
|
|
||||||
pools: Pool[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
|
||||||
// Cache results for 30mins
|
|
||||||
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
|
||||||
const DEFAULT_TIMEOUT_MS = 1000;
|
|
||||||
// tslint:enable:custom-no-magic-numbers
|
|
||||||
|
|
||||||
export abstract class PoolsCache {
|
|
||||||
protected static _isExpired(value: CacheValue): boolean {
|
|
||||||
return Date.now() >= value.expiresAt;
|
|
||||||
}
|
|
||||||
constructor(
|
|
||||||
protected readonly _cache: { [key: string]: CacheValue },
|
|
||||||
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public async getFreshPoolsForPairAsync(
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
|
||||||
): Promise<Pool[]> {
|
|
||||||
const timeout = new Promise<Pool[]>(resolve => setTimeout(resolve, timeoutMs, []));
|
|
||||||
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCachedPoolAddressesForPair(
|
|
||||||
takerToken: string,
|
|
||||||
makerToken: string,
|
|
||||||
ignoreExpired: boolean = true,
|
|
||||||
): string[] | undefined {
|
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
|
||||||
const value = this._cache[key];
|
|
||||||
if (ignoreExpired) {
|
|
||||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
|
||||||
}
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (PoolsCache._isExpired(value)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (value || []).pools.map(pool => pool.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
|
||||||
const cached = this.getCachedPoolAddressesForPair(takerToken, makerToken, false);
|
|
||||||
return cached !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
|
||||||
const value = this._cache[key];
|
|
||||||
if (value === undefined || value.expiresAt >= Date.now()) {
|
|
||||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
|
||||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
|
||||||
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
|
||||||
}
|
|
||||||
return this._cache[key].pools;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
|
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
|
||||||
this._cache[key] = {
|
|
||||||
pools,
|
|
||||||
expiresAt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
|
||||||
}
|
|
||||||
@@ -2,38 +2,10 @@ import { BigNumber } from '@0x/utils';
|
|||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
import { ZERO_AMOUNT } from './constants';
|
||||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
|
|
||||||
|
|
||||||
// tslint:disable:no-bitwise
|
// tslint:disable:no-bitwise
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fee-adjusted rate of a two-hop quote. Returns zero if the
|
|
||||||
* quote falls short of the target input.
|
|
||||||
*/
|
|
||||||
export function getTwoHopAdjustedRate(
|
|
||||||
side: MarketOperation,
|
|
||||||
twoHopQuote: DexSample<MultiHopFillData>,
|
|
||||||
targetInput: BigNumber,
|
|
||||||
outputAmountPerEth: BigNumber,
|
|
||||||
fees: FeeSchedule = {},
|
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
|
||||||
): BigNumber {
|
|
||||||
const { output, input, fillData } = twoHopQuote;
|
|
||||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
|
||||||
return ZERO_AMOUNT;
|
|
||||||
}
|
|
||||||
const penalty = outputAmountPerEth.times(
|
|
||||||
exchangeProxyOverhead(
|
|
||||||
SOURCE_FLAGS.MultiHop |
|
|
||||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
|
||||||
SOURCE_FLAGS[fillData.secondHopSource.source],
|
|
||||||
).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
|
||||||
);
|
|
||||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
|
||||||
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the "complete" rate given the input/output of a path.
|
* Computes the "complete" rate given the input/output of a path.
|
||||||
* This value penalizes the path if it falls short of the target input.
|
* This value penalizes the path if it falls short of the target input.
|
||||||
|
|||||||
@@ -1,199 +1,121 @@
|
|||||||
import { ChainId } from '@0x/contract-addresses';
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { BigNumber, NULL_BYTES } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { SamplerOverrides } from '../../types';
|
import { Address } from '../../types';
|
||||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
|
||||||
|
|
||||||
import { BancorService } from './bancor_service';
|
import { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types';
|
||||||
import { PoolsCache } from './pools_cache';
|
import { SamplerServiceRpcClient } from './sampler_service_rpc_client';
|
||||||
import { SamplerOperations } from './sampler_operations';
|
|
||||||
import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
|
||||||
|
|
||||||
/**
|
const DEFAULT_LIQUIDITY_SAMPLES = 16;
|
||||||
* Generate sample amounts up to `maxFillAmount`.
|
|
||||||
*/
|
interface TokenInfo {
|
||||||
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
|
decimals: number;
|
||||||
const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i));
|
address: Address;
|
||||||
const distributionSum = BigNumber.sum(...distribution);
|
gasCost: number;
|
||||||
const stepSizes = distribution.map(d => d.div(distributionSum));
|
symbol: string;
|
||||||
const amounts = stepSizes.map((_s, i) => {
|
|
||||||
if (i === numSamples - 1) {
|
|
||||||
return maxFillAmount;
|
|
||||||
}
|
|
||||||
return maxFillAmount
|
|
||||||
.times(BigNumber.sum(...[0, ...stepSizes.slice(0, i + 1)]))
|
|
||||||
.integerValue(BigNumber.ROUND_UP);
|
|
||||||
});
|
|
||||||
return amounts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchedOperationResult<T> = T extends BatchedOperation<infer TResult> ? TResult : never;
|
export interface Sampler {
|
||||||
|
chainId: ChainId;
|
||||||
|
getTokenInfosAsync(tokens: Address[]): Promise<TokenInfo[]>;
|
||||||
|
getPricesAsync(paths: Address[][], sources: ERC20BridgeSource[]): Promise<BigNumber[]>;
|
||||||
|
getSellLiquidityAsync(path: Address[], takerAmount: BigNumber, sources: ERC20BridgeSource[], numSamples?: number): Promise<DexSample[][]>;
|
||||||
|
getBuyLiquidityAsync(path: Address[], makerAmount: BigNumber, sources: ERC20BridgeSource[], numSamples?: number): Promise<DexSample[][]>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
export class SamplerClient implements Sampler {
|
||||||
* Encapsulates interactions with the `ERC20BridgeSampler` contract.
|
static createFromChainIdAndEndpoint(chainId: ChainId, endpoint: string): SamplerClient {
|
||||||
*/
|
return new SamplerClient(chainId, new SamplerServiceRpcClient(endpoint));
|
||||||
export class DexOrderSampler extends SamplerOperations {
|
|
||||||
constructor(
|
|
||||||
public readonly chainId: ChainId,
|
|
||||||
_samplerContract: ERC20BridgeSamplerContract,
|
|
||||||
private readonly _samplerOverrides?: SamplerOverrides,
|
|
||||||
poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache },
|
|
||||||
tokenAdjacencyGraph?: TokenAdjacencyGraph,
|
|
||||||
liquidityProviderRegistry?: LiquidityProviderRegistry,
|
|
||||||
bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
|
|
||||||
) {
|
|
||||||
super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
static async createFromEndpointAsync(endpoint: string): Promise<SamplerClient> {
|
||||||
|
const service = new SamplerServiceRpcClient(endpoint);
|
||||||
// prettier-ignore
|
const chainId = await service.getChainIdAsync();
|
||||||
public async executeAsync<
|
return new SamplerClient(
|
||||||
T1
|
chainId,
|
||||||
>(...ops: [T1]): Promise<[
|
service,
|
||||||
BatchedOperationResult<T1>
|
);
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2
|
|
||||||
>(...ops: [T1, T2]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3
|
|
||||||
>(...ops: [T1, T2, T3]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4
|
|
||||||
>(...ops: [T1, T2, T3, T4]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5, T6
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5, T6]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>,
|
|
||||||
BatchedOperationResult<T6>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5, T6, T7
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>,
|
|
||||||
BatchedOperationResult<T6>,
|
|
||||||
BatchedOperationResult<T7>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5, T6, T7, T8
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>,
|
|
||||||
BatchedOperationResult<T6>,
|
|
||||||
BatchedOperationResult<T7>,
|
|
||||||
BatchedOperationResult<T8>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5, T6, T7, T8, T9
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8, T9]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>,
|
|
||||||
BatchedOperationResult<T6>,
|
|
||||||
BatchedOperationResult<T7>,
|
|
||||||
BatchedOperationResult<T8>,
|
|
||||||
BatchedOperationResult<T9>
|
|
||||||
]>;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
public async executeAsync<
|
|
||||||
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
|
||||||
>(...ops: [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): Promise<[
|
|
||||||
BatchedOperationResult<T1>,
|
|
||||||
BatchedOperationResult<T2>,
|
|
||||||
BatchedOperationResult<T3>,
|
|
||||||
BatchedOperationResult<T4>,
|
|
||||||
BatchedOperationResult<T5>,
|
|
||||||
BatchedOperationResult<T6>,
|
|
||||||
BatchedOperationResult<T7>,
|
|
||||||
BatchedOperationResult<T8>,
|
|
||||||
BatchedOperationResult<T9>,
|
|
||||||
BatchedOperationResult<T10>,
|
|
||||||
]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
|
||||||
*/
|
|
||||||
public async executeAsync(...ops: any[]): Promise<any[]> {
|
|
||||||
return this.executeBatchAsync(ops);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private constructor(
|
||||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
private readonly _chainId: number,
|
||||||
* Takes an arbitrary length array, but is not typesafe.
|
private readonly _service: SamplerServiceRpcClient,
|
||||||
*/
|
) {}
|
||||||
public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(ops: T): Promise<any[]> {
|
|
||||||
const callDatas = ops.map(o => o.encodeCall());
|
|
||||||
const { overrides, block } = this._samplerOverrides
|
|
||||||
? this._samplerOverrides
|
|
||||||
: { overrides: undefined, block: undefined };
|
|
||||||
|
|
||||||
// All operations are NOOPs
|
public get chainId(): ChainId {
|
||||||
if (callDatas.every(cd => cd === NULL_BYTES)) {
|
return this._chainId;
|
||||||
return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES));
|
}
|
||||||
}
|
|
||||||
// Execute all non-empty calldatas.
|
public async getPricesAsync(
|
||||||
const rawCallResults = await this._samplerContract
|
paths: Address[][],
|
||||||
.batchCall(callDatas.filter(cd => cd !== NULL_BYTES))
|
sources: ERC20BridgeSource[],
|
||||||
.callAsync({ overrides }, block);
|
): Promise<BigNumber[]> {
|
||||||
// Return the parsed results.
|
return this._service.getPricesAsync(paths.map(p => ({
|
||||||
let rawCallResultsIdx = 0;
|
tokenPath: p,
|
||||||
return callDatas.map((callData, i) => {
|
demand: true,
|
||||||
// tslint:disable-next-line:boolean-naming
|
sources,
|
||||||
const { data, success } =
|
})));
|
||||||
callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : { success: true, data: NULL_BYTES };
|
}
|
||||||
return success ? ops[i].handleCallResults(data) : ops[i].handleRevert(data);
|
|
||||||
});
|
public async getTokenInfosAsync(tokens: Address[]): Promise<TokenInfo[]> {
|
||||||
|
return this._service.getTokensAsync(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSellLiquidityAsync(
|
||||||
|
path: Address[],
|
||||||
|
takerAmount: BigNumber,
|
||||||
|
sources: ERC20BridgeSource[],
|
||||||
|
numSamples: number = DEFAULT_LIQUIDITY_SAMPLES,
|
||||||
|
): Promise<DexSample[][]> {
|
||||||
|
const liquidity = await this._service.getSellLiquidityAsync(
|
||||||
|
sources.map(s => ({
|
||||||
|
numSamples,
|
||||||
|
tokenPath: path,
|
||||||
|
inputAmount: takerAmount,
|
||||||
|
source: s,
|
||||||
|
demand: true,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return liquidity.map(
|
||||||
|
liq => liq.liquidityCurves.map(
|
||||||
|
pts =>
|
||||||
|
pts.map(pt => ({
|
||||||
|
input: pt.sellAmount,
|
||||||
|
output: pt.buyAmount,
|
||||||
|
encodedFillData: pt.encodedFillData,
|
||||||
|
metadata: pt.metadata,
|
||||||
|
gasCost: pt.gasCost,
|
||||||
|
source: liq.source,
|
||||||
|
}) as DexSample),
|
||||||
|
)).flat(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getBuyLiquidityAsync(
|
||||||
|
path: Address[],
|
||||||
|
makerAmount: BigNumber,
|
||||||
|
sources: ERC20BridgeSource[],
|
||||||
|
numSamples: number = DEFAULT_LIQUIDITY_SAMPLES,
|
||||||
|
): Promise<DexSample[][]> {
|
||||||
|
const liquidity = await this._service.getBuyLiquidityAsync(
|
||||||
|
sources.map(s => ({
|
||||||
|
numSamples,
|
||||||
|
tokenPath: path,
|
||||||
|
inputAmount: makerAmount,
|
||||||
|
source: s,
|
||||||
|
demand: true,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return liquidity.map(
|
||||||
|
liq => liq.liquidityCurves.map(
|
||||||
|
pts =>
|
||||||
|
pts.map(pt => ({
|
||||||
|
input: pt.buyAmount,
|
||||||
|
output: pt.sellAmount,
|
||||||
|
encodedFillData: pt.encodedFillData,
|
||||||
|
metadata: pt.metadata,
|
||||||
|
gasCost: pt.gasCost,
|
||||||
|
source: liq.source,
|
||||||
|
}) as DexSample),
|
||||||
|
)).flat(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { ContractFunctionObj } from '@0x/base-contract';
|
|
||||||
import { BigNumber, decodeBytesAsRevertError, logUtils } from '@0x/utils';
|
|
||||||
|
|
||||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
|
||||||
|
|
||||||
import { ERC20BridgeSource, FillData, SourceQuoteOperation } from './types';
|
|
||||||
|
|
||||||
export type Parameters<T> = T extends (...args: infer TArgs) => any ? TArgs : never;
|
|
||||||
|
|
||||||
export interface SamplerContractCall<
|
|
||||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
|
||||||
TFillData extends FillData = FillData
|
|
||||||
> {
|
|
||||||
contract: ERC20BridgeSamplerContract;
|
|
||||||
function: TFunc;
|
|
||||||
params: Parameters<TFunc>;
|
|
||||||
callback?: (callResults: string, fillData: TFillData) => BigNumber[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SamplerContractOperation<
|
|
||||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
|
||||||
TFillData extends FillData = FillData
|
|
||||||
> implements SourceQuoteOperation<TFillData> {
|
|
||||||
public readonly source: ERC20BridgeSource;
|
|
||||||
public fillData: TFillData;
|
|
||||||
private readonly _samplerContract: ERC20BridgeSamplerContract;
|
|
||||||
private readonly _samplerFunction: TFunc;
|
|
||||||
private readonly _params: Parameters<TFunc>;
|
|
||||||
private readonly _callback?: (callResults: string, fillData: TFillData) => BigNumber[];
|
|
||||||
|
|
||||||
constructor(opts: { source: ERC20BridgeSource; fillData?: TFillData } & SamplerContractCall<TFunc, TFillData>) {
|
|
||||||
this.source = opts.source;
|
|
||||||
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
|
|
||||||
this._samplerContract = opts.contract;
|
|
||||||
this._samplerFunction = opts.function;
|
|
||||||
this._params = opts.params;
|
|
||||||
this._callback = opts.callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public encodeCall(): string {
|
|
||||||
return this._samplerFunction
|
|
||||||
.bind(this._samplerContract)(...this._params)
|
|
||||||
.getABIEncodedTransactionData();
|
|
||||||
}
|
|
||||||
public handleCallResults(callResults: string): BigNumber[] {
|
|
||||||
if (this._callback !== undefined) {
|
|
||||||
return this._callback(callResults, this.fillData);
|
|
||||||
} else {
|
|
||||||
return this._samplerContract.getABIDecodedReturnData<BigNumber[]>(this._samplerFunction.name, callResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public handleRevert(callResults: string): BigNumber[] {
|
|
||||||
let msg = callResults;
|
|
||||||
try {
|
|
||||||
msg = decodeBytesAsRevertError(callResults).toString();
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
logUtils.warn(`SamplerContractOperation: ${this.source}.${this._samplerFunction.name} reverted ${msg}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { BigNumber, logUtils, NULL_BYTES } from '@0x/utils';
|
|
||||||
|
|
||||||
import { ERC20BridgeSource, FillData, SourceQuoteOperation } from './types';
|
|
||||||
|
|
||||||
interface SamplerNoOperationCall {
|
|
||||||
callback: () => BigNumber[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SamplerNoOperation can be used for sources where we already have all the necessary information
|
|
||||||
* required to perform the sample operations, without needing access to any on-chain data. Using a noop sample
|
|
||||||
* you can skip the eth_call, and just calculate the results directly in typescript land.
|
|
||||||
*/
|
|
||||||
export class SamplerNoOperation<TFillData extends FillData = FillData> implements SourceQuoteOperation<TFillData> {
|
|
||||||
public readonly source: ERC20BridgeSource;
|
|
||||||
public fillData: TFillData;
|
|
||||||
private readonly _callback: () => BigNumber[];
|
|
||||||
|
|
||||||
constructor(opts: { source: ERC20BridgeSource; fillData?: TFillData } & SamplerNoOperationCall) {
|
|
||||||
this.source = opts.source;
|
|
||||||
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
|
|
||||||
this._callback = opts.callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line:prefer-function-over-method
|
|
||||||
public encodeCall(): string {
|
|
||||||
return NULL_BYTES;
|
|
||||||
}
|
|
||||||
public handleCallResults(_callResults: string): BigNumber[] {
|
|
||||||
return this._callback();
|
|
||||||
}
|
|
||||||
public handleRevert(_callResults: string): BigNumber[] {
|
|
||||||
logUtils.warn(`SamplerNoOperation: ${this.source} reverted`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,169 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { Client as OpenRpcClient, HTTPTransport, RequestManager } from '@open-rpc/client-js';
|
||||||
|
|
||||||
|
import { Address, Bytes } from '../../types';
|
||||||
|
|
||||||
|
type DecimalString = string;
|
||||||
|
|
||||||
|
export interface LiquidityCurvePoint {
|
||||||
|
sellAmount: BigNumber;
|
||||||
|
buyAmount: BigNumber;
|
||||||
|
encodedFillData: Bytes;
|
||||||
|
metadata: object;
|
||||||
|
gasCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RpcLiquidityCurvePoint {
|
||||||
|
sellAmount: DecimalString;
|
||||||
|
buyAmount: DecimalString;
|
||||||
|
encodedFillData: Bytes;
|
||||||
|
jsonMetadata: string;
|
||||||
|
gasCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LiquidityRequest {
|
||||||
|
tokenPath: Address[];
|
||||||
|
inputAmount: BigNumber;
|
||||||
|
source: string;
|
||||||
|
demand?: boolean;
|
||||||
|
numSamples?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpcLiquidityRequest = Omit<LiquidityRequest, 'inputAmount'> & {
|
||||||
|
inputAmount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceRequest {
|
||||||
|
tokenPath: Address[];
|
||||||
|
sources?: string[];
|
||||||
|
demand?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpcPriceRequest = PriceRequest;
|
||||||
|
|
||||||
|
export interface LiquidityResponse {
|
||||||
|
source: string;
|
||||||
|
liquidityCurves: LiquidityCurvePoint[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpcLiquidityResponse = & Omit<LiquidityResponse, 'liquidityCurves'> & {
|
||||||
|
source: string;
|
||||||
|
liquidityCurves: RpcLiquidityCurvePoint[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TokenResponse {
|
||||||
|
address: Address;
|
||||||
|
symbol: string;
|
||||||
|
decimals: number;
|
||||||
|
gasCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpcTokenResponse = TokenResponse;
|
||||||
|
|
||||||
|
export class SamplerServiceRpcClient {
|
||||||
|
private _rpcClient: OpenRpcClient;
|
||||||
|
|
||||||
|
public constructor(url: string) {
|
||||||
|
const transport = new HTTPTransport(url);
|
||||||
|
// HACK(dorothy-zbornak): One of AS/API's deps globally registers a version of
|
||||||
|
// isometric-fetch that doesn't work with open-rpc. It seems to disagree on
|
||||||
|
// the type of 'headers'.
|
||||||
|
(transport as any).headers = {'content-type': 'application/json'};
|
||||||
|
this._rpcClient = new OpenRpcClient(new RequestManager([transport]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _requestAsync<TResult, TArgs = any>(method: string, params: TArgs[] = []): Promise<TResult> {
|
||||||
|
try {
|
||||||
|
return await this._rpcClient.request({ method, params }) as Promise<TResult>;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Error making RPC request "${method}" to sampler service: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChainIdAsync(): Promise<number> {
|
||||||
|
return this._requestAsync<number>('get_chain_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSellLiquidityAsync(reqs: LiquidityRequest[]): Promise<LiquidityResponse[]> {
|
||||||
|
const resp = await this._requestAsync<RpcLiquidityResponse[], RpcLiquidityRequest[]>(
|
||||||
|
'get_sell_liquidity',
|
||||||
|
[
|
||||||
|
reqs.map(r => ({
|
||||||
|
...r,
|
||||||
|
inputAmount: r.inputAmount.toString(10),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return resp.map(r => ({
|
||||||
|
...r,
|
||||||
|
liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({
|
||||||
|
...c,
|
||||||
|
buyAmount: new BigNumber(c.buyAmount),
|
||||||
|
sellAmount: new BigNumber(c.sellAmount),
|
||||||
|
metadata: decodeMetadata(c.jsonMetadata),
|
||||||
|
}))),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getBuyLiquidityAsync(reqs: LiquidityRequest[]): Promise<LiquidityResponse[]> {
|
||||||
|
const resp = await this._requestAsync<RpcLiquidityResponse[], RpcLiquidityRequest[]>(
|
||||||
|
'get_buy_liquidity',
|
||||||
|
[
|
||||||
|
reqs.map(r => ({
|
||||||
|
...r,
|
||||||
|
inputAmount: r.inputAmount.toString(10),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return resp.map(r => ({
|
||||||
|
...r,
|
||||||
|
liquidityCurves: r.liquidityCurves.map(a => a.map(c => ({
|
||||||
|
...c,
|
||||||
|
buyAmount: new BigNumber(c.buyAmount),
|
||||||
|
sellAmount: new BigNumber(c.sellAmount),
|
||||||
|
metadata: decodeMetadata(c.jsonMetadata),
|
||||||
|
}))),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPricesAsync(reqs: PriceRequest[]): Promise<BigNumber[]> {
|
||||||
|
const resp = await this._requestAsync<DecimalString[], RpcPriceRequest[]>(
|
||||||
|
'get_prices',
|
||||||
|
[ reqs ],
|
||||||
|
);
|
||||||
|
return resp.map(r => new BigNumber(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTokensAsync(addresses: Address[]): Promise<TokenResponse[]> {
|
||||||
|
return this._requestAsync<RpcTokenResponse[], Address[]>(
|
||||||
|
'get_tokens',
|
||||||
|
[ addresses ],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeMetadata(jsonMetadata: string): any {
|
||||||
|
if (!jsonMetadata) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return unmarshallMetadata(JSON.parse(jsonMetadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmarshallMetadata(v: any): any {
|
||||||
|
switch (typeof(v)) {
|
||||||
|
case 'string':
|
||||||
|
if (/^\d+n$/.test(v)) {
|
||||||
|
return new BigNumber(v.slice(0, -1));
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
case 'object':
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v.map(v => unmarshallMetadata(v));
|
||||||
|
}
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
...Object.entries(v).map(([k, v]) => ({ [k]: v})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
FillQuoteTransformerLimitOrderInfo,
|
|
||||||
FillQuoteTransformerOrderType,
|
FillQuoteTransformerOrderType,
|
||||||
FillQuoteTransformerRfqOrderInfo,
|
LimitOrderFields,
|
||||||
|
RfqOrderFields,
|
||||||
|
Signature,
|
||||||
} from '@0x/protocol-utils';
|
} from '@0x/protocol-utils';
|
||||||
import { MarketOperation } from '@0x/types';
|
import { MarketOperation } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
import { Address, Bytes, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
||||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from '../../utils/quote_requestor';
|
import { NativeOrderWithFillableAmounts } from '../native_orders';
|
||||||
|
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||||
|
|
||||||
import { CollapsedPath } from './path';
|
import { CollapsedPath } from './path';
|
||||||
@@ -34,80 +36,76 @@ export enum AggregationError {
|
|||||||
/**
|
/**
|
||||||
* DEX sources to aggregate.
|
* DEX sources to aggregate.
|
||||||
*/
|
*/
|
||||||
export enum ERC20BridgeSource {
|
export enum ERC20BridgeSource {
|
||||||
Native = 'Native',
|
Native = 'Native',
|
||||||
Uniswap = 'Uniswap',
|
Uniswap = 'Uniswap',
|
||||||
UniswapV2 = 'Uniswap_V2',
|
UniswapV2 = 'Uniswap_V2',
|
||||||
Eth2Dai = 'Eth2Dai',
|
Eth2Dai = 'Eth2Dai',
|
||||||
Kyber = 'Kyber',
|
Kyber = 'Kyber',
|
||||||
Curve = 'Curve',
|
Curve = 'Curve',
|
||||||
LiquidityProvider = 'LiquidityProvider',
|
LiquidityProvider = 'LiquidityProvider',
|
||||||
MultiBridge = 'MultiBridge',
|
MultiBridge = 'MultiBridge',
|
||||||
Balancer = 'Balancer',
|
Balancer = 'Balancer',
|
||||||
BalancerV2 = 'Balancer_V2',
|
BalancerV2 = 'Balancer_V2',
|
||||||
Cream = 'CREAM',
|
Cream = 'CREAM',
|
||||||
Bancor = 'Bancor',
|
Bancor = 'Bancor',
|
||||||
MakerPsm = 'MakerPsm',
|
MakerPsm = 'MakerPsm',
|
||||||
MStable = 'mStable',
|
MStable = 'mStable',
|
||||||
Mooniswap = 'Mooniswap',
|
Mooniswap = 'Mooniswap',
|
||||||
MultiHop = 'MultiHop',
|
MultiHop = 'MultiHop',
|
||||||
Shell = 'Shell',
|
Shell = 'Shell',
|
||||||
Swerve = 'Swerve',
|
Swerve = 'Swerve',
|
||||||
SnowSwap = 'SnowSwap',
|
SnowSwap = 'SnowSwap',
|
||||||
SushiSwap = 'SushiSwap',
|
SushiSwap = 'SushiSwap',
|
||||||
Dodo = 'DODO',
|
Dodo = 'DODO',
|
||||||
DodoV2 = 'DODO_V2',
|
DodoV2 = 'DODO_V2',
|
||||||
CryptoCom = 'CryptoCom',
|
CryptoCom = 'CryptoCom',
|
||||||
Linkswap = 'Linkswap',
|
Linkswap = 'Linkswap',
|
||||||
KyberDmm = 'KyberDMM',
|
KyberDmm = 'KyberDMM',
|
||||||
Smoothy = 'Smoothy',
|
Smoothy = 'Smoothy',
|
||||||
Component = 'Component',
|
Component = 'Component',
|
||||||
Saddle = 'Saddle',
|
Saddle = 'Saddle',
|
||||||
XSigma = 'xSigma',
|
XSigma = 'xSigma',
|
||||||
UniswapV3 = 'Uniswap_V3',
|
UniswapV3 = 'Uniswap_V3',
|
||||||
CurveV2 = 'Curve_V2',
|
CurveV2 = 'Curve_V2',
|
||||||
Lido = 'Lido',
|
Lido = 'Lido',
|
||||||
ShibaSwap = 'ShibaSwap',
|
ShibaSwap = 'ShibaSwap',
|
||||||
AaveV2 = 'Aave_V2',
|
AaveV2 = 'Aave_V2',
|
||||||
Compound = 'Compound',
|
Compound = 'Compound',
|
||||||
Synapse = 'Synapse',
|
Synapse = 'Synapse',
|
||||||
// BSC only
|
// BSC only
|
||||||
PancakeSwap = 'PancakeSwap',
|
PancakeSwap = 'PancakeSwap',
|
||||||
PancakeSwapV2 = 'PancakeSwap_V2',
|
PancakeSwapV2 = 'PancakeSwap_V2',
|
||||||
BakerySwap = 'BakerySwap',
|
BakerySwap = 'BakerySwap',
|
||||||
Nerve = 'Nerve',
|
Nerve = 'Nerve',
|
||||||
Belt = 'Belt',
|
Belt = 'Belt',
|
||||||
Ellipsis = 'Ellipsis',
|
Ellipsis = 'Ellipsis',
|
||||||
ApeSwap = 'ApeSwap',
|
ApeSwap = 'ApeSwap',
|
||||||
CafeSwap = 'CafeSwap',
|
CafeSwap = 'CafeSwap',
|
||||||
CheeseSwap = 'CheeseSwap',
|
CheeseSwap = 'CheeseSwap',
|
||||||
JulSwap = 'JulSwap',
|
JulSwap = 'JulSwap',
|
||||||
ACryptos = 'ACryptoS',
|
ACryptos = 'ACryptoS',
|
||||||
// Polygon only
|
// Polygon only
|
||||||
QuickSwap = 'QuickSwap',
|
QuickSwap = 'QuickSwap',
|
||||||
ComethSwap = 'ComethSwap',
|
ComethSwap = 'ComethSwap',
|
||||||
Dfyn = 'Dfyn',
|
Dfyn = 'Dfyn',
|
||||||
WaultSwap = 'WaultSwap',
|
WaultSwap = 'WaultSwap',
|
||||||
Polydex = 'Polydex',
|
Polydex = 'Polydex',
|
||||||
FirebirdOneSwap = 'FirebirdOneSwap',
|
FirebirdOneSwap = 'FirebirdOneSwap',
|
||||||
JetSwap = 'JetSwap',
|
JetSwap = 'JetSwap',
|
||||||
IronSwap = 'IronSwap',
|
IronSwap = 'IronSwap',
|
||||||
// Avalanche
|
// Avalanche
|
||||||
Pangolin = 'Pangolin',
|
Pangolin = 'Pangolin',
|
||||||
TraderJoe = 'TraderJoe',
|
TraderJoe = 'TraderJoe',
|
||||||
// Celo only
|
// Celo only
|
||||||
UbeSwap = 'UbeSwap',
|
UbeSwap = 'UbeSwap',
|
||||||
// Fantom
|
// Fantom
|
||||||
SpiritSwap = 'SpiritSwap',
|
SpiritSwap = 'SpiritSwap',
|
||||||
SpookySwap = 'SpookySwap',
|
SpookySwap = 'SpookySwap',
|
||||||
Beethovenx = 'Beethovenx',
|
Beethovenx = 'Beethovenx',
|
||||||
MorpheusSwap = 'MorpheusSwap',
|
MorpheusSwap = 'MorpheusSwap',
|
||||||
}
|
}
|
||||||
export type SourcesWithPoolsCache =
|
export type SourcesWithPoolsCache = ERC20BridgeSource.Balancer | ERC20BridgeSource.BalancerV2 | ERC20BridgeSource.Cream;
|
||||||
| ERC20BridgeSource.Balancer
|
|
||||||
| ERC20BridgeSource.BalancerV2
|
|
||||||
| ERC20BridgeSource.Beethovenx
|
|
||||||
| ERC20BridgeSource.Cream;
|
|
||||||
|
|
||||||
// tslint:disable: enum-naming
|
// tslint:disable: enum-naming
|
||||||
/**
|
/**
|
||||||
@@ -116,13 +114,11 @@ export type SourcesWithPoolsCache =
|
|||||||
export enum CurveFunctionSelectors {
|
export enum CurveFunctionSelectors {
|
||||||
None = '0x00000000',
|
None = '0x00000000',
|
||||||
exchange = '0x3df02124',
|
exchange = '0x3df02124',
|
||||||
exchange_underlying = '0xa6417ed6', // exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
exchange_underlying = '0xa6417ed6',
|
||||||
get_dy_underlying = '0x07211ef7',
|
get_dy_underlying = '0x07211ef7',
|
||||||
get_dx_underlying = '0x0e71d1b9',
|
get_dx_underlying = '0x0e71d1b9',
|
||||||
get_dy = '0x5e0d443f', // get_dy(int128,int128,uint256)
|
get_dy = '0x5e0d443f',
|
||||||
get_dx = '0x67df02ca',
|
get_dx = '0x67df02ca',
|
||||||
get_dy_uint256 = '0x556d6e9f', // get_dy(uint256,uint256,uint256)
|
|
||||||
exchange_underlying_uint256 = '0x65b2489b', // exchange_underlying(uint256,uint256,uint256,uint256)
|
|
||||||
// Curve V2
|
// Curve V2
|
||||||
exchange_v2 = '0x5b41b908',
|
exchange_v2 = '0x5b41b908',
|
||||||
exchange_underlying_v2 = '0x65b2489b',
|
exchange_underlying_v2 = '0x65b2489b',
|
||||||
@@ -131,7 +127,7 @@ export enum CurveFunctionSelectors {
|
|||||||
// Smoothy
|
// Smoothy
|
||||||
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
||||||
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
||||||
// Nerve BSC, Saddle Mainnet, Synapse
|
// Nerve BSC, Saddle Mainnet
|
||||||
swap = '0x91695586', // swap(uint8,uint8,uint256,uint256,uint256)
|
swap = '0x91695586', // swap(uint8,uint8,uint256,uint256,uint256)
|
||||||
calculateSwap = '0xa95b089f', // calculateSwap(uint8,uint8,uint256)
|
calculateSwap = '0xa95b089f', // calculateSwap(uint8,uint8,uint256)
|
||||||
}
|
}
|
||||||
@@ -175,141 +171,58 @@ export interface BalancerV2PoolInfo {
|
|||||||
vault: string;
|
vault: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AaveV2Info {
|
|
||||||
lendingPool: string;
|
|
||||||
aToken: string;
|
|
||||||
underlyingToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal `fillData` field for `Fill` objects.
|
|
||||||
export interface FillData {}
|
|
||||||
|
|
||||||
// `FillData` for native fills. Represents a single native order
|
|
||||||
export type NativeRfqOrderFillData = FillQuoteTransformerRfqOrderInfo;
|
|
||||||
export type NativeLimitOrderFillData = FillQuoteTransformerLimitOrderInfo;
|
|
||||||
export type NativeFillData = NativeRfqOrderFillData | NativeLimitOrderFillData;
|
|
||||||
|
|
||||||
// Represents an individual DEX sample from the sampler contract
|
// Represents an individual DEX sample from the sampler contract
|
||||||
export interface DexSample<TFillData extends FillData = FillData> {
|
export interface DexSample {
|
||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
fillData: TFillData;
|
encodedFillData: Bytes;
|
||||||
|
metadata?: any;
|
||||||
input: BigNumber;
|
input: BigNumber;
|
||||||
output: BigNumber;
|
output: BigNumber;
|
||||||
}
|
|
||||||
export interface CurveFillData extends FillData {
|
|
||||||
fromTokenIdx: number;
|
|
||||||
toTokenIdx: number;
|
|
||||||
pool: CurveInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BalancerFillData extends FillData {
|
|
||||||
poolAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BalancerV2FillData extends FillData {
|
|
||||||
vault: string;
|
|
||||||
poolId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UniswapV2FillData extends FillData {
|
|
||||||
tokenAddressPath: string[];
|
|
||||||
router: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShellFillData extends FillData {
|
|
||||||
poolAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LiquidityProviderFillData extends FillData {
|
|
||||||
poolAddress: string;
|
|
||||||
gasCost: number;
|
gasCost: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BancorFillData extends FillData {
|
export interface BridgeFillData {
|
||||||
path: string[];
|
encodedFillData: Bytes;
|
||||||
networkAddress: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KyberFillData extends FillData {
|
export interface UniswapV2FillData extends BridgeFillData {
|
||||||
hint: string;
|
tokenAddressPath: Address[];
|
||||||
reserveId: string;
|
|
||||||
networkProxy: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MooniswapFillData extends FillData {
|
export interface UniswapV3FillData extends BridgeFillData {
|
||||||
poolAddress: string;
|
encodedPath: Bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DODOFillData extends FillData {
|
export interface LiquidityProviderFillData extends BridgeFillData {
|
||||||
poolAddress: string;
|
poolAddress: Address;
|
||||||
isSellBase: boolean;
|
|
||||||
helperAddress: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericRouterFillData extends FillData {
|
export interface CurveFillData extends BridgeFillData {
|
||||||
router: string;
|
poolAddress: Address;
|
||||||
|
exchangeFunctionSelector: Bytes;
|
||||||
|
fromTokenIdx: number;
|
||||||
|
toTokenIdx: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultiHopFillData extends FillData {
|
export interface MooniswapFillData extends BridgeFillData {
|
||||||
firstHopSource: SourceQuoteOperation;
|
poolAddress: Address;
|
||||||
secondHopSource: SourceQuoteOperation;
|
|
||||||
intermediateToken: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MakerPsmExtendedData {
|
export interface NativeOrderFillData {
|
||||||
isSellOperation: boolean;
|
order: LimitOrderFields | RfqOrderFields;
|
||||||
takerToken: string;
|
signature: Signature;
|
||||||
}
|
fillableTakerAmount: BigNumber;
|
||||||
|
|
||||||
export type MakerPsmFillData = FillData & MakerPsmExtendedData & PsmInfo;
|
|
||||||
|
|
||||||
export interface HopInfo {
|
|
||||||
sourceIndex: BigNumber;
|
|
||||||
returnData: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UniswapV3FillData extends FillData {
|
|
||||||
tokenAddressPath: string[];
|
|
||||||
router: string;
|
|
||||||
pathAmounts: Array<{ uniswapPath: string; inputAmount: BigNumber }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KyberDmmFillData extends UniswapV2FillData {
|
|
||||||
poolsPath: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FinalUniswapV3FillData extends Omit<UniswapV3FillData, 'uniswapPaths'> {
|
|
||||||
// The uniswap-encoded path that can fll the maximum input amount.
|
|
||||||
uniswapPath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LidoFillData extends FillData {
|
|
||||||
stEthTokenAddress: string;
|
|
||||||
takerToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AaveV2FillData extends FillData {
|
|
||||||
lendingPool: string;
|
|
||||||
aToken: string;
|
|
||||||
underlyingToken: string;
|
|
||||||
takerToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompoundFillData extends FillData {
|
|
||||||
cToken: string;
|
|
||||||
takerToken: string;
|
|
||||||
makerToken: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a node on a fill path.
|
* Represents a node on a fill path.
|
||||||
*/
|
*/
|
||||||
export interface Fill<TFillData extends FillData = FillData> {
|
export interface Fill {
|
||||||
// basic data for every fill
|
// basic data for every fill
|
||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
// TODO jacob people seem to agree that orderType here is more readable
|
// TODO jacob people seem to agree that orderType here is more readable
|
||||||
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
||||||
fillData: TFillData;
|
data?: any;
|
||||||
// Unique ID of the original source path this fill belongs to.
|
// Unique ID of the original source path this fill belongs to.
|
||||||
// This is generated when the path is generated and is useful to distinguish
|
// This is generated when the path is generated and is useful to distinguish
|
||||||
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
||||||
@@ -326,15 +239,37 @@ export interface Fill<TFillData extends FillData = FillData> {
|
|||||||
parent?: Fill;
|
parent?: Fill;
|
||||||
// The index of the fill in the original path.
|
// The index of the fill in the original path.
|
||||||
index: number;
|
index: number;
|
||||||
|
// Cumulative gas cost associated with swapping against this source/pool.
|
||||||
|
gasCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeFill<TData extends BridgeFillData> extends Fill {
|
||||||
|
data: TData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericBridgeFill extends BridgeFill<BridgeFillData> {}
|
||||||
|
|
||||||
|
export interface UniswapV2BridgeFill extends BridgeFill<UniswapV2FillData> {}
|
||||||
|
|
||||||
|
export interface UniswapV3BridgeFill extends BridgeFill<UniswapV3FillData> {}
|
||||||
|
|
||||||
|
export interface LiquidityProviderBridgeFill extends BridgeFill<LiquidityProviderFillData> {}
|
||||||
|
|
||||||
|
export interface CurveBridgeFill extends BridgeFill<CurveFillData> {}
|
||||||
|
|
||||||
|
export interface MooniswapBridgeFill extends BridgeFill<MooniswapFillData> {}
|
||||||
|
|
||||||
|
export interface NativeOrderFill extends Fill {
|
||||||
|
data: NativeOrderFillData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents continguous fills on a path that have been merged together.
|
* Represents continguous fills on a path that have been merged together.
|
||||||
*/
|
*/
|
||||||
export interface CollapsedFill<TFillData extends FillData = FillData> {
|
export interface CollapsedFill {
|
||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
||||||
fillData: TFillData;
|
data?: any;
|
||||||
// Unique ID of the original source path this fill belongs to.
|
// Unique ID of the original source path this fill belongs to.
|
||||||
// This is generated when the path is generated and is useful to distinguish
|
// This is generated when the path is generated and is useful to distinguish
|
||||||
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
||||||
@@ -354,56 +289,70 @@ export interface CollapsedFill<TFillData extends FillData = FillData> {
|
|||||||
input: BigNumber;
|
input: BigNumber;
|
||||||
output: BigNumber;
|
output: BigNumber;
|
||||||
}>;
|
}>;
|
||||||
|
gasCost: number;
|
||||||
|
isFallback: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface CollapsedBridgeFill<TData extends BridgeFillData> extends CollapsedFill {
|
||||||
* A `CollapsedFill` wrapping a native order.
|
data: TData;
|
||||||
*/
|
}
|
||||||
export interface NativeCollapsedFill extends CollapsedFill<NativeFillData> {}
|
|
||||||
|
|
||||||
export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> {
|
export interface CollapsedGenericBridgeFill extends CollapsedBridgeFill<BridgeFillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedUniswapV2BridgeFill extends CollapsedBridgeFill<UniswapV2FillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedUniswapV3BridgeFill extends CollapsedBridgeFill<UniswapV3FillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedLiquidityProviderBridgeFill extends CollapsedBridgeFill<LiquidityProviderFillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedCurveBridgeFill extends CollapsedBridgeFill<CurveFillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedMooniswapBridgeFill extends CollapsedBridgeFill<MooniswapFillData> {}
|
||||||
|
|
||||||
|
export interface CollapsedNativeOrderFill extends CollapsedFill {
|
||||||
|
data: NativeOrderFillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptimizedOrder {
|
||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
fillData: TFillData;
|
type: FillQuoteTransformerOrderType;
|
||||||
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
inputToken: string;
|
||||||
makerToken: string;
|
outputToken: string;
|
||||||
takerToken: string;
|
gasCost: number;
|
||||||
makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill
|
inputAmount: BigNumber;
|
||||||
takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill
|
outputAmount: BigNumber;
|
||||||
fills: CollapsedFill[];
|
fills: CollapsedFill[];
|
||||||
|
isFallback: boolean;
|
||||||
|
fillData: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
|
export interface OptimizedBridgeOrder<TFillData extends BridgeFillData> extends OptimizedOrder {
|
||||||
extends OptimizedMarketOrderBase<TFillData> {
|
|
||||||
type: FillQuoteTransformerOrderType.Bridge;
|
type: FillQuoteTransformerOrderType.Bridge;
|
||||||
fillData: TFillData;
|
|
||||||
sourcePathId: string;
|
sourcePathId: string;
|
||||||
|
fillData: TFillData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
export interface OptimizedGenericBridgeOrder extends OptimizedBridgeOrder<BridgeFillData> {}
|
||||||
|
|
||||||
|
export interface OptimizedUniswapV2BridgeOrder extends OptimizedBridgeOrder<UniswapV2FillData> {}
|
||||||
|
|
||||||
|
export interface OptimizedNativeOrder extends OptimizedOrder {
|
||||||
|
fillData: Omit<NativeOrderFillData, 'type'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptimizedLimitOrder extends OptimizedNativeOrder {
|
||||||
type: FillQuoteTransformerOrderType.Limit;
|
type: FillQuoteTransformerOrderType.Limit;
|
||||||
fillData: NativeLimitOrderFillData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
export interface OptimizedRfqOrder extends OptimizedNativeOrder {
|
||||||
type: FillQuoteTransformerOrderType.Rfq;
|
type: FillQuoteTransformerOrderType.Rfq;
|
||||||
fillData: NativeRfqOrderFillData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimized orders to fill.
|
|
||||||
*/
|
|
||||||
export type OptimizedMarketOrder =
|
|
||||||
| OptimizedMarketBridgeOrder<FillData>
|
|
||||||
| OptimizedMarketOrderBase<NativeLimitOrderFillData>
|
|
||||||
| OptimizedMarketOrderBase<NativeRfqOrderFillData>;
|
|
||||||
|
|
||||||
export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
||||||
quoteRequestor?: QuoteRequestor;
|
quoteRequestor?: QuoteRequestor;
|
||||||
firmQuoteValidator?: RfqFirmQuoteValidator;
|
firmQuoteValidator?: RfqFirmQuoteValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeeEstimate = (fillData: FillData) => number | BigNumber;
|
|
||||||
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
|
|
||||||
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -444,30 +393,10 @@ export interface GetMarketOrdersOpts {
|
|||||||
* percentage, no fallback quote will be provided.
|
* percentage, no fallback quote will be provided.
|
||||||
*/
|
*/
|
||||||
maxFallbackSlippage: number;
|
maxFallbackSlippage: number;
|
||||||
/**
|
|
||||||
* Number of samples to take for each DEX quote.
|
|
||||||
*/
|
|
||||||
numSamples: number;
|
|
||||||
/**
|
|
||||||
* The exponential sampling distribution base.
|
|
||||||
* A value of 1 will result in evenly spaced samples.
|
|
||||||
* > 1 will result in more samples at lower sizes.
|
|
||||||
* < 1 will result in more samples at higher sizes.
|
|
||||||
* Default: 1.25.
|
|
||||||
*/
|
|
||||||
sampleDistributionBase: number;
|
|
||||||
/**
|
/**
|
||||||
* Number of samples to use when creating fill curves with neon-router
|
* Number of samples to use when creating fill curves with neon-router
|
||||||
*/
|
*/
|
||||||
neonRouterNumSamples: number;
|
neonRouterNumSamples: number;
|
||||||
/**
|
|
||||||
* Fees for each liquidity source, expressed in gas.
|
|
||||||
*/
|
|
||||||
feeSchedule: FeeSchedule;
|
|
||||||
/**
|
|
||||||
* Estimated gas consumed by each liquidity source.
|
|
||||||
*/
|
|
||||||
gasSchedule: FeeSchedule;
|
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||||
/**
|
/**
|
||||||
* Whether to pad the quote with a redundant fallback quote using different
|
* Whether to pad the quote with a redundant fallback quote using different
|
||||||
@@ -539,17 +468,20 @@ export interface BatchedOperation<TResult> {
|
|||||||
handleRevert(callResults: string): TResult;
|
handleRevert(callResults: string): TResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SourceQuoteOperation<TFillData extends FillData = FillData> extends BatchedOperation<BigNumber[]> {
|
export interface OptimizedHop {
|
||||||
readonly source: ERC20BridgeSource;
|
inputToken: Address;
|
||||||
fillData: TFillData;
|
outputToken: Address;
|
||||||
|
inputAmount: BigNumber;
|
||||||
|
outputAmount: BigNumber;
|
||||||
|
sourceFlags: bigint;
|
||||||
|
orders: OptimizedOrder[];
|
||||||
|
adjustedCompleteRate: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizerResult {
|
export interface OptimizerResult {
|
||||||
optimizedOrders: OptimizedMarketOrder[];
|
|
||||||
sourceFlags: bigint;
|
|
||||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
|
||||||
marketSideLiquidity: MarketSideLiquidity;
|
|
||||||
adjustedRate: BigNumber;
|
adjustedRate: BigNumber;
|
||||||
|
hops: OptimizedHop[];
|
||||||
|
marketSideLiquidity: MarketSideLiquidity;
|
||||||
unoptimizedPath?: CollapsedPath;
|
unoptimizedPath?: CollapsedPath;
|
||||||
takerAmountPerEth: BigNumber;
|
takerAmountPerEth: BigNumber;
|
||||||
makerAmountPerEth: BigNumber;
|
makerAmountPerEth: BigNumber;
|
||||||
@@ -561,7 +493,7 @@ export interface OptimizerResultWithReport extends OptimizerResult {
|
|||||||
priceComparisonsReport?: PriceComparisonsReport;
|
priceComparisonsReport?: PriceComparisonsReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MarketDepthSide = Array<Array<DexSample<FillData>>>;
|
export type MarketDepthSide = Array<Array<DexSample>>;
|
||||||
|
|
||||||
export interface MarketDepth {
|
export interface MarketDepth {
|
||||||
bids: MarketDepthSide;
|
bids: MarketDepthSide;
|
||||||
@@ -570,25 +502,30 @@ export interface MarketDepth {
|
|||||||
takerTokenDecimals: number;
|
takerTokenDecimals: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TokenAmountPerEth {
|
||||||
|
[tokenAddress: string]: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MarketSideLiquidity {
|
export interface MarketSideLiquidity {
|
||||||
side: MarketOperation;
|
side: MarketOperation;
|
||||||
inputAmount: BigNumber;
|
inputAmount: BigNumber;
|
||||||
inputToken: string;
|
inputToken: string;
|
||||||
outputToken: string;
|
outputToken: string;
|
||||||
outputAmountPerEth: BigNumber;
|
tokenAmountPerEth: TokenAmountPerEth;
|
||||||
inputAmountPerEth: BigNumber;
|
|
||||||
quoteSourceFilters: SourceFilters;
|
quoteSourceFilters: SourceFilters;
|
||||||
makerTokenDecimals: number;
|
makerTokenDecimals: number;
|
||||||
takerTokenDecimals: number;
|
takerTokenDecimals: number;
|
||||||
quotes: RawQuotes;
|
quotes: RawHopQuotes[];
|
||||||
isRfqSupported: boolean;
|
isRfqSupported: boolean;
|
||||||
|
gasPrice: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawQuotes {
|
export interface RawHopQuotes {
|
||||||
|
tokenPath: Address[];
|
||||||
|
inputToken: Address;
|
||||||
|
outputToken: Address;
|
||||||
nativeOrders: NativeOrderWithFillableAmounts[];
|
nativeOrders: NativeOrderWithFillableAmounts[];
|
||||||
rfqtIndicativeQuotes: V4RFQIndicativeQuoteMM[];
|
dexQuotes: DexSample[][];
|
||||||
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
|
|
||||||
dexQuotes: Array<Array<DexSample<FillData>>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenAdjacencyGraph {
|
export interface TokenAdjacencyGraph {
|
||||||
@@ -608,7 +545,6 @@ export interface GenerateOptimizedOrdersOpts {
|
|||||||
bridgeSlippage?: number;
|
bridgeSlippage?: number;
|
||||||
maxFallbackSlippage?: number;
|
maxFallbackSlippage?: number;
|
||||||
excludedSources?: ERC20BridgeSource[];
|
excludedSources?: ERC20BridgeSource[];
|
||||||
feeSchedule: FeeSchedule;
|
|
||||||
exchangeProxyOverhead?: ExchangeProxyOverhead;
|
exchangeProxyOverhead?: ExchangeProxyOverhead;
|
||||||
allowFallback?: boolean;
|
allowFallback?: boolean;
|
||||||
shouldBatchBridgeOrders?: boolean;
|
shouldBatchBridgeOrders?: boolean;
|
||||||
|
|||||||
31
packages/asset-swapper/src/utils/native_orders.ts
Normal file
31
packages/asset-swapper/src/utils/native_orders.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import {
|
||||||
|
CommonOrderFields,
|
||||||
|
FillQuoteTransformerOrderType,
|
||||||
|
LimitOrderFields,
|
||||||
|
RfqOrderFields,
|
||||||
|
Signature,
|
||||||
|
} from '@0x/protocol-utils';
|
||||||
|
|
||||||
|
import { SignedNativeOrder } from '../types';
|
||||||
|
|
||||||
|
export interface SignedOrder<T> {
|
||||||
|
order: T;
|
||||||
|
type: FillQuoteTransformerOrderType.Limit | FillQuoteTransformerOrderType.Rfq;
|
||||||
|
signature: Signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields & {
|
||||||
|
gasCost: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fillableMakerAmount: Amount of makerAsset that is fillable
|
||||||
|
* fillableTakerAmount: Amount of takerAsset that is fillable
|
||||||
|
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAmount
|
||||||
|
*/
|
||||||
|
export interface NativeOrderFillableAmountFields {
|
||||||
|
fillableMakerAmount: BigNumber;
|
||||||
|
fillableTakerAmount: BigNumber;
|
||||||
|
fillableTakerFeeAmount: BigNumber;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as heartbeats from 'heartbeats';
|
import * as heartbeats from 'heartbeats';
|
||||||
|
import fetch from 'axios';
|
||||||
|
|
||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import { SwapQuoterError } from '../types';
|
import { SwapQuoterError } from '../types';
|
||||||
@@ -61,7 +62,7 @@ export class ProtocolFeeUtils {
|
|||||||
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
|
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(this._ethGasStationUrl);
|
const res = await fetch(this._ethGasStationUrl);
|
||||||
const gasInfo = await res.json();
|
const gasInfo = res.data;
|
||||||
// Eth Gas Station result is gwei * 10
|
// Eth Gas Station result is gwei * 10
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
const BASE_TEN = 10;
|
const BASE_TEN = 10;
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
import { FillQuoteTransformerOrderType, RfqOrderFields, Signature } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType, RfqOrderFields } from '@0x/protocol-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import _ = require('lodash');
|
|
||||||
|
|
||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
|
import { Address, MarketOperation } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CollapsedFill,
|
|
||||||
DexSample,
|
DexSample,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
FillData,
|
RawHopQuotes,
|
||||||
MultiHopFillData,
|
OptimizedHop,
|
||||||
NativeCollapsedFill,
|
OptimizedNativeOrder,
|
||||||
NativeFillData,
|
|
||||||
NativeLimitOrderFillData,
|
|
||||||
NativeRfqOrderFillData,
|
|
||||||
RawQuotes,
|
|
||||||
} from './market_operation_utils/types';
|
} from './market_operation_utils/types';
|
||||||
|
import { NativeOrderWithFillableAmounts } from './native_orders';
|
||||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
||||||
|
|
||||||
export interface QuoteReportEntryBase {
|
export interface QuoteReportEntryBase {
|
||||||
liquiditySource: ERC20BridgeSource;
|
liquiditySource: ERC20BridgeSource;
|
||||||
makerAmount: BigNumber;
|
makerAmount: BigNumber;
|
||||||
takerAmount: BigNumber;
|
takerAmount: BigNumber;
|
||||||
fillData: FillData;
|
fillData: any;
|
||||||
}
|
}
|
||||||
export interface BridgeQuoteReportEntry extends QuoteReportEntryBase {
|
export interface BridgeQuoteReportEntry extends QuoteReportEntryBase {
|
||||||
liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>;
|
liquiditySource: Exclude<ERC20BridgeSource, ERC20BridgeSource.Native>;
|
||||||
@@ -35,14 +30,14 @@ export interface MultiHopQuoteReportEntry extends QuoteReportEntryBase {
|
|||||||
|
|
||||||
export interface NativeLimitOrderQuoteReportEntry extends QuoteReportEntryBase {
|
export interface NativeLimitOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||||
liquiditySource: ERC20BridgeSource.Native;
|
liquiditySource: ERC20BridgeSource.Native;
|
||||||
fillData: NativeFillData;
|
fillData: any;
|
||||||
fillableTakerAmount: BigNumber;
|
fillableTakerAmount: BigNumber;
|
||||||
isRFQ: false;
|
isRFQ: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
export interface NativeRfqOrderQuoteReportEntry extends QuoteReportEntryBase {
|
||||||
liquiditySource: ERC20BridgeSource.Native;
|
liquiditySource: ERC20BridgeSource.Native;
|
||||||
fillData: NativeFillData;
|
fillData: any;
|
||||||
fillableTakerAmount: BigNumber;
|
fillableTakerAmount: BigNumber;
|
||||||
isRFQ: true;
|
isRFQ: true;
|
||||||
nativeOrder: RfqOrderFields;
|
nativeOrder: RfqOrderFields;
|
||||||
@@ -118,44 +113,64 @@ export interface PriceComparisonsReport {
|
|||||||
* Generates a report of sources considered while computing the optimized
|
* Generates a report of sources considered while computing the optimized
|
||||||
* swap quote, and the sources ultimately included in the computed quote.
|
* swap quote, and the sources ultimately included in the computed quote.
|
||||||
*/
|
*/
|
||||||
export function generateQuoteReport(
|
export function generateQuoteReport(opts: {
|
||||||
marketOperation: MarketOperation,
|
side: MarketOperation,
|
||||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
inputToken: Address,
|
||||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
outputToken: Address,
|
||||||
|
rawHopQuotes: RawHopQuotes[],
|
||||||
|
hops: OptimizedHop[],
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
): QuoteReport {
|
}): QuoteReport {
|
||||||
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
|
const { inputToken, outputToken, side } = opts;
|
||||||
nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
|
// Only handle direct taker -> maker raw hops.
|
||||||
);
|
const { nativeOrders } = opts.rawHopQuotes
|
||||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRFQ)];
|
.filter(h => h.inputToken === inputToken && h.outputToken === outputToken)
|
||||||
|
.flat(1)
|
||||||
|
.reduce((a, q) => ({ ...a, dexQuotes: a.dexQuotes.concat(q.dexQuotes), nativeOrders: a.nativeOrders.concat(q.nativeOrders)}));
|
||||||
|
// According to the old code, we only include RFQT samples in quote report?
|
||||||
|
const sourcesConsidered = nativeOrders
|
||||||
|
.filter(o => o.type === FillQuoteTransformerOrderType.Rfq)
|
||||||
|
.map(o => nativeOrderToReportEntry(side, o, opts.comparisonPrice, opts.quoteRequestor));
|
||||||
|
|
||||||
let sourcesDelivered;
|
let sourcesDelivered;
|
||||||
if (Array.isArray(liquidityDelivered)) {
|
if (opts.hops.length === 1) {
|
||||||
// create easy way to look up fillable amounts
|
// Single-hop.
|
||||||
const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
|
const [hop] = opts.hops;
|
||||||
nativeOrders.map(o => {
|
sourcesDelivered = hop.orders.map(o => {
|
||||||
return [_nativeDataToId(o), o.fillableTakerAmount];
|
switch (o.type) {
|
||||||
}),
|
default: {
|
||||||
);
|
const [makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
// map sources delivered
|
? [o.outputAmount, o.inputAmount]
|
||||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
: [o.inputAmount, o.outputAmount];
|
||||||
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
|
return {
|
||||||
return nativeOrderToReportEntry(
|
makerAmount,
|
||||||
collapsedFill.type,
|
takerAmount,
|
||||||
collapsedFill.fillData,
|
liquiditySource: o.source,
|
||||||
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
|
fillData: {}, // Does this matter?
|
||||||
comparisonPrice,
|
} as BridgeQuoteReportEntry;
|
||||||
quoteRequestor,
|
}
|
||||||
);
|
case FillQuoteTransformerOrderType.Limit:
|
||||||
} else {
|
case FillQuoteTransformerOrderType.Rfq: {
|
||||||
return dexSampleToReportSource(collapsedFill, marketOperation);
|
return nativeOrderToReportEntry(side, o, opts.comparisonPrice, opts.quoteRequestor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Multi-hop.
|
||||||
|
const firstHop = opts.hops[0];
|
||||||
|
const lastHop = opts.hops[opts.hops.length - 1];
|
||||||
|
const [makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
|
? [lastHop.outputAmount, firstHop.inputAmount]
|
||||||
|
: [firstHop.inputAmount, lastHop.outputAmount];
|
||||||
sourcesDelivered = [
|
sourcesDelivered = [
|
||||||
// tslint:disable-next-line: no-unnecessary-type-assertion
|
{
|
||||||
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
makerAmount,
|
||||||
|
takerAmount,
|
||||||
|
liquiditySource: ERC20BridgeSource.MultiHop,
|
||||||
|
fillData: {},
|
||||||
|
hopSources: opts.hops.map(h => h.orders.map(o => o.source)).flat(1),
|
||||||
|
} as MultiHopQuoteReportEntry,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -169,47 +184,41 @@ export function generateQuoteReport(
|
|||||||
* swap quote, the sources ultimately included in the computed quote. This
|
* swap quote, the sources ultimately included in the computed quote. This
|
||||||
* extende version incudes all considered quotes, not only native liquidity.
|
* extende version incudes all considered quotes, not only native liquidity.
|
||||||
*/
|
*/
|
||||||
export function generateExtendedQuoteReportSources(
|
export function generateExtendedQuoteReportSources(opts: {
|
||||||
marketOperation: MarketOperation,
|
side: MarketOperation,
|
||||||
quotes: RawQuotes,
|
inputToken: Address,
|
||||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
outputToken: Address,
|
||||||
|
rawHopQuotes: RawHopQuotes[],
|
||||||
|
hops: OptimizedHop[],
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
): ExtendedQuoteReportSources {
|
}): ExtendedQuoteReportSources {
|
||||||
|
const { inputToken, outputToken, side } = opts;
|
||||||
|
const directHops = opts.rawHopQuotes
|
||||||
|
.filter(h => h.inputToken === inputToken && h.outputToken === outputToken)
|
||||||
|
.flat(1)
|
||||||
|
.reduce((a, q) => ({ ...a, dexQuotes: a.dexQuotes.concat(q.dexQuotes), nativeOrders: a.nativeOrders.concat(q.nativeOrders)}));
|
||||||
const sourcesConsidered: ExtendedQuoteReportEntry[] = [];
|
const sourcesConsidered: ExtendedQuoteReportEntry[] = [];
|
||||||
|
|
||||||
// NativeOrders
|
// Native orders.
|
||||||
sourcesConsidered.push(
|
sourcesConsidered.push(
|
||||||
...quotes.nativeOrders.map(order =>
|
...directHops.nativeOrders
|
||||||
nativeOrderToReportEntry(
|
.filter(o => o.type === FillQuoteTransformerOrderType.Rfq)
|
||||||
order.type,
|
.map(o => nativeOrderToReportEntry(side, o, opts.comparisonPrice, opts.quoteRequestor)),
|
||||||
order as any,
|
|
||||||
order.fillableTakerAmount,
|
|
||||||
comparisonPrice,
|
|
||||||
quoteRequestor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// IndicativeQuotes
|
// Dex quotes.
|
||||||
sourcesConsidered.push(
|
sourcesConsidered.push(
|
||||||
...quotes.rfqtIndicativeQuotes.map(order => indicativeQuoteToReportEntry(order, comparisonPrice)),
|
// Only add the last sample that can satisfy the full input amount.
|
||||||
|
...directHops.dexQuotes.map(samples => samples[samples.length - 1])
|
||||||
|
.flat(1)
|
||||||
|
.filter(s => s.input.gte(opts.amount))
|
||||||
|
.map(s => dexSampleToReportSource(side, s)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// MultiHop
|
// TODO: MultiHop
|
||||||
sourcesConsidered.push(...quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, marketOperation)));
|
|
||||||
|
|
||||||
// Dex Quotes
|
|
||||||
sourcesConsidered.push(
|
|
||||||
..._.flatten(
|
|
||||||
quotes.dexQuotes.map(dex =>
|
|
||||||
dex
|
|
||||||
.filter(quote => isDexSampleForTotalAmount(quote, marketOperation, amount))
|
|
||||||
.map(quote => dexSampleToReportSource(quote, marketOperation)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const sourcesConsideredIndexed = sourcesConsidered.map(
|
const sourcesConsideredIndexed = sourcesConsidered.map(
|
||||||
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
(quote, index): ExtendedQuoteReportIndexedEntry => {
|
||||||
return {
|
return {
|
||||||
@@ -219,32 +228,45 @@ export function generateExtendedQuoteReportSources(
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let sourcesDelivered;
|
let sourcesDelivered;
|
||||||
if (Array.isArray(liquidityDelivered)) {
|
if (opts.hops.length === 1) {
|
||||||
// create easy way to look up fillable amounts
|
// Single-hop.
|
||||||
const nativeOrderSignaturesToFillableAmounts = _.fromPairs(
|
const [hop] = opts.hops;
|
||||||
quotes.nativeOrders.map(o => {
|
sourcesDelivered = hop.orders.map(o => {
|
||||||
return [_nativeDataToId(o), o.fillableTakerAmount];
|
switch (o.type) {
|
||||||
}),
|
default: {
|
||||||
);
|
const [makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
// map sources delivered
|
? [o.outputAmount, o.inputAmount]
|
||||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
: [o.inputAmount, o.outputAmount];
|
||||||
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
|
return {
|
||||||
return nativeOrderToReportEntry(
|
makerAmount,
|
||||||
collapsedFill.type,
|
takerAmount,
|
||||||
collapsedFill.fillData,
|
liquiditySource: o.source,
|
||||||
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
|
fillData: {}, // Does this matter?
|
||||||
comparisonPrice,
|
} as BridgeQuoteReportEntry;
|
||||||
quoteRequestor,
|
}
|
||||||
);
|
case FillQuoteTransformerOrderType.Limit:
|
||||||
} else {
|
case FillQuoteTransformerOrderType.Rfq: {
|
||||||
return dexSampleToReportSource(collapsedFill, marketOperation);
|
return nativeOrderToReportEntry(side, o, opts.comparisonPrice, opts.quoteRequestor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Multi-hop.
|
||||||
|
const firstHop = opts.hops[0];
|
||||||
|
const lastHop = opts.hops[opts.hops.length - 1];
|
||||||
|
const [makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
|
? [lastHop.outputAmount, firstHop.inputAmount]
|
||||||
|
: [firstHop.inputAmount, lastHop.outputAmount];
|
||||||
sourcesDelivered = [
|
sourcesDelivered = [
|
||||||
// tslint:disable-next-line: no-unnecessary-type-assertion
|
{
|
||||||
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
makerAmount,
|
||||||
|
takerAmount,
|
||||||
|
liquiditySource: ERC20BridgeSource.MultiHop,
|
||||||
|
fillData: {},
|
||||||
|
hopSources: opts.hops.map(h => h.orders.map(o => o.source)).flat(1),
|
||||||
|
} as MultiHopQuoteReportEntry,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const sourcesDeliveredIndexed = sourcesDelivered.map(
|
const sourcesDeliveredIndexed = sourcesDelivered.map(
|
||||||
@@ -263,94 +285,19 @@ export function generateExtendedQuoteReportSources(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _nativeDataToId(data: { signature: Signature }): string {
|
export function dexSampleToReportSource(
|
||||||
const { v, r, s } = data.signature;
|
side: MarketOperation,
|
||||||
return `${v}${r}${s}`;
|
sample: DexSample,
|
||||||
}
|
): BridgeQuoteReportEntry {
|
||||||
|
const [makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
/**
|
? [sample.output, sample.input]
|
||||||
* Generates a report sample for a DEX source
|
: [sample.input, sample.output];
|
||||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
return {
|
||||||
*/
|
makerAmount,
|
||||||
export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeQuoteReportEntry {
|
takerAmount,
|
||||||
const liquiditySource = ds.source;
|
liquiditySource: sample.source,
|
||||||
|
fillData: {}, // Does this matter?
|
||||||
if (liquiditySource === ERC20BridgeSource.Native) {
|
} as BridgeQuoteReportEntry;
|
||||||
throw new Error(`Unexpected liquidity source Native`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// input and output map to different values
|
|
||||||
// based on the market operation
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
|
||||||
return {
|
|
||||||
makerAmount: ds.input,
|
|
||||||
takerAmount: ds.output,
|
|
||||||
liquiditySource,
|
|
||||||
fillData: ds.fillData,
|
|
||||||
};
|
|
||||||
} else if (marketOperation === MarketOperation.Sell) {
|
|
||||||
return {
|
|
||||||
makerAmount: ds.output,
|
|
||||||
takerAmount: ds.input,
|
|
||||||
liquiditySource,
|
|
||||||
fillData: ds.fillData,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a DEX sample is the one that represents the whole amount requested by taker
|
|
||||||
* NOTE: this is used for the QuoteReport to filter samples
|
|
||||||
*/
|
|
||||||
function isDexSampleForTotalAmount(ds: DexSample, marketOperation: MarketOperation, amount: BigNumber): boolean {
|
|
||||||
// input and output map to different values
|
|
||||||
// based on the market operation
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
|
||||||
return ds.input === amount;
|
|
||||||
} else if (marketOperation === MarketOperation.Sell) {
|
|
||||||
return ds.output === amount;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a report sample for a MultiHop source
|
|
||||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
|
||||||
*/
|
|
||||||
export function multiHopSampleToReportSource(
|
|
||||||
ds: DexSample<MultiHopFillData>,
|
|
||||||
marketOperation: MarketOperation,
|
|
||||||
): MultiHopQuoteReportEntry {
|
|
||||||
const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData;
|
|
||||||
// input and output map to different values
|
|
||||||
// based on the market operation
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
|
||||||
return {
|
|
||||||
liquiditySource: ERC20BridgeSource.MultiHop,
|
|
||||||
makerAmount: ds.input,
|
|
||||||
takerAmount: ds.output,
|
|
||||||
fillData: ds.fillData,
|
|
||||||
hopSources: [firstHop.source, secondHop.source],
|
|
||||||
};
|
|
||||||
} else if (marketOperation === MarketOperation.Sell) {
|
|
||||||
return {
|
|
||||||
liquiditySource: ERC20BridgeSource.MultiHop,
|
|
||||||
makerAmount: ds.output,
|
|
||||||
takerAmount: ds.input,
|
|
||||||
fillData: ds.fillData,
|
|
||||||
hopSources: [firstHop.source, secondHop.source],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollapsedFill {
|
|
||||||
const { type } = cf;
|
|
||||||
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -358,44 +305,54 @@ function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollaps
|
|||||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||||
*/
|
*/
|
||||||
export function nativeOrderToReportEntry(
|
export function nativeOrderToReportEntry(
|
||||||
type: FillQuoteTransformerOrderType,
|
side: MarketOperation,
|
||||||
fillData: NativeLimitOrderFillData | NativeRfqOrderFillData,
|
order: OptimizedNativeOrder | NativeOrderWithFillableAmounts,
|
||||||
fillableAmount: BigNumber,
|
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
): NativeRfqOrderQuoteReportEntry | NativeLimitOrderQuoteReportEntry {
|
): NativeRfqOrderQuoteReportEntry | NativeLimitOrderQuoteReportEntry {
|
||||||
const nativeOrderBase = {
|
let nativeOrder;
|
||||||
makerAmount: fillData.order.makerAmount,
|
let makerAmount;
|
||||||
takerAmount: fillData.order.takerAmount,
|
let takerAmount;
|
||||||
fillableTakerAmount: fillableAmount,
|
let fillableTakerAmount;
|
||||||
};
|
let signature;
|
||||||
|
if (isOptimizedNativeOrder(order)) {
|
||||||
// if we find this is an rfqt order, label it as such and associate makerUri
|
nativeOrder = order.fillData.order;
|
||||||
const isRFQ = type === FillQuoteTransformerOrderType.Rfq;
|
fillableTakerAmount = order.fillData.fillableTakerAmount;
|
||||||
const rfqtMakerUri =
|
signature = order.fillData.signature;
|
||||||
isRFQ && quoteRequestor ? quoteRequestor.getMakerUriForSignature(fillData.signature) : undefined;
|
[makerAmount, takerAmount] = side === MarketOperation.Sell
|
||||||
|
? [order.outputAmount, order.outputAmount]
|
||||||
if (isRFQ) {
|
: [order.inputAmount, order.outputAmount];
|
||||||
const nativeOrder = fillData.order as RfqOrderFields;
|
|
||||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
|
||||||
return {
|
|
||||||
liquiditySource: ERC20BridgeSource.Native,
|
|
||||||
...nativeOrderBase,
|
|
||||||
isRFQ: true,
|
|
||||||
makerUri: rfqtMakerUri || '',
|
|
||||||
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
|
||||||
nativeOrder,
|
|
||||||
fillData,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
nativeOrder = order.order;
|
||||||
return {
|
fillableTakerAmount = order.fillableTakerAmount;
|
||||||
liquiditySource: ERC20BridgeSource.Native,
|
signature = order.signature;
|
||||||
...nativeOrderBase,
|
[makerAmount, takerAmount] = [nativeOrder.makerAmount, nativeOrder.takerAmount];
|
||||||
isRFQ: false,
|
|
||||||
fillData,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
const isRFQ = order.type === FillQuoteTransformerOrderType.Rfq;
|
||||||
|
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||||
|
const rfqtMakerUri =
|
||||||
|
isRFQ && quoteRequestor ? quoteRequestor.getMakerUriForSignature(signature) : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
makerAmount,
|
||||||
|
takerAmount,
|
||||||
|
isRFQ,
|
||||||
|
fillableTakerAmount,
|
||||||
|
liquiditySource: ERC20BridgeSource.Native,
|
||||||
|
fillData: {},
|
||||||
|
...(isRFQ
|
||||||
|
? {
|
||||||
|
makerUri: rfqtMakerUri,
|
||||||
|
nativeOrder,
|
||||||
|
...(comparisonPrice ? { comparisonPrice: comparisonPrice.toNumber() } : {}),
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
),
|
||||||
|
} as NativeRfqOrderQuoteReportEntry | NativeLimitOrderQuoteReportEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOptimizedNativeOrder(order: OptimizedNativeOrder | NativeOrderWithFillableAmounts): order is OptimizedNativeOrder {
|
||||||
|
return !!(order as any).fillData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
RfqmRequestOptions,
|
RfqmRequestOptions,
|
||||||
RfqPairType,
|
RfqPairType,
|
||||||
RfqRequestOpts,
|
RfqRequestOpts,
|
||||||
SignedNativeOrder,
|
SignedRfqOrder,
|
||||||
TypedMakerUrl,
|
TypedMakerUrl,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ export class QuoteRequestor {
|
|||||||
private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string },
|
private readonly _altRfqCreds?: { altRfqApiKey: string; altRfqProfile: string },
|
||||||
private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
|
private readonly _warningLogger: LogFunction = constants.DEFAULT_WARNING_LOGGER,
|
||||||
private readonly _infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER,
|
private readonly _infoLogger: LogFunction = constants.DEFAULT_INFO_LOGGER,
|
||||||
private readonly _expiryBufferMs: number = constants.DEFAULT_SWAP_QUOTER_OPTS.expiryBufferMs,
|
private readonly _expiryBufferMs: number = 120e3,
|
||||||
private readonly _metrics?: MetricsProxy,
|
private readonly _metrics?: MetricsProxy,
|
||||||
) {
|
) {
|
||||||
rfqMakerBlacklist.infoLogger = this._infoLogger;
|
rfqMakerBlacklist.infoLogger = this._infoLogger;
|
||||||
@@ -299,7 +299,7 @@ export class QuoteRequestor {
|
|||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
comparisonPrice: BigNumber | undefined,
|
comparisonPrice: BigNumber | undefined,
|
||||||
options: RfqmRequestOptions,
|
options: RfqmRequestOptions,
|
||||||
): Promise<SignedNativeOrder[]> {
|
): Promise<SignedRfqOrder[]> {
|
||||||
const _opts: RfqRequestOpts = {
|
const _opts: RfqRequestOpts = {
|
||||||
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||||
...options,
|
...options,
|
||||||
@@ -323,7 +323,7 @@ export class QuoteRequestor {
|
|||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
comparisonPrice: BigNumber | undefined,
|
comparisonPrice: BigNumber | undefined,
|
||||||
options: RfqRequestOpts,
|
options: RfqRequestOpts,
|
||||||
): Promise<SignedNativeOrder[]> {
|
): Promise<SignedRfqOrder[]> {
|
||||||
const _opts: RfqRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
const _opts: RfqRequestOpts = { ...constants.DEFAULT_RFQT_REQUEST_OPTS, ...options };
|
||||||
if (!_opts.txOrigin || [undefined, '', '0x', NULL_ADDRESS].includes(_opts.txOrigin)) {
|
if (!_opts.txOrigin || [undefined, '', '0x', NULL_ADDRESS].includes(_opts.txOrigin)) {
|
||||||
throw new Error('RFQ-T firm quotes require the presence of a tx origin');
|
throw new Error('RFQ-T firm quotes require the presence of a tx origin');
|
||||||
@@ -649,7 +649,7 @@ export class QuoteRequestor {
|
|||||||
comparisonPrice: BigNumber | undefined,
|
comparisonPrice: BigNumber | undefined,
|
||||||
options: RfqRequestOpts,
|
options: RfqRequestOpts,
|
||||||
assetOfferings: RfqMakerAssetOfferings,
|
assetOfferings: RfqMakerAssetOfferings,
|
||||||
): Promise<SignedNativeOrder[]> {
|
): Promise<SignedRfqOrder[]> {
|
||||||
const quotesRaw = await this._getQuotesAsync<V4RFQFirmQuote>(
|
const quotesRaw = await this._getQuotesAsync<V4RFQFirmQuote>(
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
@@ -725,7 +725,7 @@ export class QuoteRequestor {
|
|||||||
// Save the maker URI for later and return just the order
|
// Save the maker URI for later and return just the order
|
||||||
const rfqQuotes = validQuotes.map(result => {
|
const rfqQuotes = validQuotes.map(result => {
|
||||||
const { signature, ...rest } = result.response;
|
const { signature, ...rest } = result.response;
|
||||||
const order: SignedNativeOrder = {
|
const order: SignedRfqOrder = {
|
||||||
order: {
|
order: {
|
||||||
...rest,
|
...rest,
|
||||||
makerAmount: new BigNumber(result.response.makerAmount),
|
makerAmount: new BigNumber(result.response.makerAmount),
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
import { FillQuoteTransformerOrderType, LimitOrderFields } from '@0x/protocol-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import { MarketOperation } from '../types';
|
import { MarketOperation, SwapQuoteLimitOrder, SwapQuoteOrder } from '../types';
|
||||||
|
|
||||||
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
|
||||||
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
|
||||||
|
|
||||||
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
||||||
const { ROUND_DOWN, ROUND_UP } = BigNumber;
|
const { ROUND_DOWN, ROUND_UP } = BigNumber;
|
||||||
@@ -64,7 +61,7 @@ const EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface QuoteFillInfo {
|
export interface QuoteFillInfo {
|
||||||
orders: OptimizedMarketOrder[];
|
orders: SwapQuoteOrder[];
|
||||||
fillAmount: BigNumber;
|
fillAmount: BigNumber;
|
||||||
gasPrice: BigNumber;
|
gasPrice: BigNumber;
|
||||||
side: MarketOperation;
|
side: MarketOperation;
|
||||||
@@ -72,19 +69,17 @@ export interface QuoteFillInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QuoteFillInfoOpts {
|
export interface QuoteFillInfoOpts {
|
||||||
gasSchedule: FeeSchedule;
|
|
||||||
protocolFeeMultiplier: BigNumber;
|
protocolFeeMultiplier: BigNumber;
|
||||||
slippage: number;
|
slippage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SIMULATED_FILL_QUOTE_INFO_OPTS: QuoteFillInfoOpts = {
|
const DEFAULT_SIMULATED_FILL_QUOTE_INFO_OPTS: QuoteFillInfoOpts = {
|
||||||
gasSchedule: {},
|
|
||||||
protocolFeeMultiplier: PROTOCOL_FEE_MULTIPLIER,
|
protocolFeeMultiplier: PROTOCOL_FEE_MULTIPLIER,
|
||||||
slippage: 0,
|
slippage: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface QuoteFillOrderCall {
|
export interface QuoteFillOrderCall {
|
||||||
order: OptimizedMarketOrder;
|
order: SwapQuoteOrder;
|
||||||
// Total input amount defined in the order.
|
// Total input amount defined in the order.
|
||||||
totalOrderInput: BigNumber;
|
totalOrderInput: BigNumber;
|
||||||
// Total output amount defined in the order.
|
// Total output amount defined in the order.
|
||||||
@@ -108,7 +103,6 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
|||||||
createBestCaseFillOrderCalls(quoteInfo),
|
createBestCaseFillOrderCalls(quoteInfo),
|
||||||
quoteInfo.fillAmount,
|
quoteInfo.fillAmount,
|
||||||
protocolFeePerFillOrder,
|
protocolFeePerFillOrder,
|
||||||
opts.gasSchedule,
|
|
||||||
);
|
);
|
||||||
return fromIntermediateQuoteFillResult(result, quoteInfo);
|
return fromIntermediateQuoteFillResult(result, quoteInfo);
|
||||||
}
|
}
|
||||||
@@ -122,9 +116,9 @@ export function simulateWorstCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
|||||||
const protocolFeePerFillOrder = quoteInfo.gasPrice.times(opts.protocolFeeMultiplier);
|
const protocolFeePerFillOrder = quoteInfo.gasPrice.times(opts.protocolFeeMultiplier);
|
||||||
const bestCase = createBestCaseFillOrderCalls(quoteInfo);
|
const bestCase = createBestCaseFillOrderCalls(quoteInfo);
|
||||||
const result = {
|
const result = {
|
||||||
...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder, opts.gasSchedule),
|
...fillQuoteOrders(bestCase, quoteInfo.fillAmount, protocolFeePerFillOrder),
|
||||||
// Worst case gas and protocol fee is hitting all orders.
|
// Worst case gas and protocol fee is hitting all orders.
|
||||||
gas: getTotalGasUsedByFills(quoteInfo.orders, opts.gasSchedule),
|
gas: getTotalGasUsedByFills(quoteInfo.orders),
|
||||||
protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.filter(o => hasProtocolFee(o)).length),
|
protocolFee: protocolFeePerFillOrder.times(quoteInfo.orders.filter(o => hasProtocolFee(o)).length),
|
||||||
};
|
};
|
||||||
// Adjust the output by 1-slippage for the worst case if it is a sell
|
// Adjust the output by 1-slippage for the worst case if it is a sell
|
||||||
@@ -140,7 +134,6 @@ export function fillQuoteOrders(
|
|||||||
fillOrders: QuoteFillOrderCall[],
|
fillOrders: QuoteFillOrderCall[],
|
||||||
inputAmount: BigNumber,
|
inputAmount: BigNumber,
|
||||||
protocolFeePerFillOrder: BigNumber,
|
protocolFeePerFillOrder: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
|
||||||
): IntermediateQuoteFillResult {
|
): IntermediateQuoteFillResult {
|
||||||
const result: IntermediateQuoteFillResult = {
|
const result: IntermediateQuoteFillResult = {
|
||||||
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
||||||
@@ -151,39 +144,27 @@ export function fillQuoteOrders(
|
|||||||
if (remainingInput.lte(0)) {
|
if (remainingInput.lte(0)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (const fill of fo.order.fills) {
|
const { source } = fo.order;
|
||||||
if (remainingInput.lte(0)) {
|
result.gas += fo.order.gasCost;
|
||||||
break;
|
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
||||||
}
|
|
||||||
const { source, fillData } = fill;
|
|
||||||
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
|
||||||
result.gas += new BigNumber(gas).toNumber();
|
|
||||||
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
|
||||||
|
|
||||||
// Actual rates are rarely linear, so fill subfills individually to
|
const filledInput = solveForInputFillAmount(
|
||||||
// get a better approximation of fill size.
|
remainingInput,
|
||||||
for (const subFill of fill.subFills) {
|
fo.totalOrderInput,
|
||||||
if (remainingInput.lte(0)) {
|
fo.totalOrderInput,
|
||||||
break;
|
fo.totalOrderInputFee,
|
||||||
}
|
);
|
||||||
const filledInput = solveForInputFillAmount(
|
const filledOutput = fo.totalOrderOutput.times(filledInput.div(fo.totalOrderInput));
|
||||||
remainingInput,
|
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
||||||
subFill.input,
|
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
||||||
fo.totalOrderInput,
|
|
||||||
fo.totalOrderInputFee,
|
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
|
||||||
);
|
result.input = result.input.plus(filledInput);
|
||||||
const filledOutput = subFill.output.times(filledInput.div(subFill.input));
|
result.output = result.output.plus(filledOutput);
|
||||||
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
result.inputFee = result.inputFee.plus(filledInputFee);
|
||||||
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
result.outputFee = result.outputFee.plus(filledOutputFee);
|
||||||
|
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
||||||
|
|
||||||
result.inputBySource[source] = result.inputBySource[source].plus(filledInput);
|
|
||||||
result.input = result.input.plus(filledInput);
|
|
||||||
result.output = result.output.plus(filledOutput);
|
|
||||||
result.inputFee = result.inputFee.plus(filledInputFee);
|
|
||||||
result.outputFee = result.outputFee.plus(filledOutputFee);
|
|
||||||
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// NOTE: V4 Limit orders have Protocol fees
|
// NOTE: V4 Limit orders have Protocol fees
|
||||||
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
|
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
|
||||||
result.protocolFee = result.protocolFee.plus(protocolFee);
|
result.protocolFee = result.protocolFee.plus(protocolFee);
|
||||||
@@ -191,7 +172,7 @@ export function fillQuoteOrders(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasProtocolFee(o: OptimizedMarketOrder): boolean {
|
function hasProtocolFee(o: SwapQuoteOrder): boolean {
|
||||||
return o.type === FillQuoteTransformerOrderType.Limit;
|
return o.type === FillQuoteTransformerOrderType.Limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,13 +215,7 @@ function createBestCaseFillOrderCalls(quoteInfo: QuoteFillInfo): QuoteFillOrderC
|
|||||||
? {
|
? {
|
||||||
totalOrderInput: o.takerAmount,
|
totalOrderInput: o.takerAmount,
|
||||||
totalOrderOutput: o.makerAmount,
|
totalOrderOutput: o.makerAmount,
|
||||||
totalOrderInputFee:
|
totalOrderInputFee: ZERO_AMOUNT, // Limit orders not supported atm
|
||||||
o.type === FillQuoteTransformerOrderType.Limit
|
|
||||||
? getNativeAdjustedTakerFeeAmount(
|
|
||||||
(o.fillData as NativeLimitOrderFillData).order,
|
|
||||||
o.takerAmount,
|
|
||||||
)
|
|
||||||
: ZERO_AMOUNT,
|
|
||||||
totalOrderOutputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (sell output)
|
totalOrderOutputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (sell output)
|
||||||
}
|
}
|
||||||
: // Buy
|
: // Buy
|
||||||
@@ -248,13 +223,7 @@ function createBestCaseFillOrderCalls(quoteInfo: QuoteFillInfo): QuoteFillOrderC
|
|||||||
totalOrderInput: o.makerAmount,
|
totalOrderInput: o.makerAmount,
|
||||||
totalOrderOutput: o.takerAmount,
|
totalOrderOutput: o.takerAmount,
|
||||||
totalOrderInputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (buy input)
|
totalOrderInputFee: ZERO_AMOUNT, // makerToken fees are not supported in v4 (buy input)
|
||||||
totalOrderOutputFee:
|
totalOrderOutputFee: ZERO_AMOUNT, // Limit orders not supported atm
|
||||||
o.type === FillQuoteTransformerOrderType.Limit
|
|
||||||
? getNativeAdjustedTakerFeeAmount(
|
|
||||||
(o.fillData as NativeLimitOrderFillData).order,
|
|
||||||
o.takerAmount,
|
|
||||||
)
|
|
||||||
: ZERO_AMOUNT,
|
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -314,11 +283,10 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number {
|
function getTotalGasUsedByFills(fills: SwapQuoteOrder[]): number {
|
||||||
let gasUsed = 0;
|
let gasUsed = 0;
|
||||||
for (const f of fills) {
|
for (const f of fills) {
|
||||||
const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData);
|
gasUsed += f.gasCost;
|
||||||
gasUsed += new BigNumber(fee).toNumber();
|
|
||||||
}
|
}
|
||||||
return gasUsed;
|
return gasUsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const rfqtMocker = {
|
|||||||
afterResponseCallback: () => Promise<void>,
|
afterResponseCallback: () => Promise<void>,
|
||||||
axiosClient: AxiosInstance = axios,
|
axiosClient: AxiosInstance = axios,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const mockedAxios = new AxiosMockAdapter(axiosClient);
|
const mockedAxios = new AxiosMockAdapter(axiosClient as any); // TODO (lawrence): why ts?
|
||||||
try {
|
try {
|
||||||
// Mock out RFQT responses
|
// Mock out RFQT responses
|
||||||
for (const mockedResponse of mockedResponses) {
|
for (const mockedResponse of mockedResponses) {
|
||||||
|
|||||||
@@ -1,100 +1,21 @@
|
|||||||
import { CommonOrderFields, FillQuoteTransformerOrderType, LimitOrderFields } from '@0x/protocol-utils';
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
||||||
|
|
||||||
import { constants } from '../constants';
|
// TODO(kimpers): Consolidate this implementation with the one in @0x/token-metadata
|
||||||
import { NativeOrderFillableAmountFields, SignedNativeOrder } from '../types';
|
export function valueByChainId<T>(rest: Partial<{ [key in ChainId]: T }>, defaultValue: T): { [key in ChainId]: T } {
|
||||||
|
// TODO I don't like this but iterating through enums is weird
|
||||||
import { ZERO_AMOUNT } from './market_operation_utils/constants';
|
|
||||||
|
|
||||||
// tslint:disable: no-unnecessary-type-assertion completed-docs
|
|
||||||
|
|
||||||
export function numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber {
|
|
||||||
return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).multipliedBy(percentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAdjustedTakerAmountFromFees<T extends LimitOrderFields>(order: T): BigNumber {
|
|
||||||
return order.takerAmount.plus(order.takerTokenFeeAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an amount of taker asset, calculate the the amount of maker asset
|
|
||||||
* @param order The order
|
|
||||||
* @param makerFillAmount the amount of taker asset
|
|
||||||
*/
|
|
||||||
export function getNativeAdjustedMakerFillAmount(order: CommonOrderFields, takerFillAmount: BigNumber): BigNumber {
|
|
||||||
// Round down because exchange rate favors Maker
|
|
||||||
const makerFillAmount = takerFillAmount
|
|
||||||
.multipliedBy(order.makerAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_FLOOR);
|
|
||||||
return makerFillAmount;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Given an amount of maker asset, calculate the equivalent amount in taker asset
|
|
||||||
* @param order The order
|
|
||||||
* @param makerFillAmount the amount of maker asset
|
|
||||||
*/
|
|
||||||
export function getNativeAdjustedTakerFillAmount(order: CommonOrderFields, makerFillAmount: BigNumber): BigNumber {
|
|
||||||
// Round up because exchange rate favors Maker
|
|
||||||
const takerFillAmount = makerFillAmount
|
|
||||||
.multipliedBy(order.takerAmount)
|
|
||||||
.div(order.makerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_CEIL);
|
|
||||||
return takerFillAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an amount of taker asset, calculate the fee amount required for the taker
|
|
||||||
* @param order The order
|
|
||||||
* @param takerFillAmount the amount of taker asset
|
|
||||||
*/
|
|
||||||
export function getNativeAdjustedTakerFeeAmount(order: LimitOrderFields, takerFillAmount: BigNumber): BigNumber {
|
|
||||||
// Round down because Taker fee rate favors Taker
|
|
||||||
const takerFeeAmount = takerFillAmount
|
|
||||||
.multipliedBy(order.takerTokenFeeAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_FLOOR);
|
|
||||||
return takerFeeAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EMPTY_FILLABLE_AMOUNTS: NativeOrderFillableAmountFields = {
|
|
||||||
fillableMakerAmount: ZERO_AMOUNT,
|
|
||||||
fillableTakerAmount: ZERO_AMOUNT,
|
|
||||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getNativeAdjustedFillableAmountsFromTakerAmount(
|
|
||||||
order: SignedNativeOrder,
|
|
||||||
takerFillableAmount: BigNumber,
|
|
||||||
): NativeOrderFillableAmountFields {
|
|
||||||
if (takerFillableAmount.isZero()) {
|
|
||||||
return EMPTY_FILLABLE_AMOUNTS;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
fillableTakerAmount: takerFillableAmount,
|
[ChainId.Mainnet]: defaultValue,
|
||||||
fillableMakerAmount: getNativeAdjustedMakerFillAmount(order.order, takerFillableAmount),
|
[ChainId.Ropsten]: defaultValue,
|
||||||
fillableTakerFeeAmount:
|
[ChainId.Rinkeby]: defaultValue,
|
||||||
order.type === FillQuoteTransformerOrderType.Limit
|
[ChainId.Kovan]: defaultValue,
|
||||||
? getNativeAdjustedTakerFeeAmount(order.order as LimitOrderFields, takerFillableAmount)
|
[ChainId.Ganache]: defaultValue,
|
||||||
: ZERO_AMOUNT,
|
[ChainId.BSC]: defaultValue,
|
||||||
};
|
[ChainId.Polygon]: defaultValue,
|
||||||
}
|
[ChainId.PolygonMumbai]: defaultValue,
|
||||||
|
[ChainId.Avalanche]: defaultValue,
|
||||||
export function getNativeAdjustedFillableAmountsFromMakerAmount(
|
[ChainId.Fantom]: defaultValue,
|
||||||
order: SignedNativeOrder,
|
[ChainId.Celo]: defaultValue,
|
||||||
makerFillableAmount: BigNumber,
|
[ChainId.Optimism]: defaultValue,
|
||||||
): NativeOrderFillableAmountFields {
|
...(rest || {}),
|
||||||
if (makerFillableAmount.isZero()) {
|
|
||||||
return EMPTY_FILLABLE_AMOUNTS;
|
|
||||||
}
|
|
||||||
const takerFillableAmount = getNativeAdjustedTakerFillAmount(order.order, makerFillableAmount);
|
|
||||||
return {
|
|
||||||
fillableMakerAmount: makerFillableAmount,
|
|
||||||
fillableTakerAmount: takerFillableAmount,
|
|
||||||
fillableTakerFeeAmount:
|
|
||||||
order.type === FillQuoteTransformerOrderType.Limit
|
|
||||||
? getNativeAdjustedTakerFeeAmount(order.order as LimitOrderFields, takerFillableAmount)
|
|
||||||
: ZERO_AMOUNT,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,4 @@
|
|||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
export * from '../generated-wrappers/balance_checker';
|
export * from '../generated-wrappers/balance_checker';
|
||||||
export * from '../generated-wrappers/erc20_bridge_sampler';
|
|
||||||
export * from '../generated-wrappers/fake_taker';
|
export * from '../generated-wrappers/fake_taker';
|
||||||
|
|||||||
@@ -5,89 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
|
||||||
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
import * as BalanceChecker from '../test/generated-artifacts/BalanceChecker.json';
|
||||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
|
|
||||||
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
|
|
||||||
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
|
|
||||||
import * as CompoundSampler from '../test/generated-artifacts/CompoundSampler.json';
|
|
||||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
|
||||||
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
|
|
||||||
import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
|
|
||||||
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
|
|
||||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
|
||||||
import * as FakeTaker from '../test/generated-artifacts/FakeTaker.json';
|
import * as FakeTaker from '../test/generated-artifacts/FakeTaker.json';
|
||||||
import * as IBalancer from '../test/generated-artifacts/IBalancer.json';
|
|
||||||
import * as IBancor from '../test/generated-artifacts/IBancor.json';
|
|
||||||
import * as ICurve from '../test/generated-artifacts/ICurve.json';
|
|
||||||
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
|
|
||||||
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
|
|
||||||
import * as IMStable from '../test/generated-artifacts/IMStable.json';
|
|
||||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
|
||||||
import * as IShell from '../test/generated-artifacts/IShell.json';
|
|
||||||
import * as ISmoothy from '../test/generated-artifacts/ISmoothy.json';
|
|
||||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
|
||||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
|
||||||
import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json';
|
|
||||||
import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json';
|
|
||||||
import * as LidoSampler from '../test/generated-artifacts/LidoSampler.json';
|
|
||||||
import * as LiquidityProviderSampler from '../test/generated-artifacts/LiquidityProviderSampler.json';
|
|
||||||
import * as MakerPSMSampler from '../test/generated-artifacts/MakerPSMSampler.json';
|
|
||||||
import * as MooniswapSampler from '../test/generated-artifacts/MooniswapSampler.json';
|
|
||||||
import * as MStableSampler from '../test/generated-artifacts/MStableSampler.json';
|
|
||||||
import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json';
|
|
||||||
import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json';
|
|
||||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
|
||||||
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
|
|
||||||
import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json';
|
|
||||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
|
||||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
|
||||||
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
|
||||||
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
|
||||||
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
|
|
||||||
import * as UniswapV3Sampler from '../test/generated-artifacts/UniswapV3Sampler.json';
|
|
||||||
import * as UtilitySampler from '../test/generated-artifacts/UtilitySampler.json';
|
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
|
||||||
BalanceChecker: BalanceChecker as ContractArtifact,
|
BalanceChecker: BalanceChecker as ContractArtifact,
|
||||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
|
||||||
BalancerV2Sampler: BalancerV2Sampler as ContractArtifact,
|
|
||||||
BancorSampler: BancorSampler as ContractArtifact,
|
|
||||||
CompoundSampler: CompoundSampler as ContractArtifact,
|
|
||||||
CurveSampler: CurveSampler as ContractArtifact,
|
|
||||||
DODOSampler: DODOSampler as ContractArtifact,
|
|
||||||
DODOV2Sampler: DODOV2Sampler as ContractArtifact,
|
|
||||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
|
||||||
FakeTaker: FakeTaker as ContractArtifact,
|
FakeTaker: FakeTaker as ContractArtifact,
|
||||||
KyberDmmSampler: KyberDmmSampler as ContractArtifact,
|
|
||||||
KyberSampler: KyberSampler as ContractArtifact,
|
|
||||||
LidoSampler: LidoSampler as ContractArtifact,
|
|
||||||
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
|
|
||||||
MStableSampler: MStableSampler as ContractArtifact,
|
|
||||||
MakerPSMSampler: MakerPSMSampler as ContractArtifact,
|
|
||||||
MooniswapSampler: MooniswapSampler as ContractArtifact,
|
|
||||||
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
|
|
||||||
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
|
|
||||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
|
||||||
ShellSampler: ShellSampler as ContractArtifact,
|
|
||||||
SmoothySampler: SmoothySampler as ContractArtifact,
|
|
||||||
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
|
||||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
|
||||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
|
||||||
UniswapV3Sampler: UniswapV3Sampler as ContractArtifact,
|
|
||||||
UtilitySampler: UtilitySampler as ContractArtifact,
|
|
||||||
IBalancer: IBalancer as ContractArtifact,
|
|
||||||
IBancor: IBancor as ContractArtifact,
|
|
||||||
ICurve: ICurve as ContractArtifact,
|
|
||||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
|
||||||
IMStable: IMStable as ContractArtifact,
|
|
||||||
IMooniswap: IMooniswap as ContractArtifact,
|
|
||||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
|
||||||
IShell: IShell as ContractArtifact,
|
|
||||||
ISmoothy: ISmoothy as ContractArtifact,
|
|
||||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
|
||||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
|
||||||
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
|
|
||||||
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
|
|
||||||
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,45 +3,5 @@
|
|||||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
export * from '../test/generated-wrappers/approximate_buys';
|
|
||||||
export * from '../test/generated-wrappers/balance_checker';
|
export * from '../test/generated-wrappers/balance_checker';
|
||||||
export * from '../test/generated-wrappers/balancer_sampler';
|
|
||||||
export * from '../test/generated-wrappers/balancer_v2_sampler';
|
|
||||||
export * from '../test/generated-wrappers/bancor_sampler';
|
|
||||||
export * from '../test/generated-wrappers/compound_sampler';
|
|
||||||
export * from '../test/generated-wrappers/curve_sampler';
|
|
||||||
export * from '../test/generated-wrappers/d_o_d_o_sampler';
|
|
||||||
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';
|
|
||||||
export * from '../test/generated-wrappers/dummy_liquidity_provider';
|
|
||||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
|
||||||
export * from '../test/generated-wrappers/fake_taker';
|
export * from '../test/generated-wrappers/fake_taker';
|
||||||
export * from '../test/generated-wrappers/i_balancer';
|
|
||||||
export * from '../test/generated-wrappers/i_bancor';
|
|
||||||
export * from '../test/generated-wrappers/i_curve';
|
|
||||||
export * from '../test/generated-wrappers/i_kyber_network';
|
|
||||||
export * from '../test/generated-wrappers/i_m_stable';
|
|
||||||
export * from '../test/generated-wrappers/i_mooniswap';
|
|
||||||
export * from '../test/generated-wrappers/i_multi_bridge';
|
|
||||||
export * from '../test/generated-wrappers/i_shell';
|
|
||||||
export * from '../test/generated-wrappers/i_smoothy';
|
|
||||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
|
||||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
|
||||||
export * from '../test/generated-wrappers/kyber_dmm_sampler';
|
|
||||||
export * from '../test/generated-wrappers/kyber_sampler';
|
|
||||||
export * from '../test/generated-wrappers/lido_sampler';
|
|
||||||
export * from '../test/generated-wrappers/liquidity_provider_sampler';
|
|
||||||
export * from '../test/generated-wrappers/m_stable_sampler';
|
|
||||||
export * from '../test/generated-wrappers/maker_p_s_m_sampler';
|
|
||||||
export * from '../test/generated-wrappers/mooniswap_sampler';
|
|
||||||
export * from '../test/generated-wrappers/multi_bridge_sampler';
|
|
||||||
export * from '../test/generated-wrappers/native_order_sampler';
|
|
||||||
export * from '../test/generated-wrappers/sampler_utils';
|
|
||||||
export * from '../test/generated-wrappers/shell_sampler';
|
|
||||||
export * from '../test/generated-wrappers/smoothy_sampler';
|
|
||||||
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
|
|
||||||
export * from '../test/generated-wrappers/test_native_order_sampler';
|
|
||||||
export * from '../test/generated-wrappers/two_hop_sampler';
|
|
||||||
export * from '../test/generated-wrappers/uniswap_sampler';
|
|
||||||
export * from '../test/generated-wrappers/uniswap_v2_sampler';
|
|
||||||
export * from '../test/generated-wrappers/uniswap_v3_sampler';
|
|
||||||
export * from '../test/generated-wrappers/utility_sampler';
|
|
||||||
|
|||||||
@@ -1,52 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig",
|
"extends": "../../tsconfig",
|
||||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true, "lib": ["es2019"] },
|
||||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
"include": ["./src/**/*", "./generated-wrappers/**/*"],
|
||||||
"files": [
|
"files": [
|
||||||
"generated-artifacts/BalanceChecker.json",
|
"generated-artifacts/BalanceChecker.json",
|
||||||
"generated-artifacts/ERC20BridgeSampler.json",
|
|
||||||
"generated-artifacts/FakeTaker.json",
|
"generated-artifacts/FakeTaker.json",
|
||||||
"test/generated-artifacts/ApproximateBuys.json",
|
|
||||||
"test/generated-artifacts/BalanceChecker.json",
|
"test/generated-artifacts/BalanceChecker.json",
|
||||||
"test/generated-artifacts/BalancerSampler.json",
|
"test/generated-artifacts/FakeTaker.json"
|
||||||
"test/generated-artifacts/BalancerV2Sampler.json",
|
|
||||||
"test/generated-artifacts/BancorSampler.json",
|
|
||||||
"test/generated-artifacts/CompoundSampler.json",
|
|
||||||
"test/generated-artifacts/CurveSampler.json",
|
|
||||||
"test/generated-artifacts/DODOSampler.json",
|
|
||||||
"test/generated-artifacts/DODOV2Sampler.json",
|
|
||||||
"test/generated-artifacts/DummyLiquidityProvider.json",
|
|
||||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
|
||||||
"test/generated-artifacts/FakeTaker.json",
|
|
||||||
"test/generated-artifacts/IBalancer.json",
|
|
||||||
"test/generated-artifacts/IBancor.json",
|
|
||||||
"test/generated-artifacts/ICurve.json",
|
|
||||||
"test/generated-artifacts/IKyberNetwork.json",
|
|
||||||
"test/generated-artifacts/IMStable.json",
|
|
||||||
"test/generated-artifacts/IMooniswap.json",
|
|
||||||
"test/generated-artifacts/IMultiBridge.json",
|
|
||||||
"test/generated-artifacts/IShell.json",
|
|
||||||
"test/generated-artifacts/ISmoothy.json",
|
|
||||||
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
|
||||||
"test/generated-artifacts/IUniswapV2Router01.json",
|
|
||||||
"test/generated-artifacts/KyberDmmSampler.json",
|
|
||||||
"test/generated-artifacts/KyberSampler.json",
|
|
||||||
"test/generated-artifacts/LidoSampler.json",
|
|
||||||
"test/generated-artifacts/LiquidityProviderSampler.json",
|
|
||||||
"test/generated-artifacts/MStableSampler.json",
|
|
||||||
"test/generated-artifacts/MakerPSMSampler.json",
|
|
||||||
"test/generated-artifacts/MooniswapSampler.json",
|
|
||||||
"test/generated-artifacts/MultiBridgeSampler.json",
|
|
||||||
"test/generated-artifacts/NativeOrderSampler.json",
|
|
||||||
"test/generated-artifacts/SamplerUtils.json",
|
|
||||||
"test/generated-artifacts/ShellSampler.json",
|
|
||||||
"test/generated-artifacts/SmoothySampler.json",
|
|
||||||
"test/generated-artifacts/TestERC20BridgeSampler.json",
|
|
||||||
"test/generated-artifacts/TestNativeOrderSampler.json",
|
|
||||||
"test/generated-artifacts/TwoHopSampler.json",
|
|
||||||
"test/generated-artifacts/UniswapSampler.json",
|
|
||||||
"test/generated-artifacts/UniswapV2Sampler.json",
|
|
||||||
"test/generated-artifacts/UniswapV3Sampler.json",
|
|
||||||
"test/generated-artifacts/UtilitySampler.json"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
"extends": ["@0x/tslint-config"],
|
"extends": ["@0x/tslint-config"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"max-file-line-count": false,
|
"max-file-line-count": false,
|
||||||
"binary-expression-operand-order": false
|
"binary-expression-operand-order": false,
|
||||||
|
"no-bitwise": false,
|
||||||
|
"completed-docs": false
|
||||||
},
|
},
|
||||||
"linterOptions": {
|
"linterOptions": {
|
||||||
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]
|
"exclude": ["src/artifacts.ts", "test/artifacts.ts"]
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/packages/contract-artifacts",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/packages/contract-artifacts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"chai": "^4.0.1",
|
"chai": "^4.0.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
|
|||||||
@@ -55,14 +55,14 @@
|
|||||||
"typescript": "4.2.2"
|
"typescript": "4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^3.0.31",
|
"@0x/assert": "^3.0.32",
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/json-schemas": "^6.4.1",
|
"@0x/json-schemas": "^6.4.2",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethers": "~4.0.4"
|
"ethers": "~4.0.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -48,10 +48,10 @@
|
|||||||
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@types/yargs": "^11.0.0",
|
"@types/yargs": "^11.0.0",
|
||||||
"chai": "^4.0.1",
|
"chai": "^4.0.1",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"yargs": "^10.0.3"
|
"yargs": "^10.0.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/base-contract": "^6.4.5",
|
"@0x/base-contract": "^6.4.6",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||||
"@0x/contracts-coordinator": "^3.1.38",
|
"@0x/contracts-coordinator": "^3.1.38",
|
||||||
@@ -82,14 +82,14 @@
|
|||||||
"@0x/contracts-staking": "^2.0.45",
|
"@0x/contracts-staking": "^2.0.45",
|
||||||
"@0x/contracts-utils": "^4.8.7",
|
"@0x/contracts-utils": "^4.8.7",
|
||||||
"@0x/contracts-zero-ex": "^0.31.0",
|
"@0x/contracts-zero-ex": "^0.31.0",
|
||||||
"@0x/sol-compiler": "^4.7.8",
|
"@0x/sol-compiler": "^4.7.9",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"@ledgerhq/hw-app-eth": "^4.3.0",
|
"@ledgerhq/hw-app-eth": "^4.3.0",
|
||||||
"@types/web3-provider-engine": "^14.0.0",
|
"@types/web3-provider-engine": "^14.0.0",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"ethereumjs-util": "^7.1.0",
|
"ethereumjs-util": "^7.1.0",
|
||||||
"ethers": "~4.0.4",
|
"ethers": "~4.0.4",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
|
|||||||
@@ -41,17 +41,17 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/0xProject/protocol/tree/main/packages/protocol-utils",
|
"homepage": "https://github.com/0xProject/protocol/tree/main/packages/protocol-utils",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/dev-utils": "^4.2.11",
|
"@0x/dev-utils": "^4.2.12",
|
||||||
"@0x/ts-doc-gen": "^0.0.28",
|
"@0x/ts-doc-gen": "^0.0.28",
|
||||||
"@0x/tslint-config": "^4.1.4",
|
"@0x/tslint-config": "^4.1.4",
|
||||||
"@0x/types": "^3.3.4",
|
"@0x/types": "^3.3.5",
|
||||||
"@0x/typescript-typings": "^5.2.1",
|
"@0x/typescript-typings": "^5.2.2",
|
||||||
"@types/bn.js": "^4.11.0",
|
"@types/bn.js": "^4.11.0",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "12.12.54",
|
"@types/node": "12.12.54",
|
||||||
"@types/web3-provider-engine": "^14.0.0",
|
"@types/web3-provider-engine": "^14.0.0",
|
||||||
"ethereum-types": "^3.6.0",
|
"ethereum-types": "^3.6.1",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.0",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"shx": "^0.2.2",
|
"shx": "^0.2.2",
|
||||||
@@ -62,13 +62,13 @@
|
|||||||
"web3-provider-engine": "14.0.6"
|
"web3-provider-engine": "14.0.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^3.0.31",
|
"@0x/assert": "^3.0.32",
|
||||||
"@0x/contract-addresses": "^6.11.0",
|
"@0x/contract-addresses": "^6.11.0",
|
||||||
"@0x/contract-wrappers": "^13.19.0",
|
"@0x/contract-wrappers": "^13.19.0",
|
||||||
"@0x/json-schemas": "^6.4.1",
|
"@0x/json-schemas": "^6.4.2",
|
||||||
"@0x/subproviders": "^6.6.2",
|
"@0x/subproviders": "^6.6.3",
|
||||||
"@0x/utils": "^6.5.0",
|
"@0x/utils": "^6.5.1",
|
||||||
"@0x/web3-wrapper": "^7.6.2",
|
"@0x/web3-wrapper": "^7.6.3",
|
||||||
"chai": "^4.0.1",
|
"chai": "^4.0.1",
|
||||||
"ethereumjs-util": "^7.0.10",
|
"ethereumjs-util": "^7.0.10",
|
||||||
"ethers": "~4.0.4",
|
"ethers": "~4.0.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user