Compare commits
8 Commits
@0x/asset-
...
feat/as/sa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da240653f4 | ||
|
|
22ec626870 | ||
|
|
2f60eb1c79 | ||
|
|
313420473a | ||
|
|
89a9424ae1 | ||
|
|
eabca7a2ee | ||
|
|
a5912c293e | ||
|
|
83cae575fa |
@@ -1,40 +1,4 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "3.3.25",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "3.3.24",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "3.3.23",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "3.3.22",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "3.3.21",
|
||||
|
||||
@@ -5,22 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.3.25 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.24 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.23 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.22 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.3.21 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-erc20",
|
||||
"version": "3.3.25",
|
||||
"version": "3.3.21",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -53,8 +53,8 @@
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-utils": "^4.8.6",
|
||||
"@0x/contracts-test-utils": "^5.4.12",
|
||||
"@0x/contracts-utils": "^4.8.2",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "5.4.16",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "5.4.15",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "5.4.14",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "5.4.13",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "5.4.12",
|
||||
|
||||
@@ -5,22 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v5.4.16 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.15 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.14 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.13 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v5.4.12 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-test-utils",
|
||||
"version": "5.4.16",
|
||||
"version": "5.4.12",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -44,7 +44,7 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.29",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contract-addresses": "^6.8.0",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/json-schemas": "^6.3.0",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "1.4.8",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "1.4.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "1.4.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "1.4.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "1.4.4",
|
||||
|
||||
@@ -5,22 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.4.8 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.7 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.6 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.5 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.4.4 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-treasury",
|
||||
"version": "1.4.8",
|
||||
"version": "1.4.4",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -47,12 +47,12 @@
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/treasury",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contract-addresses": "^6.8.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.19",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contracts-erc20": "^3.3.21",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-staking": "^2.0.45",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-test-utils": "^5.4.12",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.4",
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/protocol-utils": "^1.9.3",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
|
||||
@@ -1,40 +1,4 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "4.8.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1638390144,
|
||||
"version": "4.8.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "4.8.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "4.8.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
"version": "4.8.2",
|
||||
|
||||
@@ -5,22 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.8.6 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.5 - _December 1, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.4 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.3 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.8.2 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-utils",
|
||||
"version": "4.8.6",
|
||||
"version": "4.8.2",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -52,7 +52,7 @@
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-test-utils": "^5.4.12",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
|
||||
@@ -1,55 +1,12 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1640364306,
|
||||
"version": "0.30.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.30.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
|
||||
"pr": 321
|
||||
}
|
||||
],
|
||||
"timestamp": 1638390144
|
||||
},
|
||||
{
|
||||
"timestamp": 1637102971,
|
||||
"version": "0.29.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.29.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Prevent EP ETH balance from reducing when executin mtxs",
|
||||
"pr": 365
|
||||
}
|
||||
],
|
||||
"timestamp": 1637065617
|
||||
},
|
||||
{
|
||||
"version": "0.29.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Register transformERC20() and remove transformERC20Staging()",
|
||||
"pr": 355
|
||||
},
|
||||
{
|
||||
"note": "Add OtcOrders to FullMigration",
|
||||
"pr": 350
|
||||
}
|
||||
],
|
||||
"timestamp": 1635903615
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1634668033,
|
||||
|
||||
@@ -5,27 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v0.30.1 - _December 24, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.30.0 - _December 1, 2021_
|
||||
|
||||
* Add `AaveV2` and `Compound` deposit/withdrawal liquidity source (#321)
|
||||
|
||||
## v0.29.5 - _November 16, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.29.4 - _November 16, 2021_
|
||||
|
||||
* Prevent EP ETH balance from reducing when executin mtxs (#365)
|
||||
|
||||
## v0.29.3 - _November 3, 2021_
|
||||
|
||||
* Register transformERC20() and remove transformERC20Staging() (#355)
|
||||
* Add OtcOrders to FullMigration (#350)
|
||||
|
||||
## v0.29.2 - _October 19, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -78,7 +78,7 @@ contract MetaTransactionsFeature is
|
||||
/// @dev Name of this feature.
|
||||
string public constant override FEATURE_NAME = "MetaTransactions";
|
||||
/// @dev Version of this feature.
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 1);
|
||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
|
||||
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
|
||||
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
|
||||
"MetaTransactionData("
|
||||
@@ -105,17 +105,6 @@ contract MetaTransactionsFeature is
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Ensures that the ETH balance of `this` does not go below the
|
||||
/// initial ETH balance before the call (excluding ETH attached to the call).
|
||||
modifier doesNotReduceEthBalance() {
|
||||
uint256 initialBalance = address(this).balance - msg.value;
|
||||
_;
|
||||
require(
|
||||
initialBalance <= address(this).balance,
|
||||
"MetaTransactionsFeature/ETH_LEAK"
|
||||
);
|
||||
}
|
||||
|
||||
constructor(address zeroExAddress)
|
||||
public
|
||||
FixinCommon()
|
||||
@@ -151,7 +140,6 @@ contract MetaTransactionsFeature is
|
||||
payable
|
||||
override
|
||||
nonReentrant(REENTRANCY_MTX)
|
||||
doesNotReduceEthBalance
|
||||
refundsAttachedEth
|
||||
returns (bytes memory returnResult)
|
||||
{
|
||||
@@ -176,7 +164,6 @@ contract MetaTransactionsFeature is
|
||||
payable
|
||||
override
|
||||
nonReentrant(REENTRANCY_MTX)
|
||||
doesNotReduceEthBalance
|
||||
refundsAttachedEth
|
||||
returns (bytes[] memory returnResults)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,6 @@ import "../features/interfaces/IOwnableFeature.sol";
|
||||
import "../features/TransformERC20Feature.sol";
|
||||
import "../features/MetaTransactionsFeature.sol";
|
||||
import "../features/NativeOrdersFeature.sol";
|
||||
import "../features/OtcOrdersFeature.sol";
|
||||
import "./InitialMigration.sol";
|
||||
|
||||
|
||||
@@ -41,7 +40,6 @@ contract FullMigration {
|
||||
TransformERC20Feature transformERC20;
|
||||
MetaTransactionsFeature metaTransactions;
|
||||
NativeOrdersFeature nativeOrders;
|
||||
OtcOrdersFeature otcOrders;
|
||||
}
|
||||
|
||||
/// @dev Parameters needed to initialize features.
|
||||
@@ -175,16 +173,5 @@ contract FullMigration {
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
// OtcOrdersFeature
|
||||
{
|
||||
// Register the feature.
|
||||
ownable.migrate(
|
||||
address(features.otcOrders),
|
||||
abi.encodeWithSelector(
|
||||
OtcOrdersFeature.migrate.selector
|
||||
),
|
||||
address(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,10 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./IBridgeAdapter.sol";
|
||||
import "./BridgeProtocols.sol";
|
||||
import "./mixins/MixinAaveV2.sol";
|
||||
import "./mixins/MixinBalancer.sol";
|
||||
import "./mixins/MixinBalancerV2.sol";
|
||||
import "./mixins/MixinBancor.sol";
|
||||
import "./mixins/MixinCoFiX.sol";
|
||||
import "./mixins/MixinCompound.sol";
|
||||
import "./mixins/MixinCurve.sol";
|
||||
import "./mixins/MixinCurveV2.sol";
|
||||
import "./mixins/MixinCryptoCom.sol";
|
||||
@@ -49,12 +47,10 @@ import "./mixins/MixinZeroExBridge.sol";
|
||||
|
||||
contract BridgeAdapter is
|
||||
IBridgeAdapter,
|
||||
MixinAaveV2,
|
||||
MixinBalancer,
|
||||
MixinBalancerV2,
|
||||
MixinBancor,
|
||||
MixinCoFiX,
|
||||
MixinCompound,
|
||||
MixinCurve,
|
||||
MixinCurveV2,
|
||||
MixinCryptoCom,
|
||||
@@ -76,12 +72,10 @@ contract BridgeAdapter is
|
||||
{
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
MixinAaveV2()
|
||||
MixinBalancer()
|
||||
MixinBalancerV2()
|
||||
MixinBancor(weth)
|
||||
MixinCoFiX()
|
||||
MixinCompound(weth)
|
||||
MixinCurve(weth)
|
||||
MixinCurveV2()
|
||||
MixinCryptoCom()
|
||||
@@ -251,20 +245,6 @@ contract BridgeAdapter is
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.AAVEV2) {
|
||||
boughtAmount = _tradeAaveV2(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else if (protocolId == BridgeProtocols.COMPOUND) {
|
||||
boughtAmount = _tradeCompound(
|
||||
sellToken,
|
||||
buyToken,
|
||||
sellAmount,
|
||||
order.bridgeData
|
||||
);
|
||||
} else {
|
||||
boughtAmount = _tradeZeroExBridge(
|
||||
sellToken,
|
||||
|
||||
@@ -50,6 +50,4 @@ library BridgeProtocols {
|
||||
uint128 internal constant CURVEV2 = 20;
|
||||
uint128 internal constant LIDO = 21;
|
||||
uint128 internal constant CLIPPER = 22; // Not used: Clipper is now using PLP interface
|
||||
uint128 internal constant AAVEV2 = 23;
|
||||
uint128 internal constant COMPOUND = 24;
|
||||
}
|
||||
|
||||
@@ -1,93 +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.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
|
||||
// Minimal Aave V2 LendingPool interface
|
||||
interface ILendingPool {
|
||||
/**
|
||||
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
|
||||
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
|
||||
* @param asset The address of the underlying asset to deposit
|
||||
* @param amount The amount to be deposited
|
||||
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
|
||||
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
|
||||
* is a different wallet
|
||||
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
||||
* 0 if the action is executed directly by the user, without any middle-man
|
||||
**/
|
||||
function deposit(
|
||||
address asset,
|
||||
uint256 amount,
|
||||
address onBehalfOf,
|
||||
uint16 referralCode
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
|
||||
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
|
||||
* @param asset The address of the underlying asset to withdraw
|
||||
* @param amount The underlying amount to be withdrawn
|
||||
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
|
||||
* @param to Address that will receive the underlying, same as msg.sender if the user
|
||||
* wants to receive it on his own wallet, or a different address if the beneficiary is a
|
||||
* different wallet
|
||||
* @return The final amount withdrawn
|
||||
**/
|
||||
function withdraw(
|
||||
address asset,
|
||||
uint256 amount,
|
||||
address to
|
||||
) external returns (uint256);
|
||||
}
|
||||
|
||||
contract MixinAaveV2 {
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
|
||||
function _tradeAaveV2(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256)
|
||||
{
|
||||
(ILendingPool lendingPool, address aToken) = abi.decode(bridgeData, (ILendingPool, address));
|
||||
|
||||
sellToken.approveIfBelow(
|
||||
address(lendingPool),
|
||||
sellAmount
|
||||
);
|
||||
|
||||
if (address(buyToken) == aToken) {
|
||||
lendingPool.deposit(address(sellToken), sellAmount, address(this), 0);
|
||||
// 1:1 mapping token -> aToken and have the same number of decimals as the underlying token
|
||||
return sellAmount;
|
||||
} else if (address(sellToken) == aToken) {
|
||||
return lendingPool.withdraw(address(buyToken), sellAmount, address(this));
|
||||
}
|
||||
|
||||
revert("MixinAaveV2/UNSUPPORTED_TOKEN_PAIR");
|
||||
}
|
||||
}
|
||||
@@ -1,110 +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.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||
|
||||
|
||||
/// @dev Minimal CToken interface
|
||||
interface ICToken {
|
||||
/// @dev deposits specified amount underlying tokens and mints cToken for the sender
|
||||
/// @param mintAmountInUnderlying amount of underlying tokens to deposit to mint cTokens
|
||||
/// @return status code of whether the mint was successful or not
|
||||
function mint(uint256 mintAmountInUnderlying) external returns (uint256);
|
||||
/// @dev redeems specified amount of cTokens and returns the underlying token to the sender
|
||||
/// @param redeemTokensInCtokens amount of cTokens to redeem for underlying collateral
|
||||
/// @return status code of whether the redemption was successful or not
|
||||
function redeem(uint256 redeemTokensInCtokens) external returns (uint256);
|
||||
}
|
||||
/// @dev Minimal CEther interface
|
||||
interface ICEther {
|
||||
/// @dev deposits the amount of Ether sent as value and return mints cEther for the sender
|
||||
function mint() payable external;
|
||||
/// @dev redeems specified amount of cETH and returns the underlying ether to the sender
|
||||
/// @dev redeemTokensInCEther amount of cETH to redeem for underlying ether
|
||||
/// @return status code of whether the redemption was successful or not
|
||||
function redeem(uint256 redeemTokensInCEther) external returns (uint256);
|
||||
}
|
||||
|
||||
contract MixinCompound {
|
||||
using LibERC20TokenV06 for IERC20TokenV06;
|
||||
using LibSafeMathV06 for uint256;
|
||||
|
||||
IEtherTokenV06 private immutable WETH;
|
||||
|
||||
constructor(IEtherTokenV06 weth)
|
||||
public
|
||||
{
|
||||
WETH = weth;
|
||||
}
|
||||
|
||||
uint256 constant private COMPOUND_SUCCESS_CODE = 0;
|
||||
|
||||
function _tradeCompound(
|
||||
IERC20TokenV06 sellToken,
|
||||
IERC20TokenV06 buyToken,
|
||||
uint256 sellAmount,
|
||||
bytes memory bridgeData
|
||||
)
|
||||
internal
|
||||
returns (uint256)
|
||||
{
|
||||
(address cTokenAddress) = abi.decode(bridgeData, (address));
|
||||
uint256 beforeBalance = buyToken.balanceOf(address(this));
|
||||
|
||||
if (address(buyToken) == cTokenAddress) {
|
||||
if (address(sellToken) == address(WETH)) {
|
||||
// ETH/WETH -> cETH
|
||||
ICEther cETH = ICEther(cTokenAddress);
|
||||
// Compound expects ETH to be sent with mint call
|
||||
WETH.withdraw(sellAmount);
|
||||
// NOTE: cETH mint will revert on failure instead of returning a status code
|
||||
cETH.mint{value: sellAmount}();
|
||||
} else {
|
||||
sellToken.approveIfBelow(
|
||||
cTokenAddress,
|
||||
sellAmount
|
||||
);
|
||||
// Token -> cToken
|
||||
ICToken cToken = ICToken(cTokenAddress);
|
||||
require(cToken.mint(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_MINT_CTOKEN");
|
||||
}
|
||||
} else if (address(sellToken) == cTokenAddress) {
|
||||
if (address(buyToken) == address(WETH)) {
|
||||
// cETH -> ETH/WETH
|
||||
uint256 etherBalanceBefore = address(this).balance;
|
||||
ICEther cETH = ICEther(cTokenAddress);
|
||||
require(cETH.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CETHER");
|
||||
uint256 etherBalanceAfter = address(this).balance;
|
||||
uint256 receivedEtherBalance = etherBalanceAfter.safeSub(etherBalanceBefore);
|
||||
WETH.deposit{value: receivedEtherBalance}();
|
||||
} else {
|
||||
ICToken cToken = ICToken(cTokenAddress);
|
||||
require(cToken.redeem(sellAmount) == COMPOUND_SUCCESS_CODE, "MixinCompound/FAILED_TO_REDEEM_CTOKEN");
|
||||
}
|
||||
}
|
||||
|
||||
return buyToken.balanceOf(address(this)).safeSub(beforeBalance);
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,6 @@ contract TestMetaTransactionsTransformERC20Feature is
|
||||
payable
|
||||
returns (uint256 outputTokenAmount)
|
||||
{
|
||||
if (msg.value == 555) {
|
||||
tx.origin.transfer(1);
|
||||
}
|
||||
|
||||
if (msg.value == 666) {
|
||||
revert('FAIL');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-zero-ex",
|
||||
"version": "0.30.1",
|
||||
"version": "0.29.2",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -43,7 +43,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAaveV2|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCompound|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|FundRecoveryFeature|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|IFundRecoveryFeature|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -56,10 +56,10 @@
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.6.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contract-addresses": "^6.8.0",
|
||||
"@0x/contracts-erc20": "^3.3.21",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-test-utils": "^5.4.12",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/order-utils": "^10.4.28",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/protocol-utils": "^1.9.3",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
IZeroExContract,
|
||||
MetaTransactionsFeatureContract,
|
||||
NativeOrdersFeatureContract,
|
||||
OtcOrdersFeatureContract,
|
||||
OwnableFeatureContract,
|
||||
SimpleFunctionRegistryFeatureContract,
|
||||
TransformERC20FeatureContract,
|
||||
@@ -114,7 +113,6 @@ export interface FullFeatures extends BootstrapFeatures {
|
||||
transformERC20: string;
|
||||
metaTransactions: string;
|
||||
nativeOrders: string;
|
||||
otcOrders: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +123,6 @@ export interface FullFeatureArtifacts extends BootstrapFeatureArtifacts {
|
||||
metaTransactions: SimpleContractArtifact;
|
||||
nativeOrders: SimpleContractArtifact;
|
||||
feeCollectorController: SimpleContractArtifact;
|
||||
otcOrders: SimpleContractArtifact;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +155,6 @@ const DEFAULT_FULL_FEATURES_ARTIFACTS = {
|
||||
metaTransactions: artifacts.MetaTransactionsFeature,
|
||||
nativeOrders: artifacts.NativeOrdersFeature,
|
||||
feeCollectorController: artifacts.FeeCollectorController,
|
||||
otcOrders: artifacts.OtcOrdersFeature,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -226,18 +222,6 @@ export async function deployFullFeaturesAsync(
|
||||
_config.protocolFeeMultiplier,
|
||||
)
|
||||
).address,
|
||||
otcOrders:
|
||||
features.otcOrders ||
|
||||
(
|
||||
await OtcOrdersFeatureContract.deployFrom0xArtifactAsync(
|
||||
_featureArtifacts.otcOrders,
|
||||
provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
_config.zeroExAddress,
|
||||
_config.wethAddress,
|
||||
)
|
||||
).address,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -81,12 +81,10 @@ import * as LiquidityProviderFeature from '../test/generated-artifacts/Liquidity
|
||||
import * as LiquidityProviderSandbox from '../test/generated-artifacts/LiquidityProviderSandbox.json';
|
||||
import * as LogMetadataTransformer from '../test/generated-artifacts/LogMetadataTransformer.json';
|
||||
import * as MetaTransactionsFeature from '../test/generated-artifacts/MetaTransactionsFeature.json';
|
||||
import * as MixinAaveV2 from '../test/generated-artifacts/MixinAaveV2.json';
|
||||
import * as MixinBalancer from '../test/generated-artifacts/MixinBalancer.json';
|
||||
import * as MixinBalancerV2 from '../test/generated-artifacts/MixinBalancerV2.json';
|
||||
import * as MixinBancor from '../test/generated-artifacts/MixinBancor.json';
|
||||
import * as MixinCoFiX from '../test/generated-artifacts/MixinCoFiX.json';
|
||||
import * as MixinCompound from '../test/generated-artifacts/MixinCompound.json';
|
||||
import * as MixinCryptoCom from '../test/generated-artifacts/MixinCryptoCom.json';
|
||||
import * as MixinCurve from '../test/generated-artifacts/MixinCurve.json';
|
||||
import * as MixinCurveV2 from '../test/generated-artifacts/MixinCurveV2.json';
|
||||
@@ -274,12 +272,10 @@ export const artifacts = {
|
||||
BridgeAdapter: BridgeAdapter as ContractArtifact,
|
||||
BridgeProtocols: BridgeProtocols as ContractArtifact,
|
||||
IBridgeAdapter: IBridgeAdapter as ContractArtifact,
|
||||
MixinAaveV2: MixinAaveV2 as ContractArtifact,
|
||||
MixinBalancer: MixinBalancer as ContractArtifact,
|
||||
MixinBalancerV2: MixinBalancerV2 as ContractArtifact,
|
||||
MixinBancor: MixinBancor as ContractArtifact,
|
||||
MixinCoFiX: MixinCoFiX as ContractArtifact,
|
||||
MixinCompound: MixinCompound as ContractArtifact,
|
||||
MixinCryptoCom: MixinCryptoCom as ContractArtifact,
|
||||
MixinCurve: MixinCurve as ContractArtifact,
|
||||
MixinCurveV2: MixinCurveV2 as ContractArtifact,
|
||||
|
||||
@@ -38,7 +38,6 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
let nativeOrdersFeature: TestMetaTransactionsNativeOrdersFeatureContract;
|
||||
|
||||
const MAX_FEE_AMOUNT = new BigNumber('1e18');
|
||||
const TRANSFORM_ERC20_ONE_WEI_VALUE = new BigNumber(555);
|
||||
const TRANSFORM_ERC20_FAILING_VALUE = new BigNumber(666);
|
||||
const TRANSFORM_ERC20_REENTER_VALUE = new BigNumber(777);
|
||||
const TRANSFORM_ERC20_BATCH_REENTER_VALUE = new BigNumber(888);
|
||||
@@ -598,7 +597,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reduce initial ETH balance', async () => {
|
||||
it('cannot reenter `executeMetaTransaction()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
@@ -610,23 +609,58 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_ONE_WEI_VALUE,
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
// Send pre-existing ETH to the EP.
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: new BigNumber(1),
|
||||
}),
|
||||
);
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith('MetaTransactionsFeature/ETH_LEAK');
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('executeMetaTransaction'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reenter `batchExecuteMetaTransactions()`', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const tx = feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.MetaTransactions.MetaTransactionCallFailedError(
|
||||
mtxHash,
|
||||
undefined,
|
||||
new ZeroExRevertErrors.Common.IllegalReentrancyError(
|
||||
feature.getSelector('batchExecuteMetaTransactions'),
|
||||
REENTRANCY_FLAG_MTX,
|
||||
).encode(),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -783,37 +817,6 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot reduce initial ETH balance', async () => {
|
||||
const args = getRandomTransformERC20Args();
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature
|
||||
.transformERC20(
|
||||
args.inputToken,
|
||||
args.outputToken,
|
||||
args.inputTokenAmount,
|
||||
args.minOutputTokenAmount,
|
||||
args.transformations,
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_ONE_WEI_VALUE,
|
||||
});
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
// Send pre-existing ETH to the EP.
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
from: owner,
|
||||
to: zeroEx.address,
|
||||
value: new BigNumber(1),
|
||||
}),
|
||||
);
|
||||
const tx = feature.batchExecuteMetaTransactions([mtx], [signature]).awaitTransactionSuccessAsync(callOpts);
|
||||
return expect(tx).to.revertWith('MetaTransactionsFeature/ETH_LEAK');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMetaTransactionExecutedBlock()', () => {
|
||||
|
||||
@@ -79,12 +79,10 @@ export * from '../test/generated-wrappers/liquidity_provider_feature';
|
||||
export * from '../test/generated-wrappers/liquidity_provider_sandbox';
|
||||
export * from '../test/generated-wrappers/log_metadata_transformer';
|
||||
export * from '../test/generated-wrappers/meta_transactions_feature';
|
||||
export * from '../test/generated-wrappers/mixin_aave_v2';
|
||||
export * from '../test/generated-wrappers/mixin_balancer';
|
||||
export * from '../test/generated-wrappers/mixin_balancer_v2';
|
||||
export * from '../test/generated-wrappers/mixin_bancor';
|
||||
export * from '../test/generated-wrappers/mixin_co_fi_x';
|
||||
export * from '../test/generated-wrappers/mixin_compound';
|
||||
export * from '../test/generated-wrappers/mixin_crypto_com';
|
||||
export * from '../test/generated-wrappers/mixin_curve';
|
||||
export * from '../test/generated-wrappers/mixin_curve_v2';
|
||||
|
||||
@@ -112,12 +112,10 @@
|
||||
"test/generated-artifacts/LiquidityProviderSandbox.json",
|
||||
"test/generated-artifacts/LogMetadataTransformer.json",
|
||||
"test/generated-artifacts/MetaTransactionsFeature.json",
|
||||
"test/generated-artifacts/MixinAaveV2.json",
|
||||
"test/generated-artifacts/MixinBalancer.json",
|
||||
"test/generated-artifacts/MixinBalancerV2.json",
|
||||
"test/generated-artifacts/MixinBancor.json",
|
||||
"test/generated-artifacts/MixinCoFiX.json",
|
||||
"test/generated-artifacts/MixinCompound.json",
|
||||
"test/generated-artifacts/MixinCryptoCom.json",
|
||||
"test/generated-artifacts/MixinCurve.json",
|
||||
"test/generated-artifacts/MixinCurveV2.json",
|
||||
|
||||
@@ -1,226 +1,4 @@
|
||||
[
|
||||
{
|
||||
"version": "16.49.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add more curve pools",
|
||||
"pr": 409
|
||||
}
|
||||
],
|
||||
"timestamp": 1643407900
|
||||
},
|
||||
{
|
||||
"version": "16.48.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Use `MIM` as an intermediate asset on `Fantom`",
|
||||
"pr": 405
|
||||
}
|
||||
],
|
||||
"timestamp": 1643148019
|
||||
},
|
||||
{
|
||||
"version": "16.47.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Adding support for Synapse on all networks",
|
||||
"pr": 400
|
||||
}
|
||||
],
|
||||
"timestamp": 1643136662
|
||||
},
|
||||
{
|
||||
"version": "16.46.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Enable `Curve` ETH/CVX pool",
|
||||
"pr": 394
|
||||
}
|
||||
],
|
||||
"timestamp": 1641863395
|
||||
},
|
||||
{
|
||||
"version": "16.45.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Handle 0 output samples and negative adjusted rate native orders in routing",
|
||||
"pr": 387
|
||||
}
|
||||
],
|
||||
"timestamp": 1641827361
|
||||
},
|
||||
{
|
||||
"version": "16.45.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update `Celo` intermediate tokens",
|
||||
"pr": 390
|
||||
}
|
||||
],
|
||||
"timestamp": 1641359319
|
||||
},
|
||||
{
|
||||
"version": "16.45.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Capture router timings",
|
||||
"pr": 388
|
||||
}
|
||||
],
|
||||
"timestamp": 1641308410
|
||||
},
|
||||
{
|
||||
"version": "16.44.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update neon-router and use router estimated output amount",
|
||||
"pr": 354
|
||||
}
|
||||
],
|
||||
"timestamp": 1640778328
|
||||
},
|
||||
{
|
||||
"version": "16.43.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "`UniswapV3` support for `Optimism`",
|
||||
"pr": 385
|
||||
}
|
||||
],
|
||||
"timestamp": 1640364306
|
||||
},
|
||||
{
|
||||
"version": "16.42.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "`UniswapV3` support for `Polygon`",
|
||||
"pr": 382
|
||||
},
|
||||
{
|
||||
"note": "Update `Beethoven` Graphql url",
|
||||
"pr": 383
|
||||
}
|
||||
],
|
||||
"timestamp": 1640124159
|
||||
},
|
||||
{
|
||||
"version": "16.41.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update mcusd contract address, and made celo native asset",
|
||||
"pr": 376
|
||||
}
|
||||
],
|
||||
"timestamp": 1638827302
|
||||
},
|
||||
{
|
||||
"version": "16.40.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `AaveV2` and `Compound` deposit/withdrawal liquidity source",
|
||||
"pr": 321
|
||||
}
|
||||
],
|
||||
"timestamp": 1638390144
|
||||
},
|
||||
{
|
||||
"version": "16.39.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Curve ETH/CRV pool",
|
||||
"pr": 378
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.38.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Capture sampler metrics",
|
||||
"pr": 374
|
||||
}
|
||||
],
|
||||
"timestamp": 1638228231
|
||||
},
|
||||
{
|
||||
"version": "16.37.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Changed Sushiswap router address",
|
||||
"pr": 373
|
||||
}
|
||||
],
|
||||
"timestamp": 1637349338
|
||||
},
|
||||
{
|
||||
"version": "16.36.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Specify liquid routes for FEI/TRIBE FXS/FRAX and OHM/FRAX",
|
||||
"pr": 371
|
||||
}
|
||||
],
|
||||
"timestamp": 1637290768
|
||||
},
|
||||
{
|
||||
"version": "16.35.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add Beethoven X, MorpheusSwap and JetSwap to Fantom",
|
||||
"pr": 370
|
||||
}
|
||||
],
|
||||
"timestamp": 1637206290
|
||||
},
|
||||
{
|
||||
"version": "16.34.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add support Celo",
|
||||
"pr": 367
|
||||
}
|
||||
],
|
||||
"timestamp": 1637102971
|
||||
},
|
||||
{
|
||||
"version": "16.33.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add support for Uniswap V3 1 bps pools",
|
||||
"pr": 366
|
||||
}
|
||||
],
|
||||
"timestamp": 1637065617
|
||||
},
|
||||
{
|
||||
"version": "16.32.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Extended Quote Report",
|
||||
"pr": 361
|
||||
}
|
||||
],
|
||||
"timestamp": 1636480845
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "16.31.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added `Curve`, `Curve_V2` and `KyberDmm` to Avalanche",
|
||||
"pr": 363
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1635903615,
|
||||
"version": "16.30.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "16.30.0",
|
||||
"changes": [
|
||||
|
||||
@@ -5,95 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v16.49.0 - _January 28, 2022_
|
||||
|
||||
* Add more curve pools (#409)
|
||||
|
||||
## v16.48.0 - _January 25, 2022_
|
||||
|
||||
* Use `MIM` as an intermediate asset on `Fantom` (#405)
|
||||
|
||||
## v16.47.0 - _January 25, 2022_
|
||||
|
||||
* Adding support for Synapse on all networks (#400)
|
||||
|
||||
## v16.46.0 - _January 11, 2022_
|
||||
|
||||
* Enable `Curve` ETH/CVX pool (#394)
|
||||
|
||||
## v16.45.2 - _January 10, 2022_
|
||||
|
||||
* Handle 0 output samples and negative adjusted rate native orders in routing (#387)
|
||||
|
||||
## v16.45.1 - _January 5, 2022_
|
||||
|
||||
* Update `Celo` intermediate tokens (#390)
|
||||
|
||||
## v16.45.0 - _January 4, 2022_
|
||||
|
||||
* Capture router timings (#388)
|
||||
|
||||
## v16.44.0 - _December 29, 2021_
|
||||
|
||||
* Update neon-router and use router estimated output amount (#354)
|
||||
|
||||
## v16.43.0 - _December 24, 2021_
|
||||
|
||||
* `UniswapV3` support for `Optimism` (#385)
|
||||
|
||||
## v16.42.0 - _December 21, 2021_
|
||||
|
||||
* `UniswapV3` support for `Polygon` (#382)
|
||||
* Update `Beethoven` Graphql url (#383)
|
||||
|
||||
## v16.41.0 - _December 6, 2021_
|
||||
|
||||
* Update mcusd contract address, and made celo native asset (#376)
|
||||
|
||||
## v16.40.0 - _December 1, 2021_
|
||||
|
||||
* Add `AaveV2` and `Compound` deposit/withdrawal liquidity source (#321)
|
||||
|
||||
## v16.39.0 - _Invalid date_
|
||||
|
||||
* Curve ETH/CRV pool (#378)
|
||||
|
||||
## v16.38.0 - _November 29, 2021_
|
||||
|
||||
* Capture sampler metrics (#374)
|
||||
|
||||
## v16.37.0 - _November 19, 2021_
|
||||
|
||||
* Changed Sushiswap router address (#373)
|
||||
|
||||
## v16.36.0 - _November 19, 2021_
|
||||
|
||||
* Specify liquid routes for FEI/TRIBE FXS/FRAX and OHM/FRAX (#371)
|
||||
|
||||
## v16.35.0 - _November 18, 2021_
|
||||
|
||||
* Add Beethoven X, MorpheusSwap and JetSwap to Fantom (#370)
|
||||
|
||||
## v16.34.0 - _November 16, 2021_
|
||||
|
||||
* Add support Celo (#367)
|
||||
|
||||
## v16.33.0 - _November 16, 2021_
|
||||
|
||||
* Add support for Uniswap V3 1 bps pools (#366)
|
||||
|
||||
## v16.32.0 - _November 9, 2021_
|
||||
|
||||
* Extended Quote Report (#361)
|
||||
|
||||
## v16.31.0 - _November 3, 2021_
|
||||
|
||||
* Added `Curve`, `Curve_V2` and `KyberDmm` to Avalanche (#363)
|
||||
|
||||
## v16.30.1 - _November 3, 2021_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v16.30.0 - _October 19, 2021_
|
||||
|
||||
* Fantom deployment (#347)
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/asset-swapper",
|
||||
"version": "16.49.0",
|
||||
"version": "16.30.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -39,7 +39,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
"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": {
|
||||
"assets": []
|
||||
}
|
||||
@@ -60,14 +60,14 @@
|
||||
"dependencies": {
|
||||
"@0x/assert": "^3.0.29",
|
||||
"@0x/base-contract": "^6.4.2",
|
||||
"@0x/contract-addresses": "^6.11.0",
|
||||
"@0x/contract-wrappers": "^13.18.5",
|
||||
"@0x/contracts-erc20": "^3.3.25",
|
||||
"@0x/contracts-zero-ex": "^0.30.1",
|
||||
"@0x/contract-addresses": "^6.8.0",
|
||||
"@0x/contract-wrappers": "^13.18.1",
|
||||
"@0x/contracts-erc20": "^3.3.21",
|
||||
"@0x/contracts-zero-ex": "^0.29.2",
|
||||
"@0x/dev-utils": "^4.2.9",
|
||||
"@0x/json-schemas": "^6.3.0",
|
||||
"@0x/neon-router": "^0.3.1",
|
||||
"@0x/protocol-utils": "^1.10.1",
|
||||
"@0x/neon-router": "^0.2.1",
|
||||
"@0x/protocol-utils": "^1.9.3",
|
||||
"@0x/quote-server": "^6.0.6",
|
||||
"@0x/types": "^3.3.4",
|
||||
"@0x/typescript-typings": "^5.2.1",
|
||||
@@ -80,8 +80,9 @@
|
||||
"@ethersproject/contracts": "^5.0.1",
|
||||
"@ethersproject/providers": "^5.0.4",
|
||||
"@ethersproject/strings": "^5.0.10",
|
||||
"axios": "^0.21.1",
|
||||
"axios-mock-adapter": "^1.19.0",
|
||||
"@open-rpc/client-js": "^1.7.1",
|
||||
"axios": "^0.24.0",
|
||||
"axios-mock-adapter": "^1.20.0",
|
||||
"cream-sor": "^0.3.3",
|
||||
"decimal.js": "^10.2.0",
|
||||
"ethereum-types": "^3.6.0",
|
||||
@@ -98,10 +99,10 @@
|
||||
"@0x/contracts-exchange": "^3.2.38",
|
||||
"@0x/contracts-exchange-libs": "^4.3.37",
|
||||
"@0x/contracts-gen": "^2.0.40",
|
||||
"@0x/contracts-test-utils": "^5.4.16",
|
||||
"@0x/contracts-utils": "^4.8.6",
|
||||
"@0x/contracts-test-utils": "^5.4.12",
|
||||
"@0x/contracts-utils": "^4.8.2",
|
||||
"@0x/mesh-rpc-client": "^9.4.2",
|
||||
"@0x/migrations": "^8.1.14",
|
||||
"@0x/migrations": "^8.1.9",
|
||||
"@0x/sol-compiler": "^4.7.5",
|
||||
"@0x/subproviders": "^6.6.0",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { SignatureType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
|
||||
@@ -11,12 +10,9 @@ import {
|
||||
RfqRequestOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterOpts,
|
||||
} from './types';
|
||||
import {
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
|
||||
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_MS = ONE_SECOND_MS * ONE_MINUTE_SECS;
|
||||
const DEFAULT_PER_PAGE = 1000;
|
||||
const ZERO_AMOUNT = new BigNumber(0);
|
||||
const ALT_MM_IMPUTED_INDICATIVE_EXPIRY_SECONDS = 180;
|
||||
|
||||
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
|
||||
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 = {
|
||||
isFromETH: false,
|
||||
isToETH: false,
|
||||
@@ -91,8 +73,6 @@ export const DEFAULT_WARNING_LOGGER: LogFunction = (obj, msg) =>
|
||||
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
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);
|
||||
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
@@ -111,8 +91,6 @@ export const constants = {
|
||||
ONE_AMOUNT: new BigNumber(1),
|
||||
ONE_SECOND_MS,
|
||||
ONE_MINUTE_MS,
|
||||
DEFAULT_SWAP_QUOTER_OPTS,
|
||||
DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID,
|
||||
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||
DEFAULT_EXCHANGE_PROXY_SWAP_QUOTE_GET_OPTS,
|
||||
DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||
|
||||
@@ -109,74 +109,44 @@ export {
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteInfo,
|
||||
SwapQuoteOrdersBreakdown,
|
||||
SwapQuoteMultiHopBreakdown,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterError,
|
||||
SwapQuoterOpts,
|
||||
SwapQuoterRfqOpts,
|
||||
SamplerMetrics,
|
||||
} from './types';
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export {
|
||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||
DEFAULT_GAS_SCHEDULE,
|
||||
SOURCE_FLAGS,
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
} from './utils/market_operation_utils/constants';
|
||||
export {
|
||||
Parameters,
|
||||
SamplerContractCall,
|
||||
SamplerContractOperation,
|
||||
} from './utils/market_operation_utils/sampler_contract_operation';
|
||||
export {
|
||||
BalancerFillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
Fill,
|
||||
FillData,
|
||||
GetMarketOrdersRfqOpts,
|
||||
KyberFillData,
|
||||
LiquidityProviderFillData,
|
||||
LiquidityProviderRegistry,
|
||||
MarketDepth,
|
||||
MarketDepthSide,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeRfqOrderFillData,
|
||||
NativeLimitOrderFillData,
|
||||
NativeFillData,
|
||||
OptimizedMarketOrder,
|
||||
SourceQuoteOperation,
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
} from './utils/market_operation_utils/types';
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeQuoteReportEntry,
|
||||
jsonifyFillData,
|
||||
MultiHopQuoteReportEntry,
|
||||
NativeLimitOrderQuoteReportEntry,
|
||||
NativeRfqOrderQuoteReportEntry,
|
||||
QuoteReport,
|
||||
QuoteReportEntry,
|
||||
ExtendedQuoteReport,
|
||||
ExtendedQuoteReportSources,
|
||||
ExtendedQuoteReportEntry,
|
||||
ExtendedQuoteReportIndexedEntry,
|
||||
ExtendedQuoteReportIndexedEntryOutbound,
|
||||
PriceComparisonsReport,
|
||||
} from './utils/quote_report_generator';
|
||||
export { QuoteRequestor, V4RFQIndicativeQuoteMM } from './utils/quote_requestor';
|
||||
export { QuoteRequestor } from './utils/quote_requestor';
|
||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export type Native = ERC20BridgeSource.Native;
|
||||
|
||||
@@ -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,12 +12,14 @@ import {
|
||||
FillQuoteTransformerSide,
|
||||
findTransformerNonce,
|
||||
} from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants, POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS } from '../constants';
|
||||
import {
|
||||
Address,
|
||||
AffiliateFeeType,
|
||||
Bytes,
|
||||
CalldataInfo,
|
||||
ExchangeProxyContractOpts,
|
||||
MarketBuySwapQuote,
|
||||
@@ -28,23 +30,22 @@ import {
|
||||
SwapQuoteConsumerOpts,
|
||||
SwapQuoteExecutionOpts,
|
||||
SwapQuoteGetOutputOpts,
|
||||
SwapQuoteLiquidityProviderBridgeOrder,
|
||||
SwapQuoteUniswapV2BridgeOrder,
|
||||
SwapQuoteUniswapV3BridgeOrder,
|
||||
SwapQuoteCurveBridgeOrder,
|
||||
SwapQuoteMooniswapBridgeOrder,
|
||||
SwapQuoteHop,
|
||||
SwapQuoteGenericBridgeOrder,
|
||||
SwapQuoteOrder,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { valueByChainId } from '../utils/utils';
|
||||
import {
|
||||
CURVE_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
|
||||
MOONISWAP_LIQUIDITY_PROVIDER_BY_CHAIN_ID,
|
||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||
} from '../utils/market_operation_utils/constants';
|
||||
import { poolEncoder } from '../utils/market_operation_utils/orders';
|
||||
import {
|
||||
CurveFillData,
|
||||
ERC20BridgeSource,
|
||||
FinalUniswapV3FillData,
|
||||
LiquidityProviderFillData,
|
||||
MooniswapFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
UniswapV2FillData,
|
||||
} from '../utils/market_operation_utils/types';
|
||||
|
||||
import {
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
MultiplexSubcall,
|
||||
multiplexTransformERC20Encoder,
|
||||
multiplexUniswapEncoder,
|
||||
multiplexBatchSellEncoder,
|
||||
} from './multiplex_encoders';
|
||||
import {
|
||||
getFQTTransformerDataFromOptimizedOrders,
|
||||
@@ -77,12 +79,30 @@ const PANCAKE_SWAP_FORKS = [
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
];
|
||||
|
||||
const FAKE_PROVIDER: any = {
|
||||
sendAsync(): void {
|
||||
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 {
|
||||
public readonly chainId: ChainId;
|
||||
public readonly transformerNonces: {
|
||||
@@ -95,9 +115,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
private readonly _exchangeProxy: IZeroExContract;
|
||||
|
||||
constructor(public readonly contractAddresses: ContractAddresses, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
assert.isNumber('chainId', chainId);
|
||||
constructor(public readonly contractAddresses: ContractAddresses, options: SwapQuoteConsumerOpts) {
|
||||
const { chainId } = options;
|
||||
this.chainId = chainId;
|
||||
this.contractAddresses = contractAddresses;
|
||||
this._exchangeProxy = new IZeroExContract(contractAddresses.exchangeProxy, FAKE_PROVIDER);
|
||||
@@ -151,15 +170,14 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
ethAmount = ethAmount.plus(sellAmount);
|
||||
}
|
||||
|
||||
const slippedOrders = slipNonNativeOrders(quote);
|
||||
|
||||
// VIP routes.
|
||||
if (
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
|
||||
) {
|
||||
const source = slippedOrders[0].source;
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
|
||||
const { source } = order;
|
||||
const { fillData } = order;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToUniswap(
|
||||
@@ -188,19 +206,20 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
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;
|
||||
if (isFromETH) {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellEthForTokenToUniswapV3(fillData.uniswapPath, minBuyAmount, NULL_ADDRESS)
|
||||
.sellEthForTokenToUniswapV3(fillData.encodedPath, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
} else if (isToETH) {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellTokenForEthToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.sellTokenForEthToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
} else {
|
||||
_calldataHexString = this._exchangeProxy
|
||||
.sellTokenForTokenToUniswapV3(fillData.uniswapPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.sellTokenForTokenToUniswapV3(fillData.encodedPath, sellAmount, minBuyAmount, NULL_ADDRESS)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
return {
|
||||
@@ -225,8 +244,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
ERC20BridgeSource.JulSwap,
|
||||
])
|
||||
) {
|
||||
const source = slippedOrders[0].source;
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<UniswapV2FillData>).fillData;
|
||||
const order = quote.hops[0].orders[0] as SwapQuoteUniswapV2BridgeOrder;
|
||||
const { source, fillData } = order;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToPancakeSwap(
|
||||
@@ -255,14 +274,13 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
[ChainId.Mainnet, ChainId.BSC].includes(this.chainId) &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])
|
||||
) {
|
||||
const fillData = (slippedOrders[0] as OptimizedMarketBridgeOrder<LiquidityProviderFillData>).fillData;
|
||||
const target = fillData.poolAddress;
|
||||
const { fillData } = quote.hops[0].orders[0] as SwapQuoteLiquidityProviderBridgeOrder;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
target,
|
||||
fillData.poolAddress,
|
||||
NULL_ADDRESS,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
@@ -284,7 +302,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
// ETH buy/sell is supported
|
||||
![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 {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
@@ -295,8 +313,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
encodeCurveLiquidityProviderData({
|
||||
curveAddress: fillData.pool.poolAddress,
|
||||
exchangeFunctionSelector: fillData.pool.exchangeFunctionSelector,
|
||||
curveAddress: fillData.poolAddress,
|
||||
exchangeFunctionSelector: fillData.exchangeFunctionSelector,
|
||||
fromCoinIdx: new BigNumber(fillData.fromTokenIdx),
|
||||
toCoinIdx: new BigNumber(fillData.toTokenIdx),
|
||||
}),
|
||||
@@ -313,7 +331,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
this.chainId === ChainId.Mainnet &&
|
||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
||||
) {
|
||||
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
|
||||
const { fillData } = quote.hops[0].orders[0] as SwapQuoteMooniswapBridgeOrder;
|
||||
return {
|
||||
calldataHexString: this._exchangeProxy
|
||||
.sellToLiquidityProvider(
|
||||
@@ -323,7 +341,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
NULL_ADDRESS,
|
||||
sellAmount,
|
||||
minBuyAmount,
|
||||
poolEncoder.encode([fillData.poolAddress]),
|
||||
encodeAddress(fillData.poolAddress),
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||
@@ -336,7 +354,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
quote.hops[0],
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
@@ -348,7 +366,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexMultiHopFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexMultiHopFillCalldata(
|
||||
{ ...quote, orders: slippedOrders },
|
||||
quote.hops,
|
||||
optsWithDefaults,
|
||||
),
|
||||
ethAmount,
|
||||
@@ -360,9 +378,8 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
// Build up the transforms.
|
||||
const transforms = [];
|
||||
// Create a WETH wrapper if coming from ETH.
|
||||
// Dont add the wethTransformer to CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isFromETH && this.chainId !== ChainId.Celo) {
|
||||
if (isFromETH) {
|
||||
// Create a WETH wrapper if coming from ETH.
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
@@ -372,51 +389,32 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
});
|
||||
}
|
||||
|
||||
// If it's two hop we have an intermediate token this is needed to encode the individual FQT
|
||||
// and we also want to ensure no dust amount is left in the flash wallet
|
||||
const intermediateToken = quote.isTwoHop ? slippedOrders[0].makerToken : NULL_ADDRESS;
|
||||
// This transformer will fill the quote.
|
||||
if (quote.isTwoHop) {
|
||||
const [firstHopOrder, secondHopOrder] = slippedOrders;
|
||||
for (const [i, hop] of quote.hops.entries()) {
|
||||
let fillAmount = !isBuyQuote(quote)
|
||||
? shouldSellEntireBalance ? MAX_UINT256 : hop.takerAmount
|
||||
: hop.makerAmount;
|
||||
let side = !isBuyQuote(quote) ? FillQuoteTransformerSide.Sell : FillQuoteTransformerSide.Buy;
|
||||
if (quote.hops.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({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
sellToken,
|
||||
buyToken: intermediateToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders([firstHopOrder]),
|
||||
side,
|
||||
fillAmount,
|
||||
sellToken: hop.takerToken,
|
||||
buyToken: hop.makerToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders(hop.orders),
|
||||
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,
|
||||
}),
|
||||
});
|
||||
})
|
||||
}
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
// Dont add the wethTransformer on CELO. There is no wrap/unwrap logic for CELO.
|
||||
if (isToETH && this.chainId !== ChainId.Celo) {
|
||||
|
||||
if (isToETH) {
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.wethTransformer,
|
||||
data: encodeWethTransformerData({
|
||||
@@ -476,10 +474,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
// Return any unspent sell tokens.
|
||||
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
|
||||
// be returned in TransformERC20Feature rather than PayTakerTransformer.
|
||||
if (!isToETH) {
|
||||
@@ -493,11 +487,10 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
amounts: [],
|
||||
}),
|
||||
});
|
||||
const TO_ETH_ADDRESS = this.chainId === ChainId.Celo ? this.contractAddresses.etherToken : ETH_TOKEN_ADDRESS;
|
||||
const calldataHexString = this._exchangeProxy
|
||||
.transformERC20(
|
||||
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
|
||||
isToETH ? TO_ETH_ADDRESS : buyToken,
|
||||
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
|
||||
shouldSellEntireBalance ? MAX_UINT256 : sellAmount,
|
||||
minBuyAmount,
|
||||
transforms,
|
||||
@@ -521,9 +514,109 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
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 = [];
|
||||
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,
|
||||
sellAmount: hop.maxTakerAmount,
|
||||
data: multiplexBatchSellEncoder.encode(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) {
|
||||
case ERC20BridgeSource.Native:
|
||||
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
|
||||
@@ -544,9 +637,9 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
case ERC20BridgeSource.SushiSwap:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV2,
|
||||
sellAmount: order.takerAmount,
|
||||
sellAmount: (order as SwapQuoteUniswapV2BridgeOrder).maxTakerAmount,
|
||||
data: multiplexUniswapEncoder.encode({
|
||||
tokens: (order.fillData as UniswapV2FillData).tokenAddressPath,
|
||||
tokens: (order as SwapQuoteUniswapV2BridgeOrder).fillData.tokenAddressPath,
|
||||
isSushi: order.source === ERC20BridgeSource.SushiSwap,
|
||||
}),
|
||||
});
|
||||
@@ -554,43 +647,46 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
case ERC20BridgeSource.LiquidityProvider:
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.LiquidityProvider,
|
||||
sellAmount: order.takerAmount,
|
||||
sellAmount: (order as SwapQuoteLiquidityProviderBridgeOrder).maxTakerAmount,
|
||||
data: multiplexPlpEncoder.encode({
|
||||
provider: (order.fillData as LiquidityProviderFillData).poolAddress,
|
||||
provider: (order as SwapQuoteLiquidityProviderBridgeOrder).fillData.poolAddress,
|
||||
auxiliaryData: NULL_BYTES,
|
||||
}),
|
||||
});
|
||||
break switch_statement;
|
||||
case ERC20BridgeSource.UniswapV3:
|
||||
const fillData = (order as OptimizedMarketBridgeOrder<FinalUniswapV3FillData>).fillData;
|
||||
subcalls.push({
|
||||
id: MultiplexSubcall.UniswapV3,
|
||||
sellAmount: order.takerAmount,
|
||||
data: fillData.uniswapPath,
|
||||
sellAmount: (order as SwapQuoteUniswapV3BridgeOrder).maxTakerAmount,
|
||||
data: (order as SwapQuoteUniswapV3BridgeOrder).fillData.encodedPath,
|
||||
});
|
||||
break switch_statement;
|
||||
default:
|
||||
const fqtData = encodeFillQuoteTransformerData({
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
sellToken: quote.takerToken,
|
||||
buyToken: quote.makerToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders(quote.orders.slice(i)),
|
||||
sellToken: order.takerToken,
|
||||
buyToken: order.makerToken,
|
||||
...getFQTTransformerDataFromOptimizedOrders(orders.slice(i)),
|
||||
refundReceiver: NULL_ADDRESS,
|
||||
fillAmount: MAX_UINT256,
|
||||
});
|
||||
const transformations = [
|
||||
{ deploymentNonce: this.transformerNonces.fillQuoteTransformer, data: fqtData },
|
||||
{
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [quote.takerToken],
|
||||
amounts: [],
|
||||
}),
|
||||
},
|
||||
// TODO(lawrence): needed?
|
||||
// {
|
||||
// deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
// data: encodePayTakerTransformerData({
|
||||
// tokens: [hop.takerToken],
|
||||
// amounts: [],
|
||||
// }),
|
||||
// },
|
||||
];
|
||||
subcalls.push({
|
||||
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({
|
||||
transformations,
|
||||
}),
|
||||
@@ -598,128 +694,21 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
break for_loop;
|
||||
}
|
||||
}
|
||||
if (opts.isFromETH) {
|
||||
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();
|
||||
}
|
||||
return subcalls;
|
||||
}
|
||||
}
|
||||
|
||||
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
|
||||
const slippage = getMaxQuoteSlippageRate(quote);
|
||||
if (!slippage) {
|
||||
return quote.orders;
|
||||
}
|
||||
return quote.orders.map(o => {
|
||||
if (o.source === ERC20BridgeSource.Native) {
|
||||
return o;
|
||||
function getTokenPathFromHops(hops: SwapQuoteHop[]): Address[] {
|
||||
const path = [];
|
||||
for (const [i, hop] of hops.entries()) {
|
||||
path.push(hop.takerToken);
|
||||
if (i === path.length - 1) {
|
||||
path.push(hop.makerToken);
|
||||
}
|
||||
return {
|
||||
...o,
|
||||
...(quote.type === MarketOperation.Sell
|
||||
? { makerAmount: o.makerAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN) }
|
||||
: { takerAmount: o.takerAmount.times(1 + slippage).integerValue(BigNumber.ROUND_UP) }),
|
||||
};
|
||||
});
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function getMaxQuoteSlippageRate(quote: MarketBuySwapQuote | MarketSellSwapQuote): number {
|
||||
if (quote.type === MarketOperation.Buy) {
|
||||
// (worstCaseTaker - bestCaseTaker) / bestCaseTaker
|
||||
// where worstCaseTaker >= bestCaseTaker
|
||||
return quote.worstCaseQuoteInfo.takerAmount
|
||||
.minus(quote.bestCaseQuoteInfo.takerAmount)
|
||||
.div(quote.bestCaseQuoteInfo.takerAmount)
|
||||
.toNumber();
|
||||
}
|
||||
// (bestCaseMaker - worstCaseMaker) / bestCaseMaker
|
||||
// where bestCaseMaker >= worstCaseMaker
|
||||
return quote.bestCaseQuoteInfo.makerAmount
|
||||
.minus(quote.worstCaseQuoteInfo.makerAmount)
|
||||
.div(quote.bestCaseQuoteInfo.makerAmount)
|
||||
.toNumber();
|
||||
function encodeAddress(address: Address): Bytes {
|
||||
return hexUtils.leftPad(hexUtils.slice(address, 0, 20));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum MultiplexSubcall {
|
||||
BatchSell,
|
||||
MultiHopSell,
|
||||
}
|
||||
|
||||
export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||
{
|
||||
name: 'transformations',
|
||||
@@ -22,15 +23,30 @@ export const multiplexTransformERC20Encoder = AbiEncoder.create([
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
export const multiplexRfqEncoder = AbiEncoder.create([
|
||||
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||
]);
|
||||
|
||||
export const multiplexUniswapEncoder = AbiEncoder.create([
|
||||
{ name: 'tokens', type: 'address[]' },
|
||||
{ name: 'isSushi', type: 'bool' },
|
||||
]);
|
||||
|
||||
export const multiplexPlpEncoder = AbiEncoder.create([
|
||||
{ name: 'provider', type: 'address' },
|
||||
{ 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 {
|
||||
createBridgeDataForBridgeOrder,
|
||||
getErc20BridgeSourceToBridgeSource,
|
||||
} from '../utils/market_operation_utils/orders';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedMarketOrderBase,
|
||||
} from '../utils/market_operation_utils/types';
|
||||
import {
|
||||
SwapQuoteGenericBridgeOrder,
|
||||
SwapQuoteOrder,
|
||||
SwapQuoteLimitOrder,
|
||||
SwapQuoteRfqOrder,
|
||||
} from '../types';
|
||||
|
||||
const MULTIPLEX_BATCH_FILL_SOURCES = [
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
@@ -29,16 +29,19 @@ export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeP
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
if (quote.isTwoHop) {
|
||||
// Must not be multi-hop.
|
||||
if (quote.hops.length > 1) {
|
||||
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;
|
||||
}
|
||||
// Use Multiplex if the non-fallback sources are a subset of
|
||||
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
|
||||
const nonFallbackSources = Object.keys(quote.sourceBreakdown);
|
||||
return nonFallbackSources.every(source => MULTIPLEX_BATCH_FILL_SOURCES.includes(source as ERC20BridgeSource));
|
||||
const nonFallbackSources = quote.hops.map(h => h.orders.filter(o => !o.isFallback).map(o => o.source)).flat(2);
|
||||
return nonFallbackSources.every(s => MULTIPLEX_BATCH_FILL_SOURCES.includes(s));
|
||||
}
|
||||
|
||||
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
|
||||
@@ -55,14 +58,12 @@ export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: Exchan
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
if (!quote.isTwoHop) {
|
||||
// Must be multi-hop.
|
||||
if (quote.hops.length < 2) {
|
||||
return false;
|
||||
}
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
return (
|
||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(firstHopOrder.source) &&
|
||||
MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(secondHopOrder.source)
|
||||
);
|
||||
const sources = quote.hops.map(h => h.orders.map(o => o.source)).flat(2);
|
||||
return sources.every(s => MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(s));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,11 +78,11 @@ export function isDirectSwapCompatible(
|
||||
if (requiresTransformERC20(opts)) {
|
||||
return false;
|
||||
}
|
||||
// Must be a single order.
|
||||
if (quote.orders.length !== 1) {
|
||||
// Must be a single hop with a single order.
|
||||
if (quote.hops.length !== 1 || quote.hops[0].orders.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const order = quote.orders[0];
|
||||
const order = quote.hops[0].orders[0];
|
||||
if (!directSources.includes(order.source)) {
|
||||
return false;
|
||||
}
|
||||
@@ -95,24 +96,24 @@ export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||
return quote.type === MarketOperation.Buy;
|
||||
}
|
||||
|
||||
function isOptimizedBridgeOrder(x: OptimizedMarketOrder): x is OptimizedMarketBridgeOrder {
|
||||
function isBridgeOrder(x: SwapQuoteOrder): x is SwapQuoteGenericBridgeOrder {
|
||||
return x.type === FillQuoteTransformerOrderType.Bridge;
|
||||
}
|
||||
|
||||
function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
return x.type === FillQuoteTransformerOrderType.Limit;
|
||||
}
|
||||
|
||||
function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
return x.type === FillQuoteTransformerOrderType.Rfq;
|
||||
}
|
||||
// function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||
// return x.type === FillQuoteTransformerOrderType.Limit;
|
||||
// }
|
||||
//
|
||||
// function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
// return x.type === FillQuoteTransformerOrderType.Rfq;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
||||
* FillQuoteTransformer.
|
||||
*/
|
||||
export function getFQTTransformerDataFromOptimizedOrders(
|
||||
orders: OptimizedMarketOrder[],
|
||||
orders: SwapQuoteOrder[],
|
||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
||||
bridgeOrders: [],
|
||||
@@ -122,25 +123,25 @@ export function getFQTTransformerDataFromOptimizedOrders(
|
||||
};
|
||||
|
||||
for (const order of orders) {
|
||||
if (isOptimizedBridgeOrder(order)) {
|
||||
if (isBridgeOrder(order)) {
|
||||
fqtData.bridgeOrders.push({
|
||||
bridgeData: createBridgeDataForBridgeOrder(order),
|
||||
makerTokenAmount: order.makerAmount,
|
||||
takerTokenAmount: order.takerAmount,
|
||||
bridgeData: order.fillData.encodedFillData,
|
||||
makerTokenAmount: order.minMakerAmount,
|
||||
takerTokenAmount: order.maxTakerAmount,
|
||||
source: getErc20BridgeSourceToBridgeSource(order.source),
|
||||
});
|
||||
} else if (isOptimizedLimitOrder(order)) {
|
||||
fqtData.limitOrders.push({
|
||||
order: order.fillData.order,
|
||||
signature: order.fillData.signature,
|
||||
maxTakerTokenFillAmount: order.takerAmount,
|
||||
});
|
||||
} else if (isOptimizedRfqOrder(order)) {
|
||||
fqtData.rfqOrders.push({
|
||||
order: order.fillData.order,
|
||||
signature: order.fillData.signature,
|
||||
maxTakerTokenFillAmount: order.takerAmount,
|
||||
});
|
||||
// } else if (isOptimizedLimitOrder(order)) {
|
||||
// fqtData.limitOrders.push({
|
||||
// order: order.fillData.order,
|
||||
// signature: order.fillData.signature,
|
||||
// maxTakerTokenFillAmount: order.takerAmount,
|
||||
// });
|
||||
// } else if (isOptimizedRfqOrder(order)) {
|
||||
// fqtData.rfqOrders.push({
|
||||
// order: order.fillData.order,
|
||||
// signature: order.fillData.signature,
|
||||
// maxTakerTokenFillAmount: order.takerAmount,
|
||||
// });
|
||||
} else {
|
||||
// Should never happen
|
||||
throw new Error('Unknown Order type');
|
||||
|
||||
@@ -20,13 +20,12 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
private readonly _contractAddresses: ContractAddresses;
|
||||
private readonly _exchangeProxyConsumer: ExchangeProxySwapQuoteConsumer;
|
||||
|
||||
public static getSwapQuoteConsumer(options: Partial<SwapQuoteConsumerOpts> = {}): SwapQuoteConsumer {
|
||||
public static getSwapQuoteConsumer(options: SwapQuoteConsumerOpts): SwapQuoteConsumer {
|
||||
return new SwapQuoteConsumer(options);
|
||||
}
|
||||
|
||||
constructor(options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
assert.isNumber('chainId', chainId);
|
||||
constructor(options: SwapQuoteConsumerOpts) {
|
||||
const { chainId } = options;
|
||||
|
||||
this.chainId = 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 { BigNumber, providerUtils } from '@0x/utils';
|
||||
import Axios, { AxiosInstance } from 'axios';
|
||||
import { BlockParamLiteral, MethodAbi, SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import { FastABI } from 'fast-abi';
|
||||
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import { Agent as HttpAgent } from 'http';
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
|
||||
import {
|
||||
Address,
|
||||
AssetSwapperContractAddresses,
|
||||
MarketBuySwapQuote,
|
||||
MarketOperation,
|
||||
@@ -19,6 +18,10 @@ import {
|
||||
SignedNativeOrder,
|
||||
SwapQuote,
|
||||
SwapQuoteInfo,
|
||||
SwapQuoteHop,
|
||||
SwapQuoteOrder,
|
||||
SwapQuoteGenericBridgeOrder,
|
||||
SwapQuoteNativeOrder,
|
||||
SwapQuoteOrdersBreakdown,
|
||||
SwapQuoteRequestOpts,
|
||||
SwapQuoterOpts,
|
||||
@@ -26,25 +29,26 @@ import {
|
||||
} from './types';
|
||||
import { assert } from './utils/assert';
|
||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||
import { BancorService } from './utils/market_operation_utils/bancor_service';
|
||||
import { SAMPLER_ADDRESS, SOURCE_FLAGS, ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
||||
import { ZERO_AMOUNT } from './utils/market_operation_utils/constants';
|
||||
import { SamplerClient } from './utils/market_operation_utils/sampler';
|
||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
GetMarketOrdersOpts,
|
||||
MarketDepth,
|
||||
MarketDepthSide,
|
||||
MarketSideLiquidity,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedHop,
|
||||
OptimizedOrder,
|
||||
OptimizedBridgeOrder,
|
||||
OptimizedLimitOrder,
|
||||
OptimizedRfqOrder,
|
||||
OptimizedGenericBridgeOrder,
|
||||
OptimizerResultWithReport,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
import { QuoteRequestor } from './utils/quote_requestor';
|
||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './utils/quote_simulation';
|
||||
import { ERC20BridgeSamplerContract } from './wrappers';
|
||||
|
||||
export abstract class Orderbook {
|
||||
public abstract getOrdersAsync(
|
||||
@@ -85,20 +89,15 @@ export class SwapQuoter {
|
||||
*
|
||||
* @return An instance of SwapQuoter
|
||||
*/
|
||||
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
|
||||
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: SwapQuoterOpts) {
|
||||
const {
|
||||
chainId,
|
||||
expiryBufferMs,
|
||||
permittedOrderFeeTypes,
|
||||
samplerGasLimit,
|
||||
rfqt,
|
||||
tokenAdjacencyGraph,
|
||||
liquidityProviderRegistry,
|
||||
} = { ...constants.DEFAULT_SWAP_QUOTER_OPTS, ...options };
|
||||
} = options;
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isValidOrderbook('orderbook', orderbook);
|
||||
assert.isNumber('chainId', chainId);
|
||||
assert.isNumber('expiryBufferMs', expiryBufferMs);
|
||||
this.chainId = chainId;
|
||||
this.provider = provider;
|
||||
this.orderbook = orderbook;
|
||||
@@ -113,45 +112,11 @@ export class SwapQuoter {
|
||||
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||
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(
|
||||
new DexOrderSampler(
|
||||
SamplerClient.createFromChainIdAndEndpoint(
|
||||
this.chainId,
|
||||
samplerContract,
|
||||
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,
|
||||
options.samplerServiceUrl,
|
||||
),
|
||||
this._contractAddresses,
|
||||
{
|
||||
@@ -216,7 +181,6 @@ export class SwapQuoter {
|
||||
MarketOperation.Buy,
|
||||
makerTokenBuyAmounts[i],
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
} else {
|
||||
@@ -243,49 +207,50 @@ export class SwapQuoter {
|
||||
takerAssetAmount: BigNumber,
|
||||
options: Partial<SwapQuoteRequestOpts> = {},
|
||||
): Promise<MarketDepth> {
|
||||
assert.isString('makerToken', makerToken);
|
||||
assert.isString('takerToken', takerToken);
|
||||
const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
||||
|
||||
let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||
? [[], []]
|
||||
: await Promise.all([
|
||||
this.orderbook.getOrdersAsync(makerToken, takerToken),
|
||||
this.orderbook.getOrdersAsync(takerToken, makerToken),
|
||||
]);
|
||||
if (!sellOrders || sellOrders.length === 0) {
|
||||
sellOrders = [createDummyOrder(makerToken, takerToken)];
|
||||
}
|
||||
if (!buyOrders || buyOrders.length === 0) {
|
||||
buyOrders = [createDummyOrder(takerToken, makerToken)];
|
||||
}
|
||||
|
||||
const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
|
||||
const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
|
||||
const { side } = marketSideLiquidity;
|
||||
|
||||
return [
|
||||
...dexQuotes,
|
||||
nativeOrders.map(o => {
|
||||
return {
|
||||
input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
|
||||
output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
||||
fillData: o,
|
||||
source: ERC20BridgeSource.Native,
|
||||
};
|
||||
}),
|
||||
];
|
||||
};
|
||||
const [bids, asks] = await Promise.all([
|
||||
this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
|
||||
this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
|
||||
]);
|
||||
return {
|
||||
bids: getMarketDepthSide(bids),
|
||||
asks: getMarketDepthSide(asks),
|
||||
makerTokenDecimals: asks.makerTokenDecimals,
|
||||
takerTokenDecimals: asks.takerTokenDecimals,
|
||||
};
|
||||
throw new Error(`Not implemented`);
|
||||
// assert.isString('makerToken', makerToken);
|
||||
// assert.isString('takerToken', takerToken);
|
||||
// const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
||||
//
|
||||
// let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||
// ? [[], []]
|
||||
// : await Promise.all([
|
||||
// this.orderbook.getOrdersAsync(makerToken, takerToken),
|
||||
// this.orderbook.getOrdersAsync(takerToken, makerToken),
|
||||
// ]);
|
||||
// if (!sellOrders || sellOrders.length === 0) {
|
||||
// sellOrders = [createDummyOrder(makerToken, takerToken)];
|
||||
// }
|
||||
// if (!buyOrders || buyOrders.length === 0) {
|
||||
// buyOrders = [createDummyOrder(takerToken, makerToken)];
|
||||
// }
|
||||
//
|
||||
// const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
|
||||
// const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
|
||||
// const { side } = marketSideLiquidity;
|
||||
//
|
||||
// return [
|
||||
// ...dexQuotes,
|
||||
// nativeOrders.map(o => {
|
||||
// return {
|
||||
// input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
|
||||
// output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
||||
// fillData: o,
|
||||
// source: ERC20BridgeSource.Native,
|
||||
// };
|
||||
// }),
|
||||
// ];
|
||||
// };
|
||||
// const [bids, asks] = await Promise.all([
|
||||
// this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
|
||||
// this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
|
||||
// ]);
|
||||
// return {
|
||||
// bids: getMarketDepthSide(bids),
|
||||
// asks: getMarketDepthSide(asks),
|
||||
// makerTokenDecimals: asks.makerTokenDecimals,
|
||||
// takerTokenDecimals: asks.takerTokenDecimals,
|
||||
// };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,9 +329,6 @@ export class SwapQuoter {
|
||||
const calcOpts: GetMarketOrdersOpts = {
|
||||
...cloneOpts,
|
||||
gasPrice,
|
||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
||||
),
|
||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||
};
|
||||
// pass the QuoteRequestor on if rfqt enabled
|
||||
@@ -397,12 +359,13 @@ export class SwapQuoter {
|
||||
marketOperation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
opts.bridgeSlippage,
|
||||
);
|
||||
|
||||
// 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.worstCaseQuoteInfo.gas += exchangeProxyOverhead;
|
||||
|
||||
@@ -496,27 +459,22 @@ function createSwapQuote(
|
||||
optimizerResult: OptimizerResultWithReport,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
operation: MarketOperation,
|
||||
side: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const {
|
||||
optimizedOrders,
|
||||
hops,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
sourceFlags,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
priceComparisonsReport,
|
||||
} = optimizerResult;
|
||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
||||
|
||||
// Calculate quote info
|
||||
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } = isTwoHop
|
||||
? calculateTwoHopQuoteInfo(optimizedOrders, operation, gasSchedule, slippage)
|
||||
: calculateQuoteInfo(optimizedOrders, operation, assetFillAmount, gasPrice, gasSchedule, slippage);
|
||||
const quoteHops = hops.map(hop => toSwapQuoteHop(hop, side, slippage));
|
||||
const { bestCaseQuoteInfo, worstCaseQuoteInfo, sourceBreakdown } =
|
||||
calculateQuoteInfo(quoteHops, side, assetFillAmount, gasPrice, slippage);
|
||||
|
||||
// Put together the swap quote
|
||||
const { makerTokenDecimals, takerTokenDecimals } = optimizerResult.marketSideLiquidity;
|
||||
@@ -524,7 +482,7 @@ function createSwapQuote(
|
||||
makerToken,
|
||||
takerToken,
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
orders: hops.map(h => h.orders).flat(1),
|
||||
bestCaseQuoteInfo,
|
||||
worstCaseQuoteInfo,
|
||||
sourceBreakdown,
|
||||
@@ -533,117 +491,216 @@ function createSwapQuote(
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
extendedQuoteReportSources,
|
||||
isTwoHop,
|
||||
priceComparisonsReport,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
if (side === MarketOperation.Buy) {
|
||||
return {
|
||||
...swapQuote,
|
||||
type: MarketOperation.Buy,
|
||||
makerTokenFillAmount: assetFillAmount,
|
||||
maxSlippage: slippage,
|
||||
hops: quoteHops,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...swapQuote,
|
||||
type: MarketOperation.Sell,
|
||||
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(
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
hops: SwapQuoteHop[],
|
||||
side: MarketOperation,
|
||||
fillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||
const bestCaseFillResult = simulateBestCaseFill({
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule },
|
||||
});
|
||||
|
||||
const worstCaseFillResult = simulateWorstCaseFill({
|
||||
gasPrice,
|
||||
orders: optimizedOrders,
|
||||
side: operation,
|
||||
fillAmount: assetFillAmount,
|
||||
opts: { gasSchedule, slippage },
|
||||
});
|
||||
|
||||
return {
|
||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(bestCaseFillResult),
|
||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(worstCaseFillResult),
|
||||
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();
|
||||
return {
|
||||
bestCaseQuoteInfo: {
|
||||
makerAmount: operation === MarketOperation.Sell ? secondHopFill.output : secondHopFill.input,
|
||||
takerAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
|
||||
totalTakerAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
|
||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
},
|
||||
// TODO jacob consolidate this with quote simulation worstCase
|
||||
worstCaseQuoteInfo: {
|
||||
makerAmount: MarketOperation.Sell
|
||||
? secondHopOrder.makerAmount.times(1 - slippage).integerValue()
|
||||
: secondHopOrder.makerAmount,
|
||||
takerAmount: MarketOperation.Sell
|
||||
? firstHopOrder.takerAmount
|
||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
|
||||
totalTakerAmount: MarketOperation.Sell
|
||||
? firstHopOrder.takerAmount
|
||||
: firstHopOrder.takerAmount.times(1 + slippage).integerValue(),
|
||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
},
|
||||
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 getNextFillAmount = (fillResults: QuoteFillResult[]) => {
|
||||
if (fillResults.length === 0) {
|
||||
return fillAmount;
|
||||
}
|
||||
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) {
|
||||
tokenPath.push(hop.takerToken);
|
||||
}
|
||||
if (i === tokenPath.length - 1) {
|
||||
tokenPath.push(hop.makerToken);
|
||||
}
|
||||
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(bestCaseFillResults);
|
||||
const combinedWorstCaseFillResult = combineQuoteFillResults(worstCaseFillResults);
|
||||
const sourceBreakdown = getSwapQuoteOrdersBreakdown(side, tokenPath, bestCaseFillResults);
|
||||
return {
|
||||
sourceBreakdown,
|
||||
bestCaseQuoteInfo: fillResultsToQuoteInfo(combinedBestCaseFillResult),
|
||||
worstCaseQuoteInfo: fillResultsToQuoteInfo(combinedWorstCaseFillResult),
|
||||
};
|
||||
}
|
||||
|
||||
function combineQuoteFillResults(fillResults: QuoteFillResult[]): QuoteFillResult {
|
||||
if (fillResults.length === 0) {
|
||||
throw new Error(`Empty fillResults array`);
|
||||
}
|
||||
const lastResult = fillResults[fillResults.length - 1];
|
||||
const r = {
|
||||
...fillResults[0],
|
||||
makerAssetAmount: lastResult.makerAssetAmount,
|
||||
totalMakerAssetAmount: lastResult.totalMakerAssetAmount,
|
||||
};
|
||||
for (const fr of fillResults.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: hopBreakdowns,
|
||||
},
|
||||
};
|
||||
}
|
||||
return breakdown;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-wrappers';
|
||||
import {
|
||||
FillQuoteTransformerLimitOrderInfo,
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerRfqOrderInfo,
|
||||
LimitOrderFields,
|
||||
RfqOrder,
|
||||
RfqOrderFields,
|
||||
@@ -16,13 +18,21 @@ import {
|
||||
ERC20BridgeSource,
|
||||
GetMarketOrdersOpts,
|
||||
LiquidityProviderRegistry,
|
||||
OptimizedMarketOrder,
|
||||
LiquidityProviderFillData,
|
||||
TokenAdjacencyGraph,
|
||||
BridgeFillData,
|
||||
CurveFillData,
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
NativeOrderFillData,
|
||||
MooniswapFillData,
|
||||
} from './utils/market_operation_utils/types';
|
||||
export { SamplerMetrics } from './utils/market_operation_utils/types';
|
||||
import { ExtendedQuoteReportSources, PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
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).
|
||||
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
|
||||
@@ -38,7 +48,9 @@ export interface SignedOrder<T> {
|
||||
signature: Signature;
|
||||
}
|
||||
|
||||
export type SignedNativeOrder = SignedOrder<LimitOrderFields> | SignedOrder<RfqOrderFields>;
|
||||
export type SignedRfqOrder = SignedOrder<RfqOrderFields>;
|
||||
export type SignedLimitOrder = SignedOrder<LimitOrderFields>;
|
||||
export type SignedNativeOrder = SignedLimitOrder | SignedRfqOrder;
|
||||
export type NativeOrderWithFillableAmounts = SignedNativeOrder & NativeOrderFillableAmountFields;
|
||||
|
||||
/**
|
||||
@@ -167,20 +179,72 @@ export interface SwapQuoteBase {
|
||||
takerToken: string;
|
||||
makerToken: string;
|
||||
gasPrice: BigNumber;
|
||||
orders: OptimizedMarketOrder[];
|
||||
hops: SwapQuoteHop[];
|
||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
extendedQuoteReportSources?: ExtendedQuoteReportSources;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
takerTokenDecimals: number;
|
||||
takerAmountPerEth: 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.
|
||||
* type: Specified MarketOperation the SwapQuote is provided for
|
||||
@@ -222,15 +286,17 @@ export interface SwapQuoteInfo {
|
||||
* percentage breakdown of each liquidity source used in quote
|
||||
*/
|
||||
export type SwapQuoteOrdersBreakdown = Partial<
|
||||
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: BigNumber } & {
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: BigNumber;
|
||||
intermediateToken: string;
|
||||
hops: ERC20BridgeSource[];
|
||||
};
|
||||
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: number } & {
|
||||
[ERC20BridgeSource.MultiHop]: SwapQuoteMultiHopBreakdown;
|
||||
}
|
||||
>;
|
||||
|
||||
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.
|
||||
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
||||
@@ -328,15 +394,16 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
||||
chainId: ChainId;
|
||||
orderRefreshIntervalMs: number;
|
||||
expiryBufferMs: number;
|
||||
ethereumRpcUrl?: string;
|
||||
// ethereumRpcUrl?: string;
|
||||
contractAddresses?: AssetSwapperContractAddresses;
|
||||
samplerGasLimit?: number;
|
||||
multiBridgeAddress?: string;
|
||||
// multiBridgeAddress?: string;
|
||||
ethGasStationUrl?: string;
|
||||
rfqt?: SwapQuoterRfqOpts;
|
||||
samplerOverrides?: SamplerOverrides;
|
||||
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
||||
liquidityProviderRegistry?: LiquidityProviderRegistry;
|
||||
// samplerOverrides?: SamplerOverrides;
|
||||
// tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
||||
// liquidityProviderRegistry?: LiquidityProviderRegistry;
|
||||
samplerServiceUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,8 +479,6 @@ export interface SamplerCallResult {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export enum AltQuoteModel {
|
||||
Firm = 'firm',
|
||||
Indicative = 'indicative',
|
||||
|
||||
@@ -223,17 +223,7 @@ export async function returnQuoteFromAltMMAsync<ResponseT>(
|
||||
cancelToken,
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.response) {
|
||||
// request was made and market maker responded
|
||||
warningLogger(
|
||||
{ data: err.response.data, status: err.response.status, headers: err.response.headers },
|
||||
`Alt RFQ MM request failed`,
|
||||
);
|
||||
} else if (err.request) {
|
||||
warningLogger({}, 'Alt RFQ MM no response received');
|
||||
} else {
|
||||
warningLogger({ err: err.message }, 'Failed to construct Alt RFQ MM request');
|
||||
}
|
||||
warningLogger(err, `Alt RFQ MM request failed`);
|
||||
throw new Error(`Alt RFQ MM request failed`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
const RESERVES_GQL_QUERY = gql`
|
||||
{
|
||||
reserves(
|
||||
first: 300
|
||||
where: { isActive: true, isFrozen: false }
|
||||
orderBy: totalLiquidity
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
underlyingAsset
|
||||
aToken {
|
||||
id
|
||||
}
|
||||
pool {
|
||||
id
|
||||
lendingPool
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface AaveReserve {
|
||||
id: string;
|
||||
underlyingAsset: string;
|
||||
aToken: {
|
||||
id: string;
|
||||
};
|
||||
pool: {
|
||||
id: string;
|
||||
lendingPool: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: AaveReserve[];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const RESERVES_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches Aave V2 reserve information from the official subgraph(s).
|
||||
* The reserve information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address
|
||||
*/
|
||||
export class AaveV2ReservesCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _subgraphUrl: string) {
|
||||
const resfreshReserves = async () => this.fetchAndUpdateReservesAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
resfreshReserves();
|
||||
setInterval(resfreshReserves, RESERVES_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
/**
|
||||
* Fetches Aave V2 reserves from the subgraph and updates the cache
|
||||
*/
|
||||
public async fetchAndUpdateReservesAsync(): Promise<void> {
|
||||
try {
|
||||
const { reserves } = await request<{ reserves: AaveReserve[] }>(this._subgraphUrl, RESERVES_GQL_QUERY);
|
||||
const newCache = reserves.reduce<Cache>((memo, reserve) => {
|
||||
const underlyingAsset = reserve.underlyingAsset.toLowerCase();
|
||||
if (!memo[underlyingAsset]) {
|
||||
memo[underlyingAsset] = [];
|
||||
}
|
||||
|
||||
memo[underlyingAsset].push(reserve);
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Aave V2 reserves cache: ${err.message}`);
|
||||
// Empty cache just to be safe
|
||||
this._cache = {};
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): AaveReserve | undefined {
|
||||
// Deposit takerToken into reserve
|
||||
if (this._cache[takerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[takerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === makerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Withdraw makerToken from reserve
|
||||
if (this._cache[makerToken.toLowerCase()]) {
|
||||
const matchingReserve = this._cache[makerToken.toLowerCase()].find(
|
||||
r => r.aToken.id === takerToken.toLowerCase(),
|
||||
);
|
||||
if (matchingReserve) {
|
||||
return matchingReserve;
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -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 { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -8,10 +7,6 @@ import { MarketOperation } from '../../types';
|
||||
import { COMPARISON_PRICE_DECIMALS, SOURCE_FLAGS } from './constants';
|
||||
import {
|
||||
ComparisonPrice,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
FeeEstimate,
|
||||
FeeSchedule,
|
||||
MarketSideLiquidity,
|
||||
} from './types';
|
||||
|
||||
@@ -29,41 +24,20 @@ export function getComparisonPrices(
|
||||
adjustedRate: BigNumber,
|
||||
amount: BigNumber,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
feeSchedule: FeeSchedule,
|
||||
exchangeProxyOverhead: ExchangeProxyOverhead,
|
||||
gasPrice: BigNumber,
|
||||
): ComparisonPrice {
|
||||
let wholeOrder: BigNumber | undefined;
|
||||
let feeInEth: BigNumber | number;
|
||||
|
||||
// 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 };
|
||||
}
|
||||
}
|
||||
let feeInEth = gasPrice.times(100e3);
|
||||
|
||||
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)
|
||||
const feePenalty = !marketSideLiquidity.outputAmountPerEth.isZero()
|
||||
? marketSideLiquidity.outputAmountPerEth.times(feeInEth)
|
||||
const feePenalty = !outputAmountPerEth.isZero()
|
||||
? outputAmountPerEth.times(feeInEth)
|
||||
: // if it's a sell, the input token is the taker token
|
||||
marketSideLiquidity.inputAmountPerEth
|
||||
inputAmountPerEth
|
||||
.times(feeInEth)
|
||||
.times(marketSideLiquidity.side === MarketOperation.Sell ? adjustedRate : adjustedRate.pow(-1));
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import axios from 'axios';
|
||||
|
||||
import { constants } from '../../constants';
|
||||
|
||||
export interface CToken {
|
||||
tokenAddress: string;
|
||||
underlyingAddress: string;
|
||||
}
|
||||
|
||||
interface CTokenApiResponse {
|
||||
cToken: Array<{
|
||||
token_address: string;
|
||||
underlying_address: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Cache {
|
||||
[key: string]: CToken;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const CTOKEN_REFRESH_INTERVAL_MS = 30 * constants.ONE_MINUTE_MS;
|
||||
|
||||
/**
|
||||
* Fetches a list of CTokens from Compound's official API.
|
||||
* The token information is updated every 30 minutes and cached
|
||||
* so that it can be accessed with the underlying token's address.
|
||||
*/
|
||||
export class CompoundCTokenCache {
|
||||
private _cache: Cache = {};
|
||||
constructor(private readonly _apiUrl: string, private readonly _wethAddress: string) {
|
||||
const refreshCTokenCache = async () => this.fetchAndUpdateCTokensAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
refreshCTokenCache();
|
||||
setInterval(refreshCTokenCache, CTOKEN_REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public async fetchAndUpdateCTokensAsync(): Promise<void> {
|
||||
try {
|
||||
const { data } = await axios.get<CTokenApiResponse>(`${this._apiUrl}/ctoken`);
|
||||
const newCache = data?.cToken.reduce<Cache>((memo, cToken) => {
|
||||
// NOTE: Re-map cETH with null underlying token address to WETH address (we only handle WETH internally)
|
||||
const underlyingAddressClean = cToken.underlying_address
|
||||
? cToken.underlying_address.toLowerCase()
|
||||
: this._wethAddress;
|
||||
|
||||
const tokenData: CToken = {
|
||||
tokenAddress: cToken.token_address.toLowerCase(),
|
||||
underlyingAddress: underlyingAddressClean,
|
||||
};
|
||||
memo[underlyingAddressClean] = tokenData;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this._cache = newCache;
|
||||
} catch (err) {
|
||||
logUtils.warn(`Failed to update Compound cToken cache: ${err.message}`);
|
||||
// NOTE: Safe to keep already cached data as tokens should only be added to the list
|
||||
}
|
||||
}
|
||||
public get(takerToken: string, makerToken: string): CToken | undefined {
|
||||
// mint cToken
|
||||
let cToken = this._cache[takerToken.toLowerCase()];
|
||||
if (cToken && makerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// redeem cToken
|
||||
cToken = this._cache[makerToken.toLowerCase()];
|
||||
if (cToken && takerToken.toLowerCase() === cToken.tokenAddress.toLowerCase()) {
|
||||
return cToken;
|
||||
}
|
||||
|
||||
// No match
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||
import { DexSample, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
@@ -18,12 +18,9 @@ export function createFills(opts: {
|
||||
targetInput?: BigNumber;
|
||||
outputAmountPerEth?: BigNumber;
|
||||
inputAmountPerEth?: BigNumber;
|
||||
excludedSources?: ERC20BridgeSource[];
|
||||
feeSchedule?: FeeSchedule;
|
||||
gasPrice: BigNumber;
|
||||
}): Fill[][] {
|
||||
const { side } = opts;
|
||||
const excludedSources = opts.excludedSources || [];
|
||||
const feeSchedule = opts.feeSchedule || {};
|
||||
const orders = opts.orders || [];
|
||||
const dexQuotes = opts.dexQuotes || [];
|
||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
||||
@@ -35,15 +32,15 @@ export function createFills(opts: {
|
||||
opts.targetInput,
|
||||
outputAmountPerEth,
|
||||
inputAmountPerEth,
|
||||
feeSchedule,
|
||||
opts.gasPrice,
|
||||
);
|
||||
// Create DEX fills.
|
||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, opts.gasPrice),
|
||||
);
|
||||
return [...dexFills, nativeFills]
|
||||
.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[] {
|
||||
@@ -95,63 +92,67 @@ export function nativeOrdersToFills(
|
||||
targetInput: BigNumber = POSITIVE_INF,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
// Create a single path from all orders.
|
||||
let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
|
||||
for (const o of orders) {
|
||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
|
||||
const makerAmount = fillableMakerAmount;
|
||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||
const outputPenalty = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
// targetInput can be less than the order size
|
||||
// whilst the penalty is constant, it affects the adjusted output
|
||||
// only up until the target has been exhausted.
|
||||
// A large order and an order at the exact target should be penalized
|
||||
// the same.
|
||||
const clippedInput = BigNumber.min(targetInput, input);
|
||||
// scale the clipped output inline with the input
|
||||
const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
const adjustedOutput =
|
||||
side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
const adjustedRate =
|
||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// Skip orders with rates that are <= 0.
|
||||
if (adjustedRate.lte(0)) {
|
||||
continue;
|
||||
}
|
||||
fills.push({
|
||||
sourcePathId,
|
||||
adjustedRate,
|
||||
adjustedOutput,
|
||||
input: clippedInput,
|
||||
output: clippedOutput,
|
||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
index: 0, // TBD
|
||||
parent: undefined, // TBD
|
||||
source: ERC20BridgeSource.Native,
|
||||
type,
|
||||
fillData: { ...o },
|
||||
});
|
||||
if (orders.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// Sort by descending adjusted rate.
|
||||
fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
||||
// Re-index fills.
|
||||
for (let i = 0; i < fills.length; ++i) {
|
||||
fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
||||
fills[i].index = i;
|
||||
}
|
||||
return fills;
|
||||
throw new Error(`Not implemented`);
|
||||
// const sourcePathId = hexUtils.random();
|
||||
// // Create a single path from all orders.
|
||||
// let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
|
||||
// for (const o of orders) {
|
||||
// const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
|
||||
// const makerAmount = fillableMakerAmount;
|
||||
// const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||
// const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||
// const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||
// const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
||||
// const outputPenalty = ethToOutputAmount({
|
||||
// input,
|
||||
// output,
|
||||
// inputAmountPerEth,
|
||||
// outputAmountPerEth,
|
||||
// ethAmount: fee,
|
||||
// });
|
||||
// // targetInput can be less than the order size
|
||||
// // whilst the penalty is constant, it affects the adjusted output
|
||||
// // only up until the target has been exhausted.
|
||||
// // A large order and an order at the exact target should be penalized
|
||||
// // the same.
|
||||
// const clippedInput = BigNumber.min(targetInput, input);
|
||||
// // scale the clipped output inline with the input
|
||||
// const clippedOutput = clippedInput.dividedBy(input).times(output);
|
||||
// const adjustedOutput =
|
||||
// side === MarketOperation.Sell ? clippedOutput.minus(outputPenalty) : clippedOutput.plus(outputPenalty);
|
||||
// const adjustedRate =
|
||||
// side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||
// // Skip orders with rates that are <= 0.
|
||||
// if (adjustedRate.lte(0)) {
|
||||
// continue;
|
||||
// }
|
||||
// fills.push({
|
||||
// sourcePathId,
|
||||
// adjustedRate,
|
||||
// adjustedOutput,
|
||||
// input: clippedInput,
|
||||
// output: clippedOutput,
|
||||
// flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||
// index: 0, // TBD
|
||||
// parent: undefined, // TBD
|
||||
// source: ERC20BridgeSource.Native,
|
||||
// type,
|
||||
// fillData: { ...o },
|
||||
// });
|
||||
// }
|
||||
// // Sort by descending adjusted rate.
|
||||
// fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
||||
// // Re-index fills.
|
||||
// for (let i = 0; i < fills.length; ++i) {
|
||||
// fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
||||
// fills[i].index = i;
|
||||
// }
|
||||
// return fills;
|
||||
}
|
||||
|
||||
export function dexSamplesToFills(
|
||||
@@ -159,7 +160,7 @@ export function dexSamplesToFills(
|
||||
samples: DexSample[],
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): Fill[] {
|
||||
const sourcePathId = hexUtils.random();
|
||||
const fills: Fill[] = [];
|
||||
@@ -171,10 +172,10 @@ export function dexSamplesToFills(
|
||||
for (let i = 0; i < nonzeroSamples.length; i++) {
|
||||
const sample = nonzeroSamples[i];
|
||||
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 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;
|
||||
if (i === 0) {
|
||||
// Only the first fill in a DEX path incurs a penalty.
|
||||
@@ -194,11 +195,15 @@ export function dexSamplesToFills(
|
||||
output,
|
||||
adjustedOutput,
|
||||
source,
|
||||
fillData,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
gasCost: sample.gasCost,
|
||||
index: i,
|
||||
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
||||
flags: SOURCE_FLAGS[source],
|
||||
data: {
|
||||
...metadata,
|
||||
encodedFillData,
|
||||
},
|
||||
});
|
||||
}
|
||||
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 { Omit } from '../../types';
|
||||
|
||||
import { ZERO_AMOUNT } from './constants';
|
||||
import { getTwoHopAdjustedRate } from './rate_utils';
|
||||
import {
|
||||
DexSample,
|
||||
ExchangeProxyOverhead,
|
||||
FeeSchedule,
|
||||
MarketSideLiquidity,
|
||||
MultiHopFillData,
|
||||
TokenAdjacencyGraph,
|
||||
} from './types';
|
||||
|
||||
@@ -30,49 +20,3 @@ export function getIntermediateTokens(
|
||||
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 { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||
import { Address, MarketOperation } from '../../types';
|
||||
|
||||
import { MAX_UINT256, ZERO_AMOUNT } from './constants';
|
||||
import {
|
||||
AaveV2FillData,
|
||||
AggregationError,
|
||||
BalancerFillData,
|
||||
BalancerV2FillData,
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CompoundFillData,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
DODOFillData,
|
||||
CollapsedGenericBridgeFill,
|
||||
ERC20BridgeSource,
|
||||
FillData,
|
||||
FinalUniswapV3FillData,
|
||||
GenericRouterFillData,
|
||||
KyberDmmFillData,
|
||||
KyberFillData,
|
||||
LidoFillData,
|
||||
LiquidityProviderFillData,
|
||||
MakerPsmFillData,
|
||||
MooniswapFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeLimitOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedMarketOrderBase,
|
||||
OrderDomain,
|
||||
ShellFillData,
|
||||
UniswapV2FillData,
|
||||
UniswapV3FillData,
|
||||
CollapsedNativeOrderFill,
|
||||
OptimizedGenericBridgeOrder,
|
||||
OptimizedLimitOrder,
|
||||
OptimizedRfqOrder,
|
||||
} from './types';
|
||||
|
||||
// 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 {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Balancer:
|
||||
@@ -134,8 +69,6 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'BakerySwap');
|
||||
case ERC20BridgeSource.Nerve:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Nerve');
|
||||
case ERC20BridgeSource.Synapse:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Synapse');
|
||||
case ERC20BridgeSource.Belt:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Belt');
|
||||
case ERC20BridgeSource.Ellipsis:
|
||||
@@ -188,371 +121,60 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Pangolin');
|
||||
case ERC20BridgeSource.TraderJoe:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'TraderJoe');
|
||||
case ERC20BridgeSource.UbeSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'UbeSwap');
|
||||
case ERC20BridgeSource.Beethovenx:
|
||||
return encodeBridgeSourceId(BridgeProtocol.BalancerV2, 'Beethovenx');
|
||||
case ERC20BridgeSource.SpiritSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpiritSwap');
|
||||
case ERC20BridgeSource.SpookySwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'SpookySwap');
|
||||
case ERC20BridgeSource.MorpheusSwap:
|
||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'MorpheusSwap');
|
||||
case ERC20BridgeSource.AaveV2:
|
||||
return encodeBridgeSourceId(BridgeProtocol.AaveV2, 'AaveV2');
|
||||
case ERC20BridgeSource.Compound:
|
||||
return encodeBridgeSourceId(BridgeProtocol.Compound, 'Compound');
|
||||
default:
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
fill: CollapsedFill,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketBridgeOrder {
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
fill: CollapsedGenericBridgeFill,
|
||||
inputToken: Address,
|
||||
outputToken: Address,
|
||||
): OptimizedGenericBridgeOrder {
|
||||
return {
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
||||
inputToken,
|
||||
outputToken,
|
||||
inputAmount: fill.input,
|
||||
outputAmount: fill.output,
|
||||
fillData: fill.data,
|
||||
source: fill.source,
|
||||
sourcePathId: fill.sourcePathId,
|
||||
type: FillQuoteTransformerOrderType.Bridge,
|
||||
fills: [fill],
|
||||
gasCost: fill.gasCost,
|
||||
isFallback: fill.isFallback,
|
||||
...((fill as any).metadata !== undefined ? { metadata: (fill as any).metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData {
|
||||
switch (fill.source) {
|
||||
case ERC20BridgeSource.UniswapV3: {
|
||||
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;
|
||||
export function getMakerTakerTokens(side: MarketOperation, inputToken: Address, outputToken: Address): [Address, Address] {
|
||||
const makerToken = side === MarketOperation.Sell ? outputToken : inputToken;
|
||||
const takerToken = side === MarketOperation.Sell ? inputToken : outputToken;
|
||||
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(
|
||||
fill: NativeCollapsedFill,
|
||||
fill: CollapsedNativeOrderFill,
|
||||
side: MarketOperation,
|
||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||
const fillData = fill.fillData;
|
||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
const base = {
|
||||
type: fill.type,
|
||||
source: ERC20BridgeSource.Native,
|
||||
makerToken: fillData.order.makerToken,
|
||||
takerToken: fillData.order.takerToken,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
fills: [fill],
|
||||
fillData,
|
||||
};
|
||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||
): OptimizedLimitOrder | OptimizedRfqOrder {
|
||||
throw new Error(`No implementado`);
|
||||
// const fillData = fill.fillData;
|
||||
// const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||
// const base = {
|
||||
// type: fill.type,
|
||||
// source: ERC20BridgeSource.Native,
|
||||
// makerToken: fillData.order.makerToken,
|
||||
// takerToken: fillData.order.takerToken,
|
||||
// makerAmount,
|
||||
// takerAmount,
|
||||
// fills: [fill],
|
||||
// fillData,
|
||||
// };
|
||||
// return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||
// ? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||
// : { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { MarketOperation } from '../../types';
|
||||
import { Address, MarketOperation } from '../../types';
|
||||
|
||||
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
||||
import { ethToOutputAmount } from './fills';
|
||||
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
|
||||
import { createBridgeOrder, createNativeOptimizedOrder } from './orders';
|
||||
import { getCompleteRate, getRate } from './rate_utils';
|
||||
import {
|
||||
BridgeFill,
|
||||
CollapsedGenericBridgeFill,
|
||||
CollapsedFill,
|
||||
CollapsedNativeOrderFill,
|
||||
ERC20BridgeSource,
|
||||
ExchangeProxyOverhead,
|
||||
Fill,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedOrder,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
@@ -38,10 +40,11 @@ export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
||||
|
||||
export class Path {
|
||||
public collapsedFills?: ReadonlyArray<CollapsedFill>;
|
||||
public orders?: OptimizedMarketOrder[];
|
||||
public orders?: OptimizedOrder[];
|
||||
public sourceFlags: bigint = BigInt(0);
|
||||
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||
protected _adjustedSize: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||
private _fallbackFillsStartIndex: number = 0;
|
||||
|
||||
public static create(
|
||||
side: MarketOperation,
|
||||
@@ -107,33 +110,25 @@ export class Path {
|
||||
// Add the fills to the end that aren't already included
|
||||
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
|
||||
];
|
||||
this._fallbackFillsStartIndex = nativeFills.length + otherFills.length;
|
||||
// Recompute the source flags
|
||||
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
|
||||
return this;
|
||||
}
|
||||
|
||||
public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
public collapse(opts: { side: MarketOperation, inputToken: Address; outputToken: Address; }): CollapsedPath {
|
||||
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
|
||||
this.orders = [];
|
||||
for (let i = 0; i < collapsedFills.length; ) {
|
||||
for (let i = 0; i < collapsedFills.length; ++i) {
|
||||
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
|
||||
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as NativeCollapsedFill, opts.side));
|
||||
++i;
|
||||
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as CollapsedNativeOrderFill, opts.side));
|
||||
continue;
|
||||
}
|
||||
// If there are contiguous bridge orders, we can batch them together.
|
||||
// TODO jacob pretty sure this is from DFB and we can remove
|
||||
const contiguousBridgeFills = [collapsedFills[i]];
|
||||
for (let j = i + 1; j < collapsedFills.length; ++j) {
|
||||
if (collapsedFills[j].source === ERC20BridgeSource.Native) {
|
||||
break;
|
||||
}
|
||||
contiguousBridgeFills.push(collapsedFills[j]);
|
||||
}
|
||||
|
||||
this.orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts.side));
|
||||
i += 1;
|
||||
this.orders.push(createBridgeOrder(
|
||||
collapsedFills[i] as CollapsedGenericBridgeFill,
|
||||
opts.inputToken,
|
||||
opts.outputToken,
|
||||
));
|
||||
}
|
||||
return this as CollapsedPath;
|
||||
}
|
||||
@@ -214,7 +209,7 @@ export class Path {
|
||||
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) {
|
||||
// Fill must immediately follow its parent.
|
||||
if (this.fills[i].parent) {
|
||||
@@ -222,8 +217,9 @@ export class Path {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!skipDuplicateCheck) {
|
||||
if (!quick) {
|
||||
// Fill must not be duplicated.
|
||||
// Fills must all have the same input and output tokens.
|
||||
for (let j = 0; j < i; ++j) {
|
||||
if (this.fills[i] === this.fills[j]) {
|
||||
return false;
|
||||
@@ -249,7 +245,7 @@ export class Path {
|
||||
|
||||
private _collapseFills(): ReadonlyArray<CollapsedFill> {
|
||||
this.collapsedFills = [];
|
||||
for (const fill of this.fills) {
|
||||
for (const [i, fill] of this.fills.entries()) {
|
||||
const source = fill.source;
|
||||
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
|
||||
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
|
||||
@@ -257,8 +253,9 @@ export class Path {
|
||||
if (prevFill.sourcePathId === fill.sourcePathId) {
|
||||
prevFill.input = prevFill.input.plus(fill.input);
|
||||
prevFill.output = prevFill.output.plus(fill.output);
|
||||
prevFill.fillData = fill.fillData;
|
||||
prevFill.data = fill.data;
|
||||
prevFill.subFills.push(fill);
|
||||
prevFill.gasCost;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -266,10 +263,12 @@ export class Path {
|
||||
sourcePathId: fill.sourcePathId,
|
||||
source: fill.source,
|
||||
type: fill.type,
|
||||
fillData: fill.fillData,
|
||||
data: fill.data,
|
||||
input: fill.input,
|
||||
output: fill.output,
|
||||
subFills: [fill],
|
||||
gasCost: fill.gasCost,
|
||||
isFallback: this._fallbackFillsStartIndex > 0 ? i >= this._fallbackFillsStartIndex : false,
|
||||
});
|
||||
}
|
||||
return this.collapsedFills;
|
||||
@@ -296,5 +295,5 @@ export class Path {
|
||||
|
||||
export interface CollapsedPath extends Path {
|
||||
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
||||
readonly orders: OptimizedMarketOrder[];
|
||||
readonly orders: OptimizedOrder[];
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { assert } from '@0x/assert';
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
import { DEFAULT_INFO_LOGGER } from '../../constants';
|
||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID } from '../market_operation_utils/constants';
|
||||
|
||||
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
||||
import { getRate } from './rate_utils';
|
||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
|
||||
import { DexSample, ERC20BridgeSource, Fill } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
||||
|
||||
const RUN_LIMIT_DECAY_FACTOR = 0.5;
|
||||
const RUST_ROUTER_NUM_SAMPLES = 200;
|
||||
const FILL_QUOTE_TRANSFORMER_GAS_OVERHEAD = new BigNumber(150e3);
|
||||
// NOTE: The Rust router will panic with less than 3 samples
|
||||
const MIN_NUM_SAMPLE_INPUTS = 3;
|
||||
@@ -40,11 +42,11 @@ function calculateOuputFee(
|
||||
sampleOrNativeOrder: DexSample | NativeOrderWithFillableAmounts,
|
||||
outputAmountPerEth: BigNumber,
|
||||
inputAmountPerEth: BigNumber,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
): BigNumber {
|
||||
if (isDexSample(sampleOrNativeOrder)) {
|
||||
const { input, output, source, fillData } = sampleOrNativeOrder;
|
||||
const fee = fees[source]?.(fillData) || 0;
|
||||
const { input, output, source, encodedFillData } = sampleOrNativeOrder;
|
||||
const fee = gasPrice.times(sampleOrNativeOrder.gasCost);
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
@@ -54,47 +56,64 @@ function calculateOuputFee(
|
||||
});
|
||||
return outputFee;
|
||||
} else {
|
||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
|
||||
const outputFee = ethToOutputAmount({
|
||||
input,
|
||||
output,
|
||||
inputAmountPerEth,
|
||||
outputAmountPerEth,
|
||||
ethAmount: fee,
|
||||
});
|
||||
return outputFee;
|
||||
throw new Error(`No implementado`);
|
||||
// const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||
// const outputFee = ethToOutputAmount({
|
||||
// input,
|
||||
// output,
|
||||
// inputAmountPerEth,
|
||||
// outputAmountPerEth,
|
||||
// ethAmount: fee,
|
||||
// });
|
||||
// return outputFee;
|
||||
}
|
||||
}
|
||||
|
||||
// Use linear interpolation to approximate the output
|
||||
// at a certain input somewhere between the two samples
|
||||
// See https://en.wikipedia.org/wiki/Linear_interpolation
|
||||
const interpolateOutputFromSamples = (
|
||||
left: { input: BigNumber; output: BigNumber },
|
||||
right: { input: BigNumber; output: BigNumber },
|
||||
targetInput: BigNumber,
|
||||
): BigNumber =>
|
||||
left.output.plus(
|
||||
right.output
|
||||
.minus(left.output)
|
||||
.dividedBy(right.input.minus(left.input))
|
||||
.times(targetInput.minus(left.input)),
|
||||
);
|
||||
|
||||
function findRoutesAndCreateOptimalPath(
|
||||
side: MarketOperation,
|
||||
samples: DexSample[][],
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
neonRouterNumSamples: number,
|
||||
gasPrice: BigNumber,
|
||||
): Path | undefined {
|
||||
const createFill = (sample: DexSample): Fill | undefined => {
|
||||
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
||||
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
|
||||
if (fills.length === 0) {
|
||||
return undefined;
|
||||
const createFill = (sample: DexSample) =>
|
||||
dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)[0];
|
||||
// Track sample id's to integers (required by rust router)
|
||||
const sampleIdLookup: { [key: string]: number } = {};
|
||||
let sampleIdCounter = 0;
|
||||
const sampleToId = (source: ERC20BridgeSource, index: number): number => {
|
||||
const key = `${source}-${index}`;
|
||||
if (sampleIdLookup[key]) {
|
||||
return sampleIdLookup[key];
|
||||
} else {
|
||||
sampleIdLookup[key] = ++sampleIdCounter;
|
||||
return sampleIdLookup[key];
|
||||
}
|
||||
|
||||
return fills[0];
|
||||
};
|
||||
|
||||
const samplesAndNativeOrdersWithResults: Array<DexSample[] | NativeOrderWithFillableAmounts[]> = [];
|
||||
const serializedPaths: SerializedPath[] = [];
|
||||
const sampleSourcePathIds: string[] = [];
|
||||
for (const singleSourceSamples of samples) {
|
||||
if (singleSourceSamples.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourcePathId = hexUtils.random();
|
||||
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
||||
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
||||
if (singleSourceSamples[i].output.isZero()) {
|
||||
@@ -112,11 +131,11 @@ function findRoutesAndCreateOptimalPath(
|
||||
// TODO(kimpers): Do we need to handle 0 entries, from eg Kyber?
|
||||
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
|
||||
(memo, sample, sampleIdx) => {
|
||||
memo.ids.push(`${sample.source}-${serializedPaths.length}-${sampleIdx}`);
|
||||
memo.ids.push(sampleToId(sample.source, sampleIdx));
|
||||
memo.inputs.push(sample.input.integerValue().toNumber());
|
||||
memo.outputs.push(sample.output.integerValue().toNumber());
|
||||
memo.outputFees.push(
|
||||
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
||||
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
);
|
||||
@@ -133,10 +152,8 @@ function findRoutesAndCreateOptimalPath(
|
||||
|
||||
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
||||
serializedPaths.push(serializedPath);
|
||||
sampleSourcePathIds.push(sourcePathId);
|
||||
}
|
||||
|
||||
const nativeOrdersourcePathId = hexUtils.random();
|
||||
for (const [idx, nativeOrder] of nativeOrders.entries()) {
|
||||
const { input: normalizedOrderInput, output: normalizedOrderOutput } = nativeOrderToNormalizedAmounts(
|
||||
side,
|
||||
@@ -147,25 +164,32 @@ function findRoutesAndCreateOptimalPath(
|
||||
if (normalizedOrderInput.isLessThanOrEqualTo(0) || normalizedOrderOutput.isLessThanOrEqualTo(0)) {
|
||||
continue;
|
||||
}
|
||||
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
||||
|
||||
// HACK: the router requires at minimum 3 samples as a basis for interpolation
|
||||
const inputs = [
|
||||
0,
|
||||
normalizedOrderInput
|
||||
.dividedBy(2)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
normalizedOrderInput.integerValue().toNumber(),
|
||||
];
|
||||
const outputs = [
|
||||
0,
|
||||
normalizedOrderOutput
|
||||
.dividedBy(2)
|
||||
.integerValue()
|
||||
.toNumber(),
|
||||
normalizedOrderOutput.integerValue().toNumber(),
|
||||
];
|
||||
// NOTE: same fee no matter if full or partial fill
|
||||
const fee = calculateOuputFee(side, nativeOrder, opts.outputAmountPerEth, opts.inputAmountPerEth, gasPrice)
|
||||
.integerValue()
|
||||
.toNumber();
|
||||
|
||||
// HACK: due to an issue with the Rust router interpolation we need to create exactly 13 samples from the native order
|
||||
const ids = [];
|
||||
const inputs = [];
|
||||
const outputs = [];
|
||||
const outputFees = [];
|
||||
for (let i = 1; i <= 13; i++) {
|
||||
const fraction = i / 13;
|
||||
const currentInput = BigNumber.min(normalizedOrderInput.times(fraction), normalizedOrderInput);
|
||||
const currentOutput = BigNumber.min(normalizedOrderOutput.times(fraction), normalizedOrderOutput);
|
||||
const id = `${ERC20BridgeSource.Native}-${serializedPaths.length}-${idx}-${i}`;
|
||||
inputs.push(currentInput.integerValue().toNumber());
|
||||
outputs.push(currentOutput.integerValue().toNumber());
|
||||
outputFees.push(fee);
|
||||
ids.push(id);
|
||||
}
|
||||
const outputFees = [fee, fee, fee];
|
||||
// NOTE: ids can be the same for all fake samples
|
||||
const id = sampleToId(ERC20BridgeSource.Native, idx);
|
||||
const ids = [id, id, id];
|
||||
|
||||
const serializedPath: SerializedPath = {
|
||||
ids,
|
||||
@@ -176,7 +200,6 @@ function findRoutesAndCreateOptimalPath(
|
||||
|
||||
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
||||
serializedPaths.push(serializedPath);
|
||||
sampleSourcePathIds.push(nativeOrdersourcePathId);
|
||||
}
|
||||
|
||||
if (serializedPaths.length === 0) {
|
||||
@@ -189,33 +212,30 @@ function findRoutesAndCreateOptimalPath(
|
||||
pathsIn: serializedPaths,
|
||||
};
|
||||
|
||||
const before = performance.now();
|
||||
const allSourcesRustRoute = new Float64Array(rustArgs.pathsIn.length);
|
||||
const strategySourcesOutputAmounts = new Float64Array(rustArgs.pathsIn.length);
|
||||
route(rustArgs, allSourcesRustRoute, strategySourcesOutputAmounts, neonRouterNumSamples);
|
||||
route(rustArgs, allSourcesRustRoute, RUST_ROUTER_NUM_SAMPLES);
|
||||
DEFAULT_INFO_LOGGER(
|
||||
{ router: 'neon-router', performanceMs: performance.now() - before, type: 'real' },
|
||||
'Rust router real routing performance',
|
||||
);
|
||||
|
||||
assert.assert(
|
||||
rustArgs.pathsIn.length === allSourcesRustRoute.length,
|
||||
'different number of sources in the Router output than the input',
|
||||
);
|
||||
assert.assert(
|
||||
rustArgs.pathsIn.length === strategySourcesOutputAmounts.length,
|
||||
'different number of sources in the Router output amounts results than the input',
|
||||
);
|
||||
|
||||
const routesAndSamplesAndOutputs = _.zip(
|
||||
allSourcesRustRoute,
|
||||
samplesAndNativeOrdersWithResults,
|
||||
strategySourcesOutputAmounts,
|
||||
sampleSourcePathIds,
|
||||
);
|
||||
const routesAndSamples = _.zip(allSourcesRustRoute, samplesAndNativeOrdersWithResults);
|
||||
|
||||
const adjustedFills: Fill[] = [];
|
||||
const totalRoutedAmount = BigNumber.sum(...allSourcesRustRoute);
|
||||
|
||||
const scale = input.dividedBy(totalRoutedAmount);
|
||||
for (const [routeInput, routeSamplesAndNativeOrders, outputAmount, sourcePathId] of routesAndSamplesAndOutputs) {
|
||||
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount || !Number.isFinite(outputAmount)) {
|
||||
for (const [routeInput, routeSamplesAndNativeOrders] of routesAndSamples) {
|
||||
if (!routeInput || !routeSamplesAndNativeOrders) {
|
||||
continue;
|
||||
}
|
||||
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
|
||||
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precisions loss for number/f64
|
||||
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
|
||||
const rustInputAdjusted = BigNumber.min(
|
||||
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
|
||||
@@ -230,23 +250,16 @@ function findRoutesAndCreateOptimalPath(
|
||||
rustInputAdjusted,
|
||||
opts.outputAmountPerEth,
|
||||
opts.inputAmountPerEth,
|
||||
fees,
|
||||
)[0] as Fill | undefined;
|
||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be skipped
|
||||
// and nativeFill will be `undefined`
|
||||
if (nativeFill) {
|
||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
|
||||
}
|
||||
gasPrice,
|
||||
)[0];
|
||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||
adjustedFills.push(nativeFill);
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: For DexSamples only
|
||||
let fill = createFill(current);
|
||||
if (!fill) {
|
||||
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
|
||||
// throughout the path (UniswapV3) and for a closer guesstimate at
|
||||
// gas used
|
||||
@@ -254,47 +267,49 @@ function findRoutesAndCreateOptimalPath(
|
||||
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
||||
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
||||
if (k === 0) {
|
||||
fill = createFill(routeSamples[0]) ?? fill;
|
||||
fill = createFill(routeSamples[0]);
|
||||
}
|
||||
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
|
||||
// Between here and the previous fill
|
||||
// HACK: Use the midpoint between the two
|
||||
const left = routeSamples[k];
|
||||
const right = routeSamples[k + 1];
|
||||
if (left && right) {
|
||||
fill =
|
||||
createFill({
|
||||
...right, // default to the greater (for gas used)
|
||||
input: rustInputAdjusted,
|
||||
output: new BigNumber(outputAmount),
|
||||
}) ?? fill;
|
||||
// Approximate how much output we get for the input with the surrounding samples
|
||||
const interpolatedOutput = interpolateOutputFromSamples(
|
||||
left,
|
||||
right,
|
||||
rustInputAdjusted,
|
||||
).decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
||||
|
||||
fill = createFill({
|
||||
...right, // default to the greater (for gas used)
|
||||
input: rustInputAdjusted,
|
||||
output: interpolatedOutput,
|
||||
});
|
||||
} else {
|
||||
assert.assert(Boolean(left || right), 'No valid sample to use');
|
||||
fill = createFill(left || right) ?? fill;
|
||||
fill = createFill(left || right);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kimpers): remove once we have solved the rounding/precision loss issues in the Rust router
|
||||
const scaleOutput = (fillInput: BigNumber, output: BigNumber) =>
|
||||
const scaleOutput = (output: BigNumber) =>
|
||||
output
|
||||
.dividedBy(fillInput)
|
||||
.dividedBy(fill.input)
|
||||
.times(rustInputAdjusted)
|
||||
.decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
||||
adjustedFills.push({
|
||||
...fill,
|
||||
input: rustInputAdjusted,
|
||||
output: scaleOutput(fill.input, fill.output),
|
||||
adjustedOutput: scaleOutput(fill.input, fill.adjustedOutput),
|
||||
output: scaleOutput(fill.output),
|
||||
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
||||
index: 0,
|
||||
parent: undefined,
|
||||
sourcePathId: sourcePathId ?? hexUtils.random(),
|
||||
});
|
||||
}
|
||||
|
||||
if (adjustedFills.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const pathFromRustInputs = Path.create(side, adjustedFills, input);
|
||||
|
||||
return pathFromRustInputs;
|
||||
@@ -306,29 +321,17 @@ export function findOptimalRustPathFromSamples(
|
||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||
input: BigNumber,
|
||||
opts: PathPenaltyOpts,
|
||||
fees: FeeSchedule,
|
||||
gasPrice: BigNumber,
|
||||
chainId: ChainId,
|
||||
neonRouterNumSamples: number,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
): Path | undefined {
|
||||
const beforeAllTimeMs = performance.now();
|
||||
let beforeTimeMs = performance.now();
|
||||
const allSourcesPath = findRoutesAndCreateOptimalPath(
|
||||
side,
|
||||
samples,
|
||||
nativeOrders,
|
||||
input,
|
||||
opts,
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
);
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'all',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
const before = performance.now();
|
||||
const logPerformance = () =>
|
||||
DEFAULT_INFO_LOGGER(
|
||||
{ router: 'neon-router', performanceMs: performance.now() - before, type: 'total' },
|
||||
'Rust router total routing performance',
|
||||
);
|
||||
|
||||
const allSourcesPath = findRoutesAndCreateOptimalPath(side, samples, nativeOrders, input, opts, gasPrice);
|
||||
if (!allSourcesPath) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -338,27 +341,11 @@ export function findOptimalRustPathFromSamples(
|
||||
// HACK(kimpers): The Rust router currently doesn't account for VIP sources correctly
|
||||
// we need to try to route them in isolation and compare with the results all sources
|
||||
if (vipSources.length > 0) {
|
||||
beforeTimeMs = performance.now();
|
||||
const vipSourcesSet = new Set(vipSources);
|
||||
const vipSourcesSamples = samples.filter(s => s[0] && vipSourcesSet.has(s[0].source));
|
||||
|
||||
if (vipSourcesSamples.length > 0) {
|
||||
const vipSourcesPath = findRoutesAndCreateOptimalPath(
|
||||
side,
|
||||
vipSourcesSamples,
|
||||
[],
|
||||
input,
|
||||
opts,
|
||||
fees,
|
||||
neonRouterNumSamples,
|
||||
);
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'vip',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
const vipSourcesPath = findRoutesAndCreateOptimalPath(side, vipSourcesSamples, [], input, opts, gasPrice);
|
||||
|
||||
const { input: allSourcesInput, output: allSourcesOutput } = allSourcesPath.adjustedSize();
|
||||
// NOTE: For sell quotes input is the taker asset and for buy quotes input is the maker asset
|
||||
@@ -371,18 +358,13 @@ export function findOptimalRustPathFromSamples(
|
||||
const allSourcesAdjustedRateWithFqtOverhead = getRate(side, allSourcesInput, outputWithFqtOverhead);
|
||||
|
||||
if (vipSourcesPath?.adjustedRate().isGreaterThan(allSourcesAdjustedRateWithFqtOverhead)) {
|
||||
logPerformance();
|
||||
return vipSourcesPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'neon-router',
|
||||
type: 'total',
|
||||
timingMs: performance.now() - beforeAllTimeMs,
|
||||
});
|
||||
|
||||
logPerformance();
|
||||
return allSourcesPath;
|
||||
}
|
||||
|
||||
@@ -395,10 +377,8 @@ export async function findOptimalPathJSAsync(
|
||||
fills: Fill[][],
|
||||
targetInput: BigNumber,
|
||||
runLimit: number = 2 ** 8,
|
||||
samplerMetrics?: SamplerMetrics,
|
||||
opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
|
||||
): Promise<Path | undefined> {
|
||||
const beforeTimeMs = performance.now();
|
||||
// Sort fill arrays by descending adjusted completed rate.
|
||||
// Remove any paths which cannot impact the optimal path
|
||||
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
|
||||
@@ -412,15 +392,7 @@ export async function findOptimalPathJSAsync(
|
||||
// Yield to event loop.
|
||||
await Promise.resolve();
|
||||
}
|
||||
const finalPath = optimalPath.isComplete() ? optimalPath : undefined;
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
samplerMetrics &&
|
||||
samplerMetrics.logRouterDetails({
|
||||
router: 'js',
|
||||
type: 'total',
|
||||
timingMs: performance.now() - beforeTimeMs,
|
||||
});
|
||||
return finalPath;
|
||||
return optimalPath.isComplete() ? optimalPath : undefined;
|
||||
}
|
||||
|
||||
// Sort fill arrays by descending adjusted completed rate.
|
||||
|
||||
@@ -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 { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
|
||||
import { ZERO_AMOUNT } from './constants';
|
||||
|
||||
// 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.
|
||||
* This value penalizes the path if it falls short of the target input.
|
||||
|
||||
@@ -1,199 +1,87 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { BigNumber, NULL_BYTES } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { SamplerOverrides } from '../../types';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
import { Address } from '../../types';
|
||||
|
||||
import { BancorService } from './bancor_service';
|
||||
import { PoolsCache } from './pools_cache';
|
||||
import { SamplerOperations } from './sampler_operations';
|
||||
import { BatchedOperation, ERC20BridgeSource, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
|
||||
import { DexSample, ERC20BridgeSource, TokenAdjacencyGraph } from './types';
|
||||
import { SamplerServiceRpcClient } from './sampler_service_rpc_client';
|
||||
|
||||
/**
|
||||
* Generate sample amounts up to `maxFillAmount`.
|
||||
*/
|
||||
export function getSampleAmounts(maxFillAmount: BigNumber, numSamples: number, expBase: number = 1): BigNumber[] {
|
||||
const distribution = [...Array<BigNumber>(numSamples)].map((_v, i) => new BigNumber(expBase).pow(i));
|
||||
const distributionSum = BigNumber.sum(...distribution);
|
||||
const stepSizes = distribution.map(d => d.div(distributionSum));
|
||||
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;
|
||||
interface TokenInfo {
|
||||
decimals: number;
|
||||
address: Address;
|
||||
gasCost: number;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
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[]): Promise<DexSample[][]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates interactions with the `ERC20BridgeSampler` contract.
|
||||
*/
|
||||
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);
|
||||
export class SamplerClient implements Sampler {
|
||||
static createFromChainIdAndEndpoint(chainId: ChainId, endpoint: string): SamplerClient {
|
||||
return new SamplerClient(chainId, new SamplerServiceRpcClient(endpoint));
|
||||
}
|
||||
|
||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
||||
|
||||
// prettier-ignore
|
||||
public async executeAsync<
|
||||
T1
|
||||
>(...ops: [T1]): Promise<[
|
||||
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);
|
||||
static async createFromEndpointAsync(endpoint: string): Promise<SamplerClient> {
|
||||
const service = new SamplerServiceRpcClient(endpoint);
|
||||
const chainId = await service.getChainIdAsync();
|
||||
return new SamplerClient(
|
||||
chainId,
|
||||
service,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of operations from `DexOrderSampler.ops` in a single transaction.
|
||||
* Takes an arbitrary length array, but is not typesafe.
|
||||
*/
|
||||
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 };
|
||||
private constructor(
|
||||
private readonly _chainId: number,
|
||||
private readonly _service: SamplerServiceRpcClient,
|
||||
) {}
|
||||
|
||||
// All operations are NOOPs
|
||||
if (callDatas.every(cd => cd === NULL_BYTES)) {
|
||||
return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES));
|
||||
}
|
||||
// Execute all non-empty calldatas.
|
||||
const rawCallResults = await this._samplerContract
|
||||
.batchCall(callDatas.filter(cd => cd !== NULL_BYTES))
|
||||
.callAsync({ overrides }, block);
|
||||
// Return the parsed results.
|
||||
let rawCallResultsIdx = 0;
|
||||
return callDatas.map((callData, i) => {
|
||||
// tslint:disable-next-line:boolean-naming
|
||||
const { data, success } =
|
||||
callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : { success: true, data: NULL_BYTES };
|
||||
return success ? ops[i].handleCallResults(data) : ops[i].handleRevert(data);
|
||||
});
|
||||
public get chainId(): ChainId {
|
||||
return this._chainId;
|
||||
}
|
||||
|
||||
public async getPricesAsync(
|
||||
paths: Address[][],
|
||||
sources: ERC20BridgeSource[],
|
||||
): Promise<BigNumber[]> {
|
||||
return this._service.getPricesAsync(paths.map(p => ({
|
||||
tokenPath: p,
|
||||
demand: true,
|
||||
sources,
|
||||
})));
|
||||
}
|
||||
|
||||
public async getTokenInfosAsync(tokens: Address[]): Promise<TokenInfo[]> {
|
||||
return this._service.getTokensAsync(tokens);
|
||||
}
|
||||
|
||||
public async getSellLiquidityAsync(
|
||||
path: Address[],
|
||||
takerAmount: BigNumber,
|
||||
sources: ERC20BridgeSource[],
|
||||
): Promise<DexSample[][]> {
|
||||
const liquidity = await this._service.getSellLiquidityAsync(
|
||||
sources.map(s => ({
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user