UniswapFeature (#2703)
* Minimal Uniswap accessor. * Add comments * Safe math * mainnet gas benchmark * Assembler Uniswap * Selectors and addresses * Fix bugs in ABI encoders * Typo * AsmUniswap test * Fix wantAmount computation * Golfing * Bypass AllowanceTarget * Generalized asm uniswapper * Implement ordering * Fix pair computation * #6 Golfing Iron * Remove 'to' argument (saves 377 gas) * New contract api * `@0x/contracts-zero-ex`: Add `UniswapFeature` * `@0x/contract-artifacts`: Regenerate artifacts * `@0x/contract-wrappers`: Regenerate wrappers * `@0x/asset-swapper`: Add Uniswap VIP support. `@0x/asset-swapper`: Add `includeSources` support. * `@0x/contracts-zero-ex`: Fix misleading comments in `UniswapFeature`. `@0x/asset-swapper`: Fix linter errors. * `@0x/asset-swapper`: Fix source filter bugs. * `@0x/contracts-zero-ex`: `UniswapFeature`: Reduce calldata size for AllowanceTarget call `@0x/asset-swapper`: Fix failing test. * `@0x/contracts-zero-ex`: Fix ETH buy tokens not being normalized to WETH. * `@0x/asset-swapper`: Fix multi-hop weirdness with source filters. * `@0x/asset-swapper`: Fix failing test. * `@0x/asset-swapper`: Really fix that broken AS test. * `@0x/asset-swapper`: use filter objects instead of source array for valid buy and sell sources/ * `@0x/asset-swapper`: Move some source filtering logic into the sampler operations. * `@0x/contracts-zero-ex`: Address PR feedback * `@0x/contracts-zero-ex`: Fix feature version bug. * `@0x/asset-swapper`: Did I actually fix AS tests this time? Who knows. Co-authored-by: Remco Bloemen <remco@0x.org> Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com> Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
32d11d1ba5
commit
f84b375cde
@ -49,6 +49,14 @@
|
|||||||
{
|
{
|
||||||
"note": "Add updated Kyber and Mooniswap rollup to FQT",
|
"note": "Add updated Kyber and Mooniswap rollup to FQT",
|
||||||
"pr": 2692
|
"pr": 2692
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add `UniswapFeature`",
|
||||||
|
"pr": 2703
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Fix versioning (`_encodeVersion()`) bug",
|
||||||
|
"pr": 2703
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -25,6 +25,7 @@ import "./features/ITokenSpenderFeature.sol";
|
|||||||
import "./features/ISignatureValidatorFeature.sol";
|
import "./features/ISignatureValidatorFeature.sol";
|
||||||
import "./features/ITransformERC20Feature.sol";
|
import "./features/ITransformERC20Feature.sol";
|
||||||
import "./features/IMetaTransactionsFeature.sol";
|
import "./features/IMetaTransactionsFeature.sol";
|
||||||
|
import "./features/IUniswapFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Interface for a fully featured Exchange Proxy.
|
/// @dev Interface for a fully featured Exchange Proxy.
|
||||||
@ -34,7 +35,8 @@ interface IZeroEx is
|
|||||||
ITokenSpenderFeature,
|
ITokenSpenderFeature,
|
||||||
ISignatureValidatorFeature,
|
ISignatureValidatorFeature,
|
||||||
ITransformERC20Feature,
|
ITransformERC20Feature,
|
||||||
IMetaTransactionsFeature
|
IMetaTransactionsFeature,
|
||||||
|
IUniswapFeature
|
||||||
{
|
{
|
||||||
// solhint-disable state-visibility
|
// solhint-disable state-visibility
|
||||||
|
|
||||||
|
43
contracts/zero-ex/contracts/src/features/IUniswapFeature.sol
Normal file
43
contracts/zero-ex/contracts/src/features/IUniswapFeature.sol
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev VIP uniswap fill functions.
|
||||||
|
interface IUniswapFeature {
|
||||||
|
|
||||||
|
/// @dev Efficiently sell directly to uniswap/sushiswap.
|
||||||
|
/// @param tokens Sell path.
|
||||||
|
/// @param sellAmount of `tokens[0]` Amount to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
|
||||||
|
/// @param isSushi Use sushiswap if true.
|
||||||
|
/// @return buyAmount Amount of `tokens[-1]` bought.
|
||||||
|
function sellToUniswap(
|
||||||
|
IERC20TokenV06[] calldata tokens,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
bool isSushi
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 buyAmount);
|
||||||
|
}
|
366
contracts/zero-ex/contracts/src/features/UniswapFeature.sol
Normal file
366
contracts/zero-ex/contracts/src/features/UniswapFeature.sol
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
|
import "../migrations/LibMigrate.sol";
|
||||||
|
import "../external/IAllowanceTarget.sol";
|
||||||
|
import "../fixins/FixinCommon.sol";
|
||||||
|
import "./IFeature.sol";
|
||||||
|
import "./IUniswapFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev VIP uniswap fill functions.
|
||||||
|
contract UniswapFeature is
|
||||||
|
IFeature,
|
||||||
|
IUniswapFeature,
|
||||||
|
FixinCommon
|
||||||
|
{
|
||||||
|
/// @dev Name of this feature.
|
||||||
|
string public constant override FEATURE_NAME = "UniswapFeature";
|
||||||
|
/// @dev Version of this feature.
|
||||||
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||||
|
/// @dev WETH contract.
|
||||||
|
IEtherTokenV06 private immutable WETH;
|
||||||
|
/// @dev AllowanceTarget instance.
|
||||||
|
IAllowanceTarget private immutable ALLOWANCE_TARGET;
|
||||||
|
|
||||||
|
// 0xFF + address of the UniswapV2Factory contract.
|
||||||
|
uint256 constant private FF_UNISWAP_FACTORY = 0xFF5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f0000000000000000000000;
|
||||||
|
// 0xFF + address of the (Sushiswap) UniswapV2Factory contract.
|
||||||
|
uint256 constant private FF_SUSHISWAP_FACTORY = 0xFFC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac0000000000000000000000;
|
||||||
|
// Init code hash of the UniswapV2Pair contract.
|
||||||
|
uint256 constant private UNISWAP_PAIR_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
||||||
|
// Init code hash of the (Sushiswap) UniswapV2Pair contract.
|
||||||
|
uint256 constant private SUSHISWAP_PAIR_INIT_CODE_HASH = 0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303;
|
||||||
|
// Mask of the lower 20 bytes of a bytes32.
|
||||||
|
uint256 constant private ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
|
||||||
|
// ETH pseudo-token address.
|
||||||
|
uint256 constant private ETH_TOKEN_ADDRESS_32 = 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee;
|
||||||
|
// Maximum token quantity that can be swapped against the UniswapV2Pair contract.
|
||||||
|
uint256 constant private MAX_SWAP_AMOUNT = 2**112;
|
||||||
|
|
||||||
|
// bytes4(keccak256("executeCall(address,bytes)"))
|
||||||
|
uint256 constant private ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32 = 0xbca8c7b500000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("getReserves()"))
|
||||||
|
uint256 constant private UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32 = 0x0902f1ac00000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("swap(uint256,uint256,address,bytes)"))
|
||||||
|
uint256 constant private UNISWAP_PAIR_SWAP_CALL_SELECTOR_32 = 0x022c0d9f00000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("transferFrom(address,address,uint256)"))
|
||||||
|
uint256 constant private TRANSFER_FROM_CALL_SELECTOR_32 = 0x23b872dd00000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("withdraw(uint256)"))
|
||||||
|
uint256 constant private WETH_WITHDRAW_CALL_SELECTOR_32 = 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("deposit()"))
|
||||||
|
uint256 constant private WETH_DEPOSIT_CALL_SELECTOR_32 = 0xd0e30db000000000000000000000000000000000000000000000000000000000;
|
||||||
|
// bytes4(keccak256("transfer(address,uint256)"))
|
||||||
|
uint256 constant private ERC20_TRANSFER_CALL_SELECTOR_32 = 0xa9059cbb00000000000000000000000000000000000000000000000000000000;
|
||||||
|
|
||||||
|
/// @dev Construct this contract.
|
||||||
|
/// @param weth The WETH contract.
|
||||||
|
/// @param allowanceTarget The AllowanceTarget contract.
|
||||||
|
constructor(IEtherTokenV06 weth, IAllowanceTarget allowanceTarget) public {
|
||||||
|
WETH = weth;
|
||||||
|
ALLOWANCE_TARGET = allowanceTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Initialize and register this feature.
|
||||||
|
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||||
|
/// @return success `LibMigrate.SUCCESS` on success.
|
||||||
|
function migrate()
|
||||||
|
external
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
_registerFeatureFunction(this.sellToUniswap.selector);
|
||||||
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Efficiently sell directly to uniswap/sushiswap.
|
||||||
|
/// @param tokens Sell path.
|
||||||
|
/// @param sellAmount of `tokens[0]` Amount to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
|
||||||
|
/// @param isSushi Use sushiswap if true.
|
||||||
|
/// @return buyAmount Amount of `tokens[-1]` bought.
|
||||||
|
function sellToUniswap(
|
||||||
|
IERC20TokenV06[] calldata tokens,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
bool isSushi
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
override
|
||||||
|
returns (uint256 buyAmount)
|
||||||
|
{
|
||||||
|
require(tokens.length > 1, "UniswapFeature/InvalidTokensLength");
|
||||||
|
{
|
||||||
|
// Load immutables onto the stack.
|
||||||
|
IEtherTokenV06 weth = WETH;
|
||||||
|
IAllowanceTarget allowanceTarget = ALLOWANCE_TARGET;
|
||||||
|
|
||||||
|
// Store some vars in memory to get around stack limits.
|
||||||
|
assembly {
|
||||||
|
// calldataload(mload(0xA00)) == first element of `tokens` array
|
||||||
|
mstore(0xA00, add(calldataload(0x04), 0x24))
|
||||||
|
// mload(0xA20) == isSushi
|
||||||
|
mstore(0xA20, isSushi)
|
||||||
|
// mload(0xA40) == WETH
|
||||||
|
mstore(0xA40, weth)
|
||||||
|
// mload(0xA60) == ALLOWANCE_TARGET
|
||||||
|
mstore(0xA60, allowanceTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
// numPairs == tokens.length - 1
|
||||||
|
let numPairs := sub(calldataload(add(calldataload(0x04), 0x4)), 1)
|
||||||
|
// We use the previous buy amount as the sell amount for the next
|
||||||
|
// pair in a path. So for the first swap we want to set it to `sellAmount`.
|
||||||
|
buyAmount := sellAmount
|
||||||
|
let buyToken
|
||||||
|
let nextPair := 0
|
||||||
|
|
||||||
|
for {let i := 0} lt(i, numPairs) {i := add(i, 1)} {
|
||||||
|
// sellToken = tokens[i]
|
||||||
|
let sellToken := loadTokenAddress(i)
|
||||||
|
// buyToken = tokens[i+1]
|
||||||
|
buyToken := loadTokenAddress(add(i, 1))
|
||||||
|
// The canonical ordering of this token pair.
|
||||||
|
let pairOrder := lt(normalizeToken(sellToken), normalizeToken(buyToken))
|
||||||
|
|
||||||
|
// Compute the pair address if it hasn't already been computed
|
||||||
|
// from the last iteration.
|
||||||
|
let pair := nextPair
|
||||||
|
if iszero(pair) {
|
||||||
|
pair := computePairAddress(sellToken, buyToken)
|
||||||
|
nextPair := 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if iszero(i) {
|
||||||
|
switch eq(sellToken, ETH_TOKEN_ADDRESS_32)
|
||||||
|
case 0 {
|
||||||
|
// For the first pair we need to transfer sellTokens into the
|
||||||
|
// pair contract using `AllowanceTarget.executeCall()`
|
||||||
|
mstore(0xB00, ALLOWANCE_TARGET_EXECUTE_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, sellToken)
|
||||||
|
mstore(0xB24, 0x40)
|
||||||
|
mstore(0xB44, 0x64)
|
||||||
|
mstore(0xB64, TRANSFER_FROM_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB68, caller())
|
||||||
|
mstore(0xB88, pair)
|
||||||
|
mstore(0xBA8, sellAmount)
|
||||||
|
if iszero(call(gas(), mload(0xA60), 0, 0xB00, 0xC8, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
// If selling ETH, we need to wrap it to WETH and transfer to the
|
||||||
|
// pair contract.
|
||||||
|
if iszero(eq(callvalue(), sellAmount)) {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
sellToken := mload(0xA40)// Re-assign to WETH
|
||||||
|
// Call `WETH.deposit{value: sellAmount}()`
|
||||||
|
mstore(0xB00, WETH_DEPOSIT_CALL_SELECTOR_32)
|
||||||
|
if iszero(call(gas(), sellToken, sellAmount, 0xB00, 0x4, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
// Call `WETH.transfer(pair, sellAmount)`
|
||||||
|
mstore(0xB00, ERC20_TRANSFER_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, pair)
|
||||||
|
mstore(0xB24, sellAmount)
|
||||||
|
if iszero(call(gas(), sellToken, 0, 0xB00, 0x44, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No need to check results, if deposit/transfers failed the UniswapV2Pair will
|
||||||
|
// reject our trade (or it may succeed if somehow the reserve was out of sync)
|
||||||
|
// this is fine for the taker.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call pair.getReserves(), store the results at `0xC00`
|
||||||
|
mstore(0xB00, UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32)
|
||||||
|
if iszero(staticcall(gas(), pair, 0xB00, 0x4, 0xC00, 0x40)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sell amount for this hop is the previous buy amount.
|
||||||
|
let pairSellAmount := buyAmount
|
||||||
|
// Compute the buy amount based on the pair reserves.
|
||||||
|
{
|
||||||
|
let sellReserve
|
||||||
|
let buyReserve
|
||||||
|
switch iszero(pairOrder)
|
||||||
|
case 0 {
|
||||||
|
// Transpose if pair order is different.
|
||||||
|
sellReserve := mload(0xC00)
|
||||||
|
buyReserve := mload(0xC20)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
sellReserve := mload(0xC20)
|
||||||
|
buyReserve := mload(0xC00)
|
||||||
|
}
|
||||||
|
// Ensure that the sellAmount is < 2¹¹².
|
||||||
|
if gt(pairSellAmount, MAX_SWAP_AMOUNT) {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
// Pairs are in the range (0, 2¹¹²) so this shouldn't overflow.
|
||||||
|
// buyAmount = (pairSellAmount * 997 * buyReserve) /
|
||||||
|
// (pairSellAmount * 997 + sellReserve * 1000);
|
||||||
|
let sellAmountWithFee := mul(pairSellAmount, 997)
|
||||||
|
buyAmount := div(
|
||||||
|
mul(sellAmountWithFee, buyReserve),
|
||||||
|
add(sellAmountWithFee, mul(sellReserve, 1000))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let receiver
|
||||||
|
// Is this the last pair contract?
|
||||||
|
switch eq(add(i, 1), numPairs)
|
||||||
|
case 0 {
|
||||||
|
// Not the last pair contract, so forward bought tokens to
|
||||||
|
// the next pair contract.
|
||||||
|
nextPair := computePairAddress(
|
||||||
|
buyToken,
|
||||||
|
loadTokenAddress(add(i, 2))
|
||||||
|
)
|
||||||
|
receiver := nextPair
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
// The last pair contract.
|
||||||
|
// Forward directly to taker UNLESS they want ETH back.
|
||||||
|
switch eq(buyToken, ETH_TOKEN_ADDRESS_32)
|
||||||
|
case 0 {
|
||||||
|
receiver := caller()
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
receiver := address()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call pair.swap()
|
||||||
|
mstore(0xB00, UNISWAP_PAIR_SWAP_CALL_SELECTOR_32)
|
||||||
|
switch pairOrder
|
||||||
|
case 0 {
|
||||||
|
mstore(0xB04, buyAmount)
|
||||||
|
mstore(0xB24, 0)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
mstore(0xB04, 0)
|
||||||
|
mstore(0xB24, buyAmount)
|
||||||
|
}
|
||||||
|
mstore(0xB44, receiver)
|
||||||
|
mstore(0xB64, 0x80)
|
||||||
|
mstore(0xB84, 0)
|
||||||
|
if iszero(call(gas(), pair, 0, 0xB00, 0xA4, 0, 0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
} // End for-loop.
|
||||||
|
|
||||||
|
// If buying ETH, unwrap the WETH first
|
||||||
|
if eq(buyToken, ETH_TOKEN_ADDRESS_32) {
|
||||||
|
// Call `WETH.withdraw(buyAmount)`
|
||||||
|
mstore(0xB00, WETH_WITHDRAW_CALL_SELECTOR_32)
|
||||||
|
mstore(0xB04, buyAmount)
|
||||||
|
if iszero(call(gas(), mload(0xA40), 0, 0xB00, 0x24, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
// Transfer ETH to the caller.
|
||||||
|
if iszero(call(gas(), caller(), buyAmount, 0xB00, 0x0, 0x00, 0x0)) {
|
||||||
|
bubbleRevert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions ///////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Load a token address from the `tokens` calldata argument.
|
||||||
|
function loadTokenAddress(idx) -> addr {
|
||||||
|
addr := and(ADDRESS_MASK, calldataload(add(mload(0xA00), mul(idx, 0x20))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ETH pseudo-token addresses to WETH.
|
||||||
|
function normalizeToken(token) -> normalized {
|
||||||
|
normalized := token
|
||||||
|
// Translate ETH pseudo-tokens to WETH.
|
||||||
|
if eq(token, ETH_TOKEN_ADDRESS_32) {
|
||||||
|
normalized := mload(0xA40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the address of the UniswapV2Pair contract given two
|
||||||
|
// tokens.
|
||||||
|
function computePairAddress(tokenA, tokenB) -> pair {
|
||||||
|
// Convert ETH pseudo-token addresses to WETH.
|
||||||
|
tokenA := normalizeToken(tokenA)
|
||||||
|
tokenB := normalizeToken(tokenB)
|
||||||
|
// There is one contract for every combination of tokens,
|
||||||
|
// which is deployed using CREATE2.
|
||||||
|
// The derivation of this address is given by:
|
||||||
|
// address(keccak256(abi.encodePacked(
|
||||||
|
// bytes(0xFF),
|
||||||
|
// address(UNISWAP_FACTORY_ADDRESS),
|
||||||
|
// keccak256(abi.encodePacked(
|
||||||
|
// tokenA < tokenB ? tokenA : tokenB,
|
||||||
|
// tokenA < tokenB ? tokenB : tokenA,
|
||||||
|
// )),
|
||||||
|
// bytes32(UNISWAP_PAIR_INIT_CODE_HASH),
|
||||||
|
// )));
|
||||||
|
|
||||||
|
// Compute the salt (the hash of the sorted tokens).
|
||||||
|
// Tokens are written in reverse memory order to packed encode
|
||||||
|
// them as two 20-byte values in a 40-byte chunk of memory
|
||||||
|
// starting at 0xB0C.
|
||||||
|
switch lt(tokenA, tokenB)
|
||||||
|
case 0 {
|
||||||
|
mstore(0xB14, tokenA)
|
||||||
|
mstore(0xB00, tokenB)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
mstore(0xB14, tokenB)
|
||||||
|
mstore(0xB00, tokenA)
|
||||||
|
}
|
||||||
|
let salt := keccak256(0xB0C, 0x28)
|
||||||
|
// Compute the pair address by hashing all the components together.
|
||||||
|
switch mload(0xA20) // isSushi
|
||||||
|
case 0 {
|
||||||
|
mstore(0xB00, FF_UNISWAP_FACTORY)
|
||||||
|
mstore(0xB15, salt)
|
||||||
|
mstore(0xB35, UNISWAP_PAIR_INIT_CODE_HASH)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
mstore(0xB00, FF_SUSHISWAP_FACTORY)
|
||||||
|
mstore(0xB15, salt)
|
||||||
|
mstore(0xB35, SUSHISWAP_PAIR_INIT_CODE_HASH)
|
||||||
|
}
|
||||||
|
pair := and(ADDRESS_MASK, keccak256(0xB00, 0x55))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revert with the return data from the most recent call.
|
||||||
|
function bubbleRevert() {
|
||||||
|
returndatacopy(0, 0, returndatasize())
|
||||||
|
revert(0, returndatasize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revert if we bought too little.
|
||||||
|
// TODO: replace with rich revert?
|
||||||
|
require(buyAmount >= minBuyAmount, "UniswapFeature/UnderBought");
|
||||||
|
}
|
||||||
|
}
|
@ -81,6 +81,6 @@ abstract contract FixinCommon {
|
|||||||
pure
|
pure
|
||||||
returns (uint256 encodedVersion)
|
returns (uint256 encodedVersion)
|
||||||
{
|
{
|
||||||
return (major << 64) | (minor << 32) | revision;
|
return (uint256(major) << 64) | (uint256(minor) << 32) | uint256(revision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ library LibERC20Transformer {
|
|||||||
|
|
||||||
/// @dev ETH pseudo-token address.
|
/// @dev ETH pseudo-token address.
|
||||||
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||||
|
/// @dev ETH pseudo-token.
|
||||||
|
IERC20TokenV06 constant internal ETH_TOKEN = IERC20TokenV06(ETH_TOKEN_ADDRESS);
|
||||||
/// @dev Return value indicating success in `IERC20Transformer.transform()`.
|
/// @dev Return value indicating success in `IERC20Transformer.transform()`.
|
||||||
/// This is just `keccak256('TRANSFORMER_SUCCESS')`.
|
/// This is just `keccak256('TRANSFORMER_SUCCESS')`.
|
||||||
bytes4 constant internal TRANSFORMER_SUCCESS = 0x13c9929e;
|
bytes4 constant internal TRANSFORMER_SUCCESS = 0x13c9929e;
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter",
|
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
|
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -32,6 +32,7 @@ import * as ISimpleFunctionRegistryFeature from '../test/generated-artifacts/ISi
|
|||||||
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
|
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
|
||||||
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
|
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
|
||||||
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
|
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
|
||||||
|
import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json';
|
||||||
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
|
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
|
||||||
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
||||||
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
||||||
@ -96,6 +97,7 @@ import * as TokenSpenderFeature from '../test/generated-artifacts/TokenSpenderFe
|
|||||||
import * as Transformer from '../test/generated-artifacts/Transformer.json';
|
import * as Transformer from '../test/generated-artifacts/Transformer.json';
|
||||||
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
|
import * as TransformERC20Feature from '../test/generated-artifacts/TransformERC20Feature.json';
|
||||||
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
|
import * as TransformerDeployer from '../test/generated-artifacts/TransformerDeployer.json';
|
||||||
|
import * as UniswapFeature from '../test/generated-artifacts/UniswapFeature.json';
|
||||||
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
||||||
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
@ -124,12 +126,14 @@ export const artifacts = {
|
|||||||
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
|
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
|
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
|
||||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||||
|
IUniswapFeature: IUniswapFeature as ContractArtifact,
|
||||||
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
||||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||||
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
|
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
|
||||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
|
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
|
||||||
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
||||||
|
UniswapFeature: UniswapFeature as ContractArtifact,
|
||||||
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
||||||
FixinCommon: FixinCommon as ContractArtifact,
|
FixinCommon: FixinCommon as ContractArtifact,
|
||||||
FixinEIP712: FixinEIP712 as ContractArtifact,
|
FixinEIP712: FixinEIP712 as ContractArtifact,
|
||||||
|
@ -29,6 +29,7 @@ export * from '../test/generated-wrappers/i_simple_function_registry_feature';
|
|||||||
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
|
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
|
||||||
export * from '../test/generated-wrappers/i_token_spender_feature';
|
export * from '../test/generated-wrappers/i_token_spender_feature';
|
||||||
export * from '../test/generated-wrappers/i_transform_erc20_feature';
|
export * from '../test/generated-wrappers/i_transform_erc20_feature';
|
||||||
|
export * from '../test/generated-wrappers/i_uniswap_feature';
|
||||||
export * from '../test/generated-wrappers/i_zero_ex';
|
export * from '../test/generated-wrappers/i_zero_ex';
|
||||||
export * from '../test/generated-wrappers/initial_migration';
|
export * from '../test/generated-wrappers/initial_migration';
|
||||||
export * from '../test/generated-wrappers/lib_bootstrap';
|
export * from '../test/generated-wrappers/lib_bootstrap';
|
||||||
@ -94,5 +95,6 @@ export * from '../test/generated-wrappers/token_spender_feature';
|
|||||||
export * from '../test/generated-wrappers/transform_erc20_feature';
|
export * from '../test/generated-wrappers/transform_erc20_feature';
|
||||||
export * from '../test/generated-wrappers/transformer';
|
export * from '../test/generated-wrappers/transformer';
|
||||||
export * from '../test/generated-wrappers/transformer_deployer';
|
export * from '../test/generated-wrappers/transformer_deployer';
|
||||||
|
export * from '../test/generated-wrappers/uniswap_feature';
|
||||||
export * from '../test/generated-wrappers/weth_transformer';
|
export * from '../test/generated-wrappers/weth_transformer';
|
||||||
export * from '../test/generated-wrappers/zero_ex';
|
export * from '../test/generated-wrappers/zero_ex';
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
|
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
|
||||||
"test/generated-artifacts/ITokenSpenderFeature.json",
|
"test/generated-artifacts/ITokenSpenderFeature.json",
|
||||||
"test/generated-artifacts/ITransformERC20Feature.json",
|
"test/generated-artifacts/ITransformERC20Feature.json",
|
||||||
|
"test/generated-artifacts/IUniswapFeature.json",
|
||||||
"test/generated-artifacts/IZeroEx.json",
|
"test/generated-artifacts/IZeroEx.json",
|
||||||
"test/generated-artifacts/InitialMigration.json",
|
"test/generated-artifacts/InitialMigration.json",
|
||||||
"test/generated-artifacts/LibBootstrap.json",
|
"test/generated-artifacts/LibBootstrap.json",
|
||||||
@ -117,6 +118,7 @@
|
|||||||
"test/generated-artifacts/TransformERC20Feature.json",
|
"test/generated-artifacts/TransformERC20Feature.json",
|
||||||
"test/generated-artifacts/Transformer.json",
|
"test/generated-artifacts/Transformer.json",
|
||||||
"test/generated-artifacts/TransformerDeployer.json",
|
"test/generated-artifacts/TransformerDeployer.json",
|
||||||
|
"test/generated-artifacts/UniswapFeature.json",
|
||||||
"test/generated-artifacts/WethTransformer.json",
|
"test/generated-artifacts/WethTransformer.json",
|
||||||
"test/generated-artifacts/ZeroEx.json"
|
"test/generated-artifacts/ZeroEx.json"
|
||||||
],
|
],
|
||||||
|
@ -109,6 +109,14 @@
|
|||||||
{
|
{
|
||||||
"note": "Added `SushiSwap`",
|
"note": "Added `SushiSwap`",
|
||||||
"pr": 2698
|
"pr": 2698
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add uniswap VIP support",
|
||||||
|
"pr": 2703
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add `includedSources` support",
|
||||||
|
"pr": 2703
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -71,6 +71,7 @@ const DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS: ExchangeProxyContractOpts
|
|||||||
sellTokenFeeAmount: ZERO_AMOUNT,
|
sellTokenFeeAmount: ZERO_AMOUNT,
|
||||||
},
|
},
|
||||||
refundReceiver: NULL_ADDRESS,
|
refundReceiver: NULL_ADDRESS,
|
||||||
|
isMetaTransaction: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
|
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
|
||||||
|
@ -27,11 +27,12 @@ import {
|
|||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { assert } from '../utils/assert';
|
import { assert } from '../utils/assert';
|
||||||
|
import { ERC20BridgeSource, UniswapV2FillData } from '../utils/market_operation_utils/types';
|
||||||
import { getTokenFromAssetData } from '../utils/utils';
|
import { getTokenFromAssetData } from '../utils/utils';
|
||||||
|
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
||||||
const { NULL_ADDRESS } = constants;
|
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
|
||||||
|
|
||||||
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||||
public readonly provider: ZeroExProvider;
|
public readonly provider: ZeroExProvider;
|
||||||
@ -82,16 +83,44 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
||||||
): Promise<CalldataInfo> {
|
): Promise<CalldataInfo> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
const optsWithDefaults: ExchangeProxyContractOpts = {
|
||||||
const { refundReceiver, affiliateFee, isFromETH, isToETH } = {
|
|
||||||
...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
...constants.DEFAULT_EXCHANGE_PROXY_EXTENSION_CONTRACT_OPTS,
|
||||||
...opts.extensionContractOpts,
|
...opts.extensionContractOpts,
|
||||||
} as ExchangeProxyContractOpts;
|
};
|
||||||
|
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||||
|
const { refundReceiver, affiliateFee, isFromETH, isToETH } = optsWithDefaults;
|
||||||
|
|
||||||
const sellToken = getTokenFromAssetData(quote.takerAssetData);
|
const sellToken = getTokenFromAssetData(quote.takerAssetData);
|
||||||
const buyToken = getTokenFromAssetData(quote.makerAssetData);
|
const buyToken = getTokenFromAssetData(quote.makerAssetData);
|
||||||
const sellAmount = quote.worstCaseQuoteInfo.totalTakerAssetAmount;
|
const sellAmount = quote.worstCaseQuoteInfo.totalTakerAssetAmount;
|
||||||
|
|
||||||
|
// VIP routes.
|
||||||
|
if (isDirectUniswapCompatible(quote, optsWithDefaults)) {
|
||||||
|
const source = quote.orders[0].fills[0].source;
|
||||||
|
const fillData = quote.orders[0].fills[0].fillData as UniswapV2FillData;
|
||||||
|
return {
|
||||||
|
calldataHexString: this._exchangeProxy
|
||||||
|
.sellToUniswap(
|
||||||
|
fillData.tokenAddressPath.map((a, i) => {
|
||||||
|
if (i === 0 && isFromETH) {
|
||||||
|
return ETH_TOKEN_ADDRESS;
|
||||||
|
}
|
||||||
|
if (i === fillData.tokenAddressPath.length - 1 && isToETH) {
|
||||||
|
return ETH_TOKEN_ADDRESS;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}),
|
||||||
|
sellAmount,
|
||||||
|
quote.worstCaseQuoteInfo.makerAssetAmount,
|
||||||
|
source === ERC20BridgeSource.SushiSwap,
|
||||||
|
)
|
||||||
|
.getABIEncodedTransactionData(),
|
||||||
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
|
toAddress: this._exchangeProxy.address,
|
||||||
|
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Build up the transforms.
|
// Build up the transforms.
|
||||||
const transforms = [];
|
const transforms = [];
|
||||||
if (isFromETH) {
|
if (isFromETH) {
|
||||||
@ -232,3 +261,29 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||||
return quote.type === MarketOperation.Buy;
|
return quote.type === MarketOperation.Buy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDirectUniswapCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
|
||||||
|
// Must not be a mtx.
|
||||||
|
if (opts.isMetaTransaction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Must not have an affiliate fee.
|
||||||
|
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Must be a single order.
|
||||||
|
if (quote.orders.length !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const order = quote.orders[0];
|
||||||
|
// With a single underlying fill/source.
|
||||||
|
if (order.fills.length !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const fill = order.fills[0];
|
||||||
|
// And that fill must be uniswap v2 or sushiswap.
|
||||||
|
if (![ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap].includes(fill.source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@ import { calculateLiquidity } from './utils/calculate_liquidity';
|
|||||||
import { MarketOperationUtils } from './utils/market_operation_utils';
|
import { MarketOperationUtils } from './utils/market_operation_utils';
|
||||||
import { createDummyOrderForSampler } from './utils/market_operation_utils/orders';
|
import { createDummyOrderForSampler } from './utils/market_operation_utils/orders';
|
||||||
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
||||||
|
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
MarketDepth,
|
MarketDepth,
|
||||||
@ -421,13 +422,13 @@ export class SwapQuoter {
|
|||||||
assert.isString('takerTokenAddress', takerTokenAddress);
|
assert.isString('takerTokenAddress', takerTokenAddress);
|
||||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||||
let [sellOrders, buyOrders] =
|
const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
||||||
options.excludedSources && options.excludedSources.includes(ERC20BridgeSource.Native)
|
let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||||
? [[], []]
|
? [[], []]
|
||||||
: await Promise.all([
|
: await Promise.all([
|
||||||
this.orderbook.getOrdersAsync(makerAssetData, takerAssetData),
|
this.orderbook.getOrdersAsync(makerAssetData, takerAssetData),
|
||||||
this.orderbook.getOrdersAsync(takerAssetData, makerAssetData),
|
this.orderbook.getOrdersAsync(takerAssetData, makerAssetData),
|
||||||
]);
|
]);
|
||||||
if (!sellOrders || sellOrders.length === 0) {
|
if (!sellOrders || sellOrders.length === 0) {
|
||||||
sellOrders = [
|
sellOrders = [
|
||||||
{
|
{
|
||||||
@ -652,12 +653,14 @@ export class SwapQuoter {
|
|||||||
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
gasPrice = await this.getGasPriceEstimationOrThrowAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceFilters = new SourceFilters([], opts.excludedSources, opts.includedSources);
|
||||||
|
|
||||||
// If RFQT is enabled and `nativeExclusivelyRFQT` is set, then `ERC20BridgeSource.Native` should
|
// If RFQT is enabled and `nativeExclusivelyRFQT` is set, then `ERC20BridgeSource.Native` should
|
||||||
// never be excluded.
|
// never be excluded.
|
||||||
if (
|
if (
|
||||||
opts.rfqt &&
|
opts.rfqt &&
|
||||||
opts.rfqt.nativeExclusivelyRFQT === true &&
|
opts.rfqt.nativeExclusivelyRFQT === true &&
|
||||||
opts.excludedSources.includes(ERC20BridgeSource.Native)
|
!sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||||
) {
|
) {
|
||||||
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQT" is set');
|
throw new Error('Native liquidity cannot be excluded if "rfqt.nativeExclusivelyRFQT" is set');
|
||||||
}
|
}
|
||||||
@ -666,7 +669,7 @@ export class SwapQuoter {
|
|||||||
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
const orderBatchPromises: Array<Promise<SignedOrder[]>> = [];
|
||||||
|
|
||||||
const skipOpenOrderbook =
|
const skipOpenOrderbook =
|
||||||
opts.excludedSources.includes(ERC20BridgeSource.Native) ||
|
!sourceFilters.isAllowed(ERC20BridgeSource.Native) ||
|
||||||
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true);
|
(opts.rfqt && opts.rfqt.nativeExclusivelyRFQT === true);
|
||||||
if (!skipOpenOrderbook) {
|
if (!skipOpenOrderbook) {
|
||||||
orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book
|
orderBatchPromises.push(this._getSignedOrdersAsync(makerAssetData, takerAssetData)); // order book
|
||||||
@ -685,7 +688,7 @@ export class SwapQuoter {
|
|||||||
opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
|
opts.rfqt.intentOnFilling && // The requestor is asking for a firm quote
|
||||||
opts.rfqt.apiKey &&
|
opts.rfqt.apiKey &&
|
||||||
this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided
|
this._isApiKeyWhitelisted(opts.rfqt.apiKey) && // A valid API key was provided
|
||||||
!opts.excludedSources.includes(ERC20BridgeSource.Native) // Native liquidity is not excluded
|
sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
|
||||||
) {
|
) {
|
||||||
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
if (!opts.rfqt.takerAddress || opts.rfqt.takerAddress === constants.NULL_ADDRESS) {
|
||||||
throw new Error('RFQ-T requests must specify a taker address');
|
throw new Error('RFQ-T requests must specify a taker address');
|
||||||
|
@ -160,6 +160,7 @@ export interface ExchangeProxyContractOpts {
|
|||||||
isToETH: boolean;
|
isToETH: boolean;
|
||||||
affiliateFee: AffiliateFee;
|
affiliateFee: AffiliateFee;
|
||||||
refundReceiver: string | ExchangeProxyRefundReceiver;
|
refundReceiver: string | ExchangeProxyRefundReceiver;
|
||||||
|
isMetaTransaction: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetExtensionContractTypeOpts {
|
export interface GetExtensionContractTypeOpts {
|
||||||
|
@ -2,8 +2,6 @@ import { BigNumber } from '@0x/utils';
|
|||||||
import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
||||||
import { Decimal } from 'decimal.js';
|
import { Decimal } from 'decimal.js';
|
||||||
|
|
||||||
import { ERC20BridgeSource } from './types';
|
|
||||||
|
|
||||||
// tslint:disable:boolean-naming
|
// tslint:disable:boolean-naming
|
||||||
|
|
||||||
export interface BalancerPool {
|
export interface BalancerPool {
|
||||||
@ -67,10 +65,10 @@ export class BalancerPoolsCache {
|
|||||||
public howToSampleBalancer(
|
public howToSampleBalancer(
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
excludedSources: ERC20BridgeSource[],
|
isAllowedSource: boolean,
|
||||||
): { onChain: boolean; offChain: boolean } {
|
): { onChain: boolean; offChain: boolean } {
|
||||||
// If Balancer is excluded as a source, do not sample.
|
// If Balancer is excluded as a source, do not sample.
|
||||||
if (excludedSources.includes(ERC20BridgeSource.Balancer)) {
|
if (!isAllowedSource) {
|
||||||
return { onChain: false, offChain: false };
|
return { onChain: false, offChain: false };
|
||||||
}
|
}
|
||||||
const cachedBalancerPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
const cachedBalancerPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
import { SourceFilters } from './source_filters';
|
||||||
import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types';
|
import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOpts } from './types';
|
||||||
|
|
||||||
// tslint:disable: custom-no-magic-numbers
|
// tslint:disable: custom-no-magic-numbers
|
||||||
@ -7,7 +8,8 @@ import { CurveFunctionSelectors, CurveInfo, ERC20BridgeSource, GetMarketOrdersOp
|
|||||||
/**
|
/**
|
||||||
* Valid sources for market sell.
|
* Valid sources for market sell.
|
||||||
*/
|
*/
|
||||||
export const SELL_SOURCES = [
|
export const SELL_SOURCE_FILTER = new SourceFilters([
|
||||||
|
ERC20BridgeSource.Native,
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
ERC20BridgeSource.UniswapV2,
|
ERC20BridgeSource.UniswapV2,
|
||||||
ERC20BridgeSource.Eth2Dai,
|
ERC20BridgeSource.Eth2Dai,
|
||||||
@ -20,29 +22,36 @@ export const SELL_SOURCES = [
|
|||||||
ERC20BridgeSource.Mooniswap,
|
ERC20BridgeSource.Mooniswap,
|
||||||
ERC20BridgeSource.Swerve,
|
ERC20BridgeSource.Swerve,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
];
|
ERC20BridgeSource.MultiHop,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid sources for market buy.
|
* Valid sources for market buy.
|
||||||
*/
|
*/
|
||||||
export const BUY_SOURCES = [
|
export const BUY_SOURCE_FILTER = new SourceFilters(
|
||||||
ERC20BridgeSource.Uniswap,
|
[
|
||||||
ERC20BridgeSource.UniswapV2,
|
ERC20BridgeSource.Native,
|
||||||
ERC20BridgeSource.Eth2Dai,
|
ERC20BridgeSource.Uniswap,
|
||||||
ERC20BridgeSource.Kyber,
|
ERC20BridgeSource.UniswapV2,
|
||||||
ERC20BridgeSource.Curve,
|
ERC20BridgeSource.Eth2Dai,
|
||||||
ERC20BridgeSource.Balancer,
|
ERC20BridgeSource.Kyber,
|
||||||
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
|
ERC20BridgeSource.Curve,
|
||||||
ERC20BridgeSource.MStable,
|
ERC20BridgeSource.Balancer,
|
||||||
ERC20BridgeSource.Mooniswap,
|
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
|
||||||
ERC20BridgeSource.Swerve,
|
ERC20BridgeSource.MStable,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.Mooniswap,
|
||||||
];
|
ERC20BridgeSource.Swerve,
|
||||||
|
ERC20BridgeSource.SushiSwap,
|
||||||
|
ERC20BridgeSource.MultiHop,
|
||||||
|
],
|
||||||
|
[ERC20BridgeSource.MultiBridge],
|
||||||
|
);
|
||||||
|
|
||||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||||
// tslint:disable-next-line: custom-no-magic-numbers
|
// tslint:disable-next-line: custom-no-magic-numbers
|
||||||
runLimit: 2 ** 15,
|
runLimit: 2 ** 15,
|
||||||
excludedSources: [],
|
excludedSources: [],
|
||||||
|
includedSources: [],
|
||||||
bridgeSlippage: 0.005,
|
bridgeSlippage: 0.005,
|
||||||
maxFallbackSlippage: 0.05,
|
maxFallbackSlippage: 0.05,
|
||||||
numSamples: 13,
|
numSamples: 13,
|
||||||
|
@ -6,15 +6,14 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { MarketOperation } from '../../types';
|
||||||
import { QuoteRequestor } from '../quote_requestor';
|
import { QuoteRequestor } from '../quote_requestor';
|
||||||
import { difference } from '../utils';
|
|
||||||
|
|
||||||
import { generateQuoteReport } from './../quote_report_generator';
|
import { generateQuoteReport } from './../quote_report_generator';
|
||||||
import {
|
import {
|
||||||
BUY_SOURCES,
|
BUY_SOURCE_FILTER,
|
||||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||||
FEE_QUOTE_SOURCES,
|
FEE_QUOTE_SOURCES,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
SELL_SOURCES,
|
SELL_SOURCE_FILTER,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
||||||
@ -28,6 +27,7 @@ import {
|
|||||||
} from './orders';
|
} from './orders';
|
||||||
import { findOptimalPathAsync } from './path_optimizer';
|
import { findOptimalPathAsync } from './path_optimizer';
|
||||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||||
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
AggregationError,
|
AggregationError,
|
||||||
DexSample,
|
DexSample,
|
||||||
@ -58,8 +58,7 @@ export async function getRfqtIndicativeQuotesAsync(
|
|||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
opts: Partial<GetMarketOrdersOpts>,
|
opts: Partial<GetMarketOrdersOpts>,
|
||||||
): Promise<RFQTIndicativeQuote[]> {
|
): Promise<RFQTIndicativeQuote[]> {
|
||||||
const hasExcludedNativeLiquidity = opts.excludedSources && opts.excludedSources.includes(ERC20BridgeSource.Native);
|
if (opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
|
||||||
if (!hasExcludedNativeLiquidity && opts.rfqt && opts.rfqt.isIndicative === true && opts.rfqt.quoteRequestor) {
|
|
||||||
return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
|
return opts.rfqt.quoteRequestor.requestRfqtIndicativeQuotesAsync(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
@ -75,6 +74,9 @@ export async function getRfqtIndicativeQuotesAsync(
|
|||||||
export class MarketOperationUtils {
|
export class MarketOperationUtils {
|
||||||
private readonly _wethAddress: string;
|
private readonly _wethAddress: string;
|
||||||
private readonly _multiBridge: string;
|
private readonly _multiBridge: string;
|
||||||
|
private readonly _sellSources: SourceFilters;
|
||||||
|
private readonly _buySources: SourceFilters;
|
||||||
|
private readonly _feeSources = new SourceFilters(FEE_QUOTE_SOURCES);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _sampler: DexOrderSampler,
|
private readonly _sampler: DexOrderSampler,
|
||||||
@ -85,6 +87,15 @@ export class MarketOperationUtils {
|
|||||||
) {
|
) {
|
||||||
this._wethAddress = contractAddresses.etherToken.toLowerCase();
|
this._wethAddress = contractAddresses.etherToken.toLowerCase();
|
||||||
this._multiBridge = contractAddresses.multiBridge.toLowerCase();
|
this._multiBridge = contractAddresses.multiBridge.toLowerCase();
|
||||||
|
const optionalQuoteSources = [];
|
||||||
|
if (this._liquidityProviderRegistry !== NULL_ADDRESS) {
|
||||||
|
optionalQuoteSources.push(ERC20BridgeSource.LiquidityProvider);
|
||||||
|
}
|
||||||
|
if (this._multiBridge !== NULL_ADDRESS) {
|
||||||
|
optionalQuoteSources.push(ERC20BridgeSource.MultiBridge);
|
||||||
|
}
|
||||||
|
this._buySources = BUY_SOURCE_FILTER.validate(optionalQuoteSources);
|
||||||
|
this._sellSources = SELL_SOURCE_FILTER.validate(optionalQuoteSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,11 +116,18 @@ export class MarketOperationUtils {
|
|||||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||||
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
||||||
const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||||
|
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||||
|
const feeSourceFilters = this._feeSources.merge(requestFilters);
|
||||||
|
const quoteSourceFilters = this._sellSources.merge(requestFilters);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onChain: sampleBalancerOnChain,
|
onChain: sampleBalancerOnChain,
|
||||||
offChain: sampleBalancerOffChain,
|
offChain: sampleBalancerOffChain,
|
||||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
|
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
||||||
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
||||||
|
);
|
||||||
|
|
||||||
// Call the sampler contract.
|
// Call the sampler contract.
|
||||||
const samplerPromise = this._sampler.executeAsync(
|
const samplerPromise = this._sampler.executeAsync(
|
||||||
@ -117,7 +135,7 @@ export class MarketOperationUtils {
|
|||||||
this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||||
// Get ETH -> maker token price.
|
// Get ETH -> maker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getMedianSellRate(
|
||||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
feeSourceFilters.sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
@ -127,7 +145,7 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
// Get ETH -> taker token price.
|
// Get ETH -> taker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getMedianSellRate(
|
||||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
feeSourceFilters.sources,
|
||||||
takerToken,
|
takerToken,
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
@ -137,10 +155,7 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
// Get sell quotes for taker -> maker.
|
// Get sell quotes for taker -> maker.
|
||||||
this._sampler.getSellQuotes(
|
this._sampler.getSellQuotes(
|
||||||
difference(
|
quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
|
||||||
SELL_SOURCES.concat(this._optionalSources()),
|
|
||||||
_opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
|
|
||||||
),
|
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
sampleAmounts,
|
sampleAmounts,
|
||||||
@ -148,37 +163,34 @@ export class MarketOperationUtils {
|
|||||||
this._liquidityProviderRegistry,
|
this._liquidityProviderRegistry,
|
||||||
this._multiBridge,
|
this._multiBridge,
|
||||||
),
|
),
|
||||||
_opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
|
this._sampler.getTwoHopSellQuotes(
|
||||||
? DexOrderSampler.constant([])
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||||
: this._sampler.getTwoHopSellQuotes(
|
makerToken,
|
||||||
difference(
|
takerToken,
|
||||||
SELL_SOURCES.concat(this._optionalSources()),
|
takerAmount,
|
||||||
_opts.excludedSources.concat(ERC20BridgeSource.MultiBridge),
|
this._tokenAdjacencyGraph,
|
||||||
),
|
this._wethAddress,
|
||||||
makerToken,
|
this._liquidityProviderRegistry,
|
||||||
takerToken,
|
),
|
||||||
takerAmount,
|
|
||||||
this._tokenAdjacencyGraph,
|
|
||||||
this._wethAddress,
|
|
||||||
this._liquidityProviderRegistry,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const rfqtPromise = getRfqtIndicativeQuotesAsync(
|
const rfqtPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||||
nativeOrders[0].makerAssetData,
|
? getRfqtIndicativeQuotesAsync(
|
||||||
nativeOrders[0].takerAssetData,
|
nativeOrders[0].makerAssetData,
|
||||||
MarketOperation.Sell,
|
nativeOrders[0].takerAssetData,
|
||||||
takerAmount,
|
MarketOperation.Sell,
|
||||||
_opts,
|
takerAmount,
|
||||||
);
|
_opts,
|
||||||
|
)
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
const offChainBalancerPromise = sampleBalancerOffChain
|
const offChainBalancerPromise = sampleBalancerOffChain
|
||||||
? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||||
: Promise.resolve([]);
|
: Promise.resolve([]);
|
||||||
|
|
||||||
const offChainBancorPromise = _opts.excludedSources.includes(ERC20BridgeSource.Bancor)
|
const offChainBancorPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Bancor)
|
||||||
? Promise.resolve([])
|
? this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, [takerAmount])
|
||||||
: this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts);
|
: Promise.resolve([]);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
||||||
@ -220,11 +232,18 @@ export class MarketOperationUtils {
|
|||||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||||
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
||||||
const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||||
|
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||||
|
const feeSourceFilters = this._feeSources.merge(requestFilters);
|
||||||
|
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onChain: sampleBalancerOnChain,
|
onChain: sampleBalancerOnChain,
|
||||||
offChain: sampleBalancerOffChain,
|
offChain: sampleBalancerOffChain,
|
||||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
|
} = this._sampler.balancerPoolsCache.howToSampleBalancer(
|
||||||
|
takerToken,
|
||||||
|
makerToken,
|
||||||
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.Balancer),
|
||||||
|
);
|
||||||
|
|
||||||
// Call the sampler contract.
|
// Call the sampler contract.
|
||||||
const samplerPromise = this._sampler.executeAsync(
|
const samplerPromise = this._sampler.executeAsync(
|
||||||
@ -232,7 +251,7 @@ export class MarketOperationUtils {
|
|||||||
this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||||
// Get ETH -> makerToken token price.
|
// Get ETH -> makerToken token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getMedianSellRate(
|
||||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
feeSourceFilters.sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
@ -242,7 +261,7 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
// Get ETH -> taker token price.
|
// Get ETH -> taker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getMedianSellRate(
|
||||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
feeSourceFilters.sources,
|
||||||
takerToken,
|
takerToken,
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
@ -252,49 +271,38 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
// Get buy quotes for taker -> maker.
|
// Get buy quotes for taker -> maker.
|
||||||
this._sampler.getBuyQuotes(
|
this._sampler.getBuyQuotes(
|
||||||
difference(
|
quoteSourceFilters.exclude(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer).sources,
|
||||||
BUY_SOURCES.concat(
|
|
||||||
this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [],
|
|
||||||
),
|
|
||||||
_opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
|
|
||||||
),
|
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
sampleAmounts,
|
sampleAmounts,
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
this._liquidityProviderRegistry,
|
this._liquidityProviderRegistry,
|
||||||
),
|
),
|
||||||
_opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
|
this._sampler.getTwoHopBuyQuotes(
|
||||||
? DexOrderSampler.constant([])
|
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
|
||||||
: this._sampler.getTwoHopBuyQuotes(
|
makerToken,
|
||||||
difference(
|
takerToken,
|
||||||
BUY_SOURCES.concat(
|
makerAmount,
|
||||||
this._liquidityProviderRegistry !== NULL_ADDRESS
|
this._tokenAdjacencyGraph,
|
||||||
? [ERC20BridgeSource.LiquidityProvider]
|
this._wethAddress,
|
||||||
: [],
|
this._liquidityProviderRegistry,
|
||||||
),
|
),
|
||||||
_opts.excludedSources,
|
|
||||||
),
|
|
||||||
makerToken,
|
|
||||||
takerToken,
|
|
||||||
makerAmount,
|
|
||||||
this._tokenAdjacencyGraph,
|
|
||||||
this._wethAddress,
|
|
||||||
this._liquidityProviderRegistry,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rfqtPromise = quoteSourceFilters.isAllowed(ERC20BridgeSource.Native)
|
||||||
|
? getRfqtIndicativeQuotesAsync(
|
||||||
|
nativeOrders[0].makerAssetData,
|
||||||
|
nativeOrders[0].takerAssetData,
|
||||||
|
MarketOperation.Buy,
|
||||||
|
makerAmount,
|
||||||
|
_opts,
|
||||||
|
)
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
const offChainBalancerPromise = sampleBalancerOffChain
|
const offChainBalancerPromise = sampleBalancerOffChain
|
||||||
? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||||
: Promise.resolve([]);
|
: Promise.resolve([]);
|
||||||
|
|
||||||
const rfqtPromise = getRfqtIndicativeQuotesAsync(
|
|
||||||
nativeOrders[0].makerAssetData,
|
|
||||||
nativeOrders[0].takerAssetData,
|
|
||||||
MarketOperation.Buy,
|
|
||||||
makerAmount,
|
|
||||||
_opts,
|
|
||||||
);
|
|
||||||
const [
|
const [
|
||||||
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
||||||
rfqtIndicativeQuotes,
|
rfqtIndicativeQuotes,
|
||||||
@ -394,14 +402,17 @@ export class MarketOperationUtils {
|
|||||||
}
|
}
|
||||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||||
|
|
||||||
const sources = difference(BUY_SOURCES, _opts.excludedSources.concat(ERC20BridgeSource.Balancer));
|
const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
|
||||||
|
const feeSourceFilters = this._feeSources.merge(requestFilters);
|
||||||
|
const quoteSourceFilters = this._buySources.merge(requestFilters);
|
||||||
|
|
||||||
const ops = [
|
const ops = [
|
||||||
...batchNativeOrders.map(orders =>
|
...batchNativeOrders.map(orders =>
|
||||||
this._sampler.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
|
this._sampler.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
|
||||||
),
|
),
|
||||||
...batchNativeOrders.map(orders =>
|
...batchNativeOrders.map(orders =>
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getMedianSellRate(
|
||||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
feeSourceFilters.sources,
|
||||||
getNativeOrderTokens(orders[0])[1],
|
getNativeOrderTokens(orders[0])[1],
|
||||||
this._wethAddress,
|
this._wethAddress,
|
||||||
ONE_ETHER,
|
ONE_ETHER,
|
||||||
@ -410,7 +421,7 @@ export class MarketOperationUtils {
|
|||||||
),
|
),
|
||||||
...batchNativeOrders.map((orders, i) =>
|
...batchNativeOrders.map((orders, i) =>
|
||||||
this._sampler.getBuyQuotes(
|
this._sampler.getBuyQuotes(
|
||||||
sources,
|
quoteSourceFilters.sources,
|
||||||
getNativeOrderTokens(orders[0])[0],
|
getNativeOrderTokens(orders[0])[0],
|
||||||
getNativeOrderTokens(orders[0])[1],
|
getNativeOrderTokens(orders[0])[1],
|
||||||
[makerAmounts[i]],
|
[makerAmounts[i]],
|
||||||
@ -593,12 +604,6 @@ export class MarketOperationUtils {
|
|||||||
: undefined;
|
: undefined;
|
||||||
return { optimizedOrders, quoteReport, isTwoHop: false };
|
return { optimizedOrders, quoteReport, isTwoHop: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
private _optionalSources(): ERC20BridgeSource[] {
|
|
||||||
return (this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : []).concat(
|
|
||||||
this._multiBridge !== NULL_ADDRESS ? [ERC20BridgeSource.MultiBridge] : [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable: max-file-line-count
|
// tslint:disable: max-file-line-count
|
||||||
|
@ -13,6 +13,7 @@ import { getKyberReserveIdsForPair } from './kyber_utils';
|
|||||||
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
|
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
|
||||||
import { getIntermediateTokens } from './multihop_utils';
|
import { getIntermediateTokens } from './multihop_utils';
|
||||||
import { SamplerContractOperation } from './sampler_contract_operation';
|
import { SamplerContractOperation } from './sampler_contract_operation';
|
||||||
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
BalancerFillData,
|
BalancerFillData,
|
||||||
BancorFillData,
|
BancorFillData,
|
||||||
@ -35,6 +36,19 @@ import {
|
|||||||
UniswapV2FillData,
|
UniswapV2FillData,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source filters for `getTwoHopBuyQuotes()` and `getTwoHopSellQuotes()`.
|
||||||
|
*/
|
||||||
|
export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([
|
||||||
|
ERC20BridgeSource.MultiHop,
|
||||||
|
ERC20BridgeSource.MultiBridge,
|
||||||
|
ERC20BridgeSource.Native,
|
||||||
|
]);
|
||||||
|
/**
|
||||||
|
* Source filters for `getSellQuotes()` and `getBuyQuotes()`.
|
||||||
|
*/
|
||||||
|
export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]);
|
||||||
|
|
||||||
// tslint:disable:no-inferred-empty-object-type no-unbound-method
|
// tslint:disable:no-inferred-empty-object-type no-unbound-method
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -553,10 +567,14 @@ export class SamplerOperations {
|
|||||||
wethAddress: string,
|
wethAddress: string,
|
||||||
liquidityProviderRegistryAddress?: string,
|
liquidityProviderRegistryAddress?: string,
|
||||||
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
|
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
|
||||||
|
const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
|
||||||
|
if (_sources.length === 0) {
|
||||||
|
return SamplerOperations.constant([]);
|
||||||
|
}
|
||||||
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
|
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
|
||||||
const subOps = intermediateTokens.map(intermediateToken => {
|
const subOps = intermediateTokens.map(intermediateToken => {
|
||||||
const firstHopOps = this._getSellQuoteOperations(
|
const firstHopOps = this._getSellQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
intermediateToken,
|
intermediateToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
[ZERO_AMOUNT],
|
[ZERO_AMOUNT],
|
||||||
@ -564,7 +582,7 @@ export class SamplerOperations {
|
|||||||
liquidityProviderRegistryAddress,
|
liquidityProviderRegistryAddress,
|
||||||
);
|
);
|
||||||
const secondHopOps = this._getSellQuoteOperations(
|
const secondHopOps = this._getSellQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
intermediateToken,
|
intermediateToken,
|
||||||
[ZERO_AMOUNT],
|
[ZERO_AMOUNT],
|
||||||
@ -624,10 +642,14 @@ export class SamplerOperations {
|
|||||||
wethAddress: string,
|
wethAddress: string,
|
||||||
liquidityProviderRegistryAddress?: string,
|
liquidityProviderRegistryAddress?: string,
|
||||||
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
|
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
|
||||||
|
const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
|
||||||
|
if (_sources.length === 0) {
|
||||||
|
return SamplerOperations.constant([]);
|
||||||
|
}
|
||||||
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
|
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
|
||||||
const subOps = intermediateTokens.map(intermediateToken => {
|
const subOps = intermediateTokens.map(intermediateToken => {
|
||||||
const firstHopOps = this._getBuyQuoteOperations(
|
const firstHopOps = this._getBuyQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
intermediateToken,
|
intermediateToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
[new BigNumber(0)],
|
[new BigNumber(0)],
|
||||||
@ -635,7 +657,7 @@ export class SamplerOperations {
|
|||||||
liquidityProviderRegistryAddress,
|
liquidityProviderRegistryAddress,
|
||||||
);
|
);
|
||||||
const secondHopOps = this._getBuyQuoteOperations(
|
const secondHopOps = this._getBuyQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
intermediateToken,
|
intermediateToken,
|
||||||
[new BigNumber(0)],
|
[new BigNumber(0)],
|
||||||
@ -776,8 +798,9 @@ export class SamplerOperations {
|
|||||||
liquidityProviderRegistryAddress?: string,
|
liquidityProviderRegistryAddress?: string,
|
||||||
multiBridgeAddress?: string,
|
multiBridgeAddress?: string,
|
||||||
): BatchedOperation<DexSample[][]> {
|
): BatchedOperation<DexSample[][]> {
|
||||||
|
const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
|
||||||
const subOps = this._getSellQuoteOperations(
|
const subOps = this._getSellQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
takerFillAmounts,
|
takerFillAmounts,
|
||||||
@ -816,8 +839,9 @@ export class SamplerOperations {
|
|||||||
wethAddress: string,
|
wethAddress: string,
|
||||||
liquidityProviderRegistryAddress?: string,
|
liquidityProviderRegistryAddress?: string,
|
||||||
): BatchedOperation<DexSample[][]> {
|
): BatchedOperation<DexSample[][]> {
|
||||||
|
const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
|
||||||
const subOps = this._getBuyQuoteOperations(
|
const subOps = this._getBuyQuoteOperations(
|
||||||
sources,
|
_sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
makerFillAmounts,
|
makerFillAmounts,
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { ERC20BridgeSource } from './types';
|
||||||
|
|
||||||
|
export class SourceFilters {
|
||||||
|
// All valid sources.
|
||||||
|
private readonly _validSources: ERC20BridgeSource[];
|
||||||
|
// Sources in `_validSources` that are not allowed.
|
||||||
|
private readonly _excludedSources: ERC20BridgeSource[];
|
||||||
|
// Sources in `_validSources` that are only allowed.
|
||||||
|
private readonly _includedSources: ERC20BridgeSource[];
|
||||||
|
|
||||||
|
public static all(): SourceFilters {
|
||||||
|
return new SourceFilters(Object.values(ERC20BridgeSource));
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
validSources: ERC20BridgeSource[] = [],
|
||||||
|
excludedSources: ERC20BridgeSource[] = [],
|
||||||
|
includedSources: ERC20BridgeSource[] = [],
|
||||||
|
) {
|
||||||
|
this._validSources = _.uniq(validSources);
|
||||||
|
this._excludedSources = _.uniq(excludedSources);
|
||||||
|
this._includedSources = _.uniq(includedSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAllowed(source: ERC20BridgeSource): boolean {
|
||||||
|
// Must be in list of valid sources.
|
||||||
|
if (this._validSources.length > 0 && !this._validSources.includes(source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Must not be excluded.
|
||||||
|
if (this._excludedSources.includes(source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If we have an inclusion list, it must be in that list.
|
||||||
|
if (this._includedSources.length > 0 && !this._includedSources.includes(source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public areAnyAllowed(sources: ERC20BridgeSource[]): boolean {
|
||||||
|
return sources.some(s => this.isAllowed(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public areAllAllowed(sources: ERC20BridgeSource[]): boolean {
|
||||||
|
return sources.every(s => this.isAllowed(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllowed(sources: ERC20BridgeSource[] = []): ERC20BridgeSource[] {
|
||||||
|
return sources.filter(s => this.isAllowed(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sources(): ERC20BridgeSource[] {
|
||||||
|
return this._validSources.filter(s => this.isAllowed(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public exclude(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
|
||||||
|
return new SourceFilters(
|
||||||
|
this._validSources,
|
||||||
|
[...this._excludedSources, ...(Array.isArray(sources) ? sources : [sources])],
|
||||||
|
this._includedSources,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public validate(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
|
||||||
|
return new SourceFilters(
|
||||||
|
[...this._validSources, ...(Array.isArray(sources) ? sources : [sources])],
|
||||||
|
this._excludedSources,
|
||||||
|
this._includedSources,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public include(sources: ERC20BridgeSource | ERC20BridgeSource[]): SourceFilters {
|
||||||
|
return new SourceFilters(this._validSources, this._excludedSources, [
|
||||||
|
...this._includedSources,
|
||||||
|
...(Array.isArray(sources) ? sources : [sources]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public merge(other: SourceFilters): SourceFilters {
|
||||||
|
let validSources = this._validSources;
|
||||||
|
if (validSources.length === 0) {
|
||||||
|
validSources = other._validSources;
|
||||||
|
} else if (other._validSources.length !== 0) {
|
||||||
|
validSources = validSources.filter(s => other._validSources.includes(s));
|
||||||
|
}
|
||||||
|
return new SourceFilters(
|
||||||
|
validSources,
|
||||||
|
[...this._excludedSources, ...other._excludedSources],
|
||||||
|
[...this._includedSources, ...other._includedSources],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -243,6 +243,11 @@ export interface GetMarketOrdersOpts {
|
|||||||
* Liquidity sources to exclude. Default is none.
|
* Liquidity sources to exclude. Default is none.
|
||||||
*/
|
*/
|
||||||
excludedSources: ERC20BridgeSource[];
|
excludedSources: ERC20BridgeSource[];
|
||||||
|
/**
|
||||||
|
* Liquidity sources to include. Default is none, which allows all supported
|
||||||
|
* sources that aren't excluded by `excludedSources`.
|
||||||
|
*/
|
||||||
|
includedSources: ERC20BridgeSource[];
|
||||||
/**
|
/**
|
||||||
* Complexity limit on the search algorithm, i.e., maximum number of
|
* Complexity limit on the search algorithm, i.e., maximum number of
|
||||||
* nodes to visit. Default is 1024.
|
* nodes to visit. Default is 1024.
|
||||||
|
@ -107,13 +107,6 @@ export function isERC20EquivalentAssetData(assetData: AssetData): assetData is E
|
|||||||
return assetDataUtils.isERC20TokenAssetData(assetData) || assetDataUtils.isERC20BridgeAssetData(assetData);
|
return assetDataUtils.isERC20TokenAssetData(assetData) || assetDataUtils.isERC20BridgeAssetData(assetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the difference between two sets.
|
|
||||||
*/
|
|
||||||
export function difference<T>(a: T[], b: T[]): T[] {
|
|
||||||
return a.filter(x => b.indexOf(x) === -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTokenFromAssetData(assetData: string): string {
|
export function getTokenFromAssetData(assetData: string): string {
|
||||||
const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
|
const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
|
||||||
if (data.assetProxyId !== AssetProxyId.ERC20 && data.assetProxyId !== AssetProxyId.ERC20Bridge) {
|
if (data.assetProxyId !== AssetProxyId.ERC20 && data.assetProxyId !== AssetProxyId.ERC20Bridge) {
|
||||||
|
@ -18,10 +18,22 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src';
|
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src';
|
||||||
import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||||
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
|
import { BalancerPoolsCache } from '../src/utils/market_operation_utils/balancer_utils';
|
||||||
import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants';
|
import {
|
||||||
|
BUY_SOURCE_FILTER,
|
||||||
|
POSITIVE_INF,
|
||||||
|
SELL_SOURCE_FILTER,
|
||||||
|
ZERO_AMOUNT,
|
||||||
|
} from '../src/utils/market_operation_utils/constants';
|
||||||
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
|
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
|
||||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types';
|
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||||
|
import {
|
||||||
|
DexSample,
|
||||||
|
ERC20BridgeSource,
|
||||||
|
FillData,
|
||||||
|
NativeFillData,
|
||||||
|
OptimizedMarketOrder,
|
||||||
|
} from '../src/utils/market_operation_utils/types';
|
||||||
|
|
||||||
const MAKER_TOKEN = randomAddress();
|
const MAKER_TOKEN = randomAddress();
|
||||||
const TAKER_TOKEN = randomAddress();
|
const TAKER_TOKEN = randomAddress();
|
||||||
@ -36,7 +48,10 @@ const DEFAULT_EXCLUDED = [
|
|||||||
ERC20BridgeSource.Bancor,
|
ERC20BridgeSource.Bancor,
|
||||||
ERC20BridgeSource.Swerve,
|
ERC20BridgeSource.Swerve,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
|
ERC20BridgeSource.MultiHop,
|
||||||
];
|
];
|
||||||
|
const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
|
||||||
|
const SELL_SOURCES = SELL_SOURCE_FILTER.sources;
|
||||||
|
|
||||||
// tslint:disable: custom-no-magic-numbers promise-function-async
|
// tslint:disable: custom-no-magic-numbers promise-function-async
|
||||||
describe('MarketOperationUtils tests', () => {
|
describe('MarketOperationUtils tests', () => {
|
||||||
@ -167,7 +182,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
fillAmounts: BigNumber[],
|
fillAmounts: BigNumber[],
|
||||||
_wethAddress: string,
|
_wethAddress: string,
|
||||||
) => {
|
) => {
|
||||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
|
return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +224,9 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
fillAmounts: BigNumber[],
|
fillAmounts: BigNumber[],
|
||||||
_wethAddress: string,
|
_wethAddress: string,
|
||||||
) => {
|
) => {
|
||||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))));
|
return BATCH_SOURCE_FILTERS.getAllowed(sources).map(s =>
|
||||||
|
createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,6 +261,27 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
return rates;
|
return rates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSortedOrderSources(side: MarketOperation, orders: OptimizedMarketOrder[]): ERC20BridgeSource[][] {
|
||||||
|
return (
|
||||||
|
orders
|
||||||
|
// Sort orders by descending rate.
|
||||||
|
.sort((a, b) =>
|
||||||
|
b.makerAssetAmount.div(b.takerAssetAmount).comparedTo(a.makerAssetAmount.div(a.takerAssetAmount)),
|
||||||
|
)
|
||||||
|
// Then sort fills by descending rate.
|
||||||
|
.map(o => {
|
||||||
|
return o.fills
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
side === MarketOperation.Sell
|
||||||
|
? b.output.div(b.input).comparedTo(a.output.div(a.input))
|
||||||
|
: b.input.div(b.output).comparedTo(a.input.div(a.output)),
|
||||||
|
)
|
||||||
|
.map(f => f.source);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const NUM_SAMPLES = 3;
|
const NUM_SAMPLES = 3;
|
||||||
|
|
||||||
interface RatesBySource {
|
interface RatesBySource {
|
||||||
@ -265,6 +303,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
[ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
|
[ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
|
||||||
[ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
|
[ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
|
||||||
[ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
|
[ERC20BridgeSource.SushiSwap]: _.times(NUM_SAMPLES, () => 0),
|
||||||
|
[ERC20BridgeSource.MultiHop]: _.times(NUM_SAMPLES, () => 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_RATES: RatesBySource = {
|
const DEFAULT_RATES: RatesBySource = {
|
||||||
@ -307,6 +346,9 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
},
|
},
|
||||||
[ERC20BridgeSource.LiquidityProvider]: { poolAddress: randomAddress() },
|
[ERC20BridgeSource.LiquidityProvider]: { poolAddress: randomAddress() },
|
||||||
[ERC20BridgeSource.SushiSwap]: { tokenAddressPath: [] },
|
[ERC20BridgeSource.SushiSwap]: { tokenAddressPath: [] },
|
||||||
|
[ERC20BridgeSource.Mooniswap]: { poolAddress: randomAddress() },
|
||||||
|
[ERC20BridgeSource.Native]: { order: createOrder() },
|
||||||
|
[ERC20BridgeSource.MultiHop]: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_OPS = {
|
const DEFAULT_OPS = {
|
||||||
@ -356,7 +398,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
|
|
||||||
const MOCK_SAMPLER = ({
|
const MOCK_SAMPLER = ({
|
||||||
async executeAsync(...ops: any[]): Promise<any[]> {
|
async executeAsync(...ops: any[]): Promise<any[]> {
|
||||||
return ops;
|
return MOCK_SAMPLER.executeBatchAsync(ops);
|
||||||
},
|
},
|
||||||
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
||||||
return ops;
|
return ops;
|
||||||
@ -377,33 +419,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
intentOnFilling: false,
|
intentOnFilling: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('returns an empty array if native liquidity is excluded from the salad', async () => {
|
it('calls RFQT', async () => {
|
||||||
const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Strict);
|
|
||||||
const result = await getRfqtIndicativeQuotesAsync(
|
|
||||||
MAKER_ASSET_DATA,
|
|
||||||
TAKER_ASSET_DATA,
|
|
||||||
MarketOperation.Sell,
|
|
||||||
new BigNumber('100e18'),
|
|
||||||
{
|
|
||||||
rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
|
|
||||||
excludedSources: [ERC20BridgeSource.Native],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result.length).to.eql(0);
|
|
||||||
requestor.verify(
|
|
||||||
r =>
|
|
||||||
r.requestRfqtIndicativeQuotesAsync(
|
|
||||||
TypeMoq.It.isAny(),
|
|
||||||
TypeMoq.It.isAny(),
|
|
||||||
TypeMoq.It.isAny(),
|
|
||||||
TypeMoq.It.isAny(),
|
|
||||||
TypeMoq.It.isAny(),
|
|
||||||
),
|
|
||||||
TypeMoq.Times.never(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls RFQT if Native source is not excluded', async () => {
|
|
||||||
const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose);
|
const requestor = TypeMoq.Mock.ofType(QuoteRequestor, TypeMoq.MockBehavior.Loose);
|
||||||
requestor
|
requestor
|
||||||
.setup(r =>
|
.setup(r =>
|
||||||
@ -424,7 +440,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
new BigNumber('100e18'),
|
new BigNumber('100e18'),
|
||||||
{
|
{
|
||||||
rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
|
rfqt: { quoteRequestor: requestor.object, ...partialRfqt },
|
||||||
excludedSources: [],
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
requestor.verifyAll();
|
requestor.verifyAll();
|
||||||
@ -481,6 +496,10 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
},
|
},
|
||||||
|
getTwoHopSellQuotes: (...args: any[]) => {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
||||||
|
},
|
||||||
getBalancerSellQuotesOffChainAsync: (
|
getBalancerSellQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -494,7 +513,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources: [],
|
excludedSources: [],
|
||||||
});
|
});
|
||||||
expect(sourcesPolled.sort()).to.deep.equals(SELL_SOURCES.slice().sort());
|
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(SELL_SOURCES.slice().sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
|
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
|
||||||
@ -504,6 +523,13 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
);
|
);
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: fn,
|
getSellQuotes: fn,
|
||||||
|
getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
args.sources.push(ERC20BridgeSource.MultiHop);
|
||||||
|
args.sources.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopSellQuotes(..._args);
|
||||||
|
},
|
||||||
getBalancerSellQuotesOffChainAsync: (
|
getBalancerSellQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -524,20 +550,27 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources: [],
|
excludedSources: [],
|
||||||
});
|
});
|
||||||
expect(args.sources.sort()).to.deep.equals(
|
expect(_.uniq(args.sources).sort()).to.deep.equals(
|
||||||
SELL_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
|
SELL_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
|
||||||
);
|
);
|
||||||
expect(args.liquidityProviderAddress).to.eql(registryAddress);
|
expect(args.liquidityProviderAddress).to.eql(registryAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not poll DEXes in `excludedSources`', async () => {
|
it('does not poll DEXes in `excludedSources`', async () => {
|
||||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
|
||||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
},
|
},
|
||||||
|
getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ...args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
sourcesPolled.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopSellQuotes(...args);
|
||||||
|
},
|
||||||
getBalancerSellQuotesOffChainAsync: (
|
getBalancerSellQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -551,7 +584,39 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources,
|
excludedSources,
|
||||||
});
|
});
|
||||||
expect(sourcesPolled.sort()).to.deep.equals(_.without(SELL_SOURCES, ...excludedSources).sort());
|
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(_.without(SELL_SOURCES, ...excludedSources).sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only polls DEXes in `includedSources`', async () => {
|
||||||
|
const includedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
|
||||||
|
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||||
|
replaceSamplerOps({
|
||||||
|
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||||
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
|
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
|
},
|
||||||
|
getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ...args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
sourcesPolled.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopSellQuotes(sources, ...args);
|
||||||
|
},
|
||||||
|
getBalancerSellQuotesOffChainAsync: (
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
takerFillAmounts: BigNumber[],
|
||||||
|
) => {
|
||||||
|
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||||
|
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||||
|
...DEFAULT_OPTS,
|
||||||
|
excludedSources: [],
|
||||||
|
includedSources,
|
||||||
|
});
|
||||||
|
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(includedSources.sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct asset data', async () => {
|
it('generates bridge orders with correct asset data', async () => {
|
||||||
@ -858,7 +923,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.be.length(3);
|
expect(improvedOrders).to.be.length(3);
|
||||||
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
|
||||||
expect(orderFillSources).to.deep.eq([
|
expect(orderFillSources).to.deep.eq([
|
||||||
[ERC20BridgeSource.Uniswap],
|
[ERC20BridgeSource.Uniswap],
|
||||||
[ERC20BridgeSource.Native],
|
[ERC20BridgeSource.Native],
|
||||||
@ -910,6 +975,13 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
},
|
},
|
||||||
|
getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
sourcesPolled.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||||
|
},
|
||||||
getBalancerBuyQuotesOffChainAsync: (
|
getBalancerBuyQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -923,7 +995,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources: [],
|
excludedSources: [],
|
||||||
});
|
});
|
||||||
expect(sourcesPolled.sort()).to.deep.equals(BUY_SOURCES.sort());
|
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(BUY_SOURCES.sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
|
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
|
||||||
@ -933,6 +1005,13 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
);
|
);
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotes: fn,
|
getBuyQuotes: fn,
|
||||||
|
getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
args.sources.push(ERC20BridgeSource.MultiHop);
|
||||||
|
args.sources.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||||
|
},
|
||||||
getBalancerBuyQuotesOffChainAsync: (
|
getBalancerBuyQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -953,20 +1032,27 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources: [],
|
excludedSources: [],
|
||||||
});
|
});
|
||||||
expect(args.sources.sort()).to.deep.eq(
|
expect(_.uniq(args.sources).sort()).to.deep.eq(
|
||||||
BUY_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
|
BUY_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
|
||||||
);
|
);
|
||||||
expect(args.liquidityProviderAddress).to.eql(registryAddress);
|
expect(args.liquidityProviderAddress).to.eql(registryAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not poll DEXes in `excludedSources`', async () => {
|
it('does not poll DEXes in `excludedSources`', async () => {
|
||||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
|
||||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
},
|
},
|
||||||
|
getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
sourcesPolled.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||||
|
},
|
||||||
getBalancerBuyQuotesOffChainAsync: (
|
getBalancerBuyQuotesOffChainAsync: (
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@ -980,7 +1066,39 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
...DEFAULT_OPTS,
|
...DEFAULT_OPTS,
|
||||||
excludedSources,
|
excludedSources,
|
||||||
});
|
});
|
||||||
expect(sourcesPolled.sort()).to.deep.eq(_.without(BUY_SOURCES, ...excludedSources).sort());
|
expect(_.uniq(sourcesPolled).sort()).to.deep.eq(_.without(BUY_SOURCES, ...excludedSources).sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only polls DEXes in `includedSources`', async () => {
|
||||||
|
const includedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
|
||||||
|
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||||
|
replaceSamplerOps({
|
||||||
|
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||||
|
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||||
|
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||||
|
},
|
||||||
|
getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
|
||||||
|
if (sources.length !== 0) {
|
||||||
|
sourcesPolled.push(ERC20BridgeSource.MultiHop);
|
||||||
|
sourcesPolled.push(...sources);
|
||||||
|
}
|
||||||
|
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
|
||||||
|
},
|
||||||
|
getBalancerBuyQuotesOffChainAsync: (
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
makerFillAmounts: BigNumber[],
|
||||||
|
) => {
|
||||||
|
sourcesPolled = sourcesPolled.concat(ERC20BridgeSource.Balancer);
|
||||||
|
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||||
|
...DEFAULT_OPTS,
|
||||||
|
excludedSources: [],
|
||||||
|
includedSources,
|
||||||
|
});
|
||||||
|
expect(_.uniq(sourcesPolled).sort()).to.deep.eq(includedSources.sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates bridge orders with correct asset data', async () => {
|
it('generates bridge orders with correct asset data', async () => {
|
||||||
@ -1198,7 +1316,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.be.length(2);
|
expect(improvedOrders).to.be.length(2);
|
||||||
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
|
const orderFillSources = getSortedOrderSources(MarketOperation.Sell, improvedOrders);
|
||||||
expect(orderFillSources).to.deep.eq([
|
expect(orderFillSources).to.deep.eq([
|
||||||
[ERC20BridgeSource.Native],
|
[ERC20BridgeSource.Native],
|
||||||
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap],
|
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap],
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Remove `ERC20BridgeSampler` artifact",
|
"note": "Remove `ERC20BridgeSampler` artifact",
|
||||||
"pr": 2647
|
"pr": 2647
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Regenerate artifacts",
|
||||||
|
"pr": 2703
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
38
packages/contract-artifacts/artifacts/IZeroEx.json
generated
38
packages/contract-artifacts/artifacts/IZeroEx.json
generated
@ -85,7 +85,7 @@
|
|||||||
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
||||||
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
||||||
],
|
],
|
||||||
"internalType": "struct IMetaTransactions.MetaTransactionData",
|
"internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
|
||||||
"name": "mtx",
|
"name": "mtx",
|
||||||
"type": "tuple"
|
"type": "tuple"
|
||||||
},
|
},
|
||||||
@ -122,14 +122,14 @@
|
|||||||
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
|
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
|
||||||
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
||||||
],
|
],
|
||||||
"internalType": "struct ITransformERC20.Transformation[]",
|
"internalType": "struct ITransformERC20Feature.Transformation[]",
|
||||||
"name": "transformations",
|
"name": "transformations",
|
||||||
"type": "tuple[]"
|
"type": "tuple[]"
|
||||||
},
|
},
|
||||||
{ "internalType": "bytes32", "name": "callDataHash", "type": "bytes32" },
|
{ "internalType": "bytes32", "name": "callDataHash", "type": "bytes32" },
|
||||||
{ "internalType": "bytes", "name": "callDataSignature", "type": "bytes" }
|
{ "internalType": "bytes", "name": "callDataSignature", "type": "bytes" }
|
||||||
],
|
],
|
||||||
"internalType": "struct ITransformERC20.TransformERC20Args",
|
"internalType": "struct ITransformERC20Feature.TransformERC20Args",
|
||||||
"name": "args",
|
"name": "args",
|
||||||
"type": "tuple"
|
"type": "tuple"
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@
|
|||||||
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
||||||
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
||||||
],
|
],
|
||||||
"internalType": "struct IMetaTransactions.MetaTransactionData[]",
|
"internalType": "struct IMetaTransactionsFeature.MetaTransactionData[]",
|
||||||
"name": "mtxs",
|
"name": "mtxs",
|
||||||
"type": "tuple[]"
|
"type": "tuple[]"
|
||||||
},
|
},
|
||||||
@ -187,7 +187,7 @@
|
|||||||
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
||||||
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
||||||
],
|
],
|
||||||
"internalType": "struct IMetaTransactions.MetaTransactionData",
|
"internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
|
||||||
"name": "mtx",
|
"name": "mtx",
|
||||||
"type": "tuple"
|
"type": "tuple"
|
||||||
},
|
},
|
||||||
@ -237,7 +237,7 @@
|
|||||||
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
||||||
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
||||||
],
|
],
|
||||||
"internalType": "struct IMetaTransactions.MetaTransactionData",
|
"internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
|
||||||
"name": "mtx",
|
"name": "mtx",
|
||||||
"type": "tuple"
|
"type": "tuple"
|
||||||
}
|
}
|
||||||
@ -262,7 +262,7 @@
|
|||||||
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
{ "internalType": "contract IERC20TokenV06", "name": "feeToken", "type": "address" },
|
||||||
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
{ "internalType": "uint256", "name": "feeAmount", "type": "uint256" }
|
||||||
],
|
],
|
||||||
"internalType": "struct IMetaTransactions.MetaTransactionData",
|
"internalType": "struct IMetaTransactionsFeature.MetaTransactionData",
|
||||||
"name": "mtx",
|
"name": "mtx",
|
||||||
"type": "tuple"
|
"type": "tuple"
|
||||||
}
|
}
|
||||||
@ -366,6 +366,18 @@
|
|||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "internalType": "contract IERC20TokenV06[]", "name": "tokens", "type": "address[]" },
|
||||||
|
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
|
||||||
|
{ "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" },
|
||||||
|
{ "internalType": "bool", "name": "isSushi", "type": "bool" }
|
||||||
|
],
|
||||||
|
"name": "sellToUniswap",
|
||||||
|
"outputs": [{ "internalType": "uint256", "name": "buyAmount", "type": "uint256" }],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"inputs": [{ "internalType": "address", "name": "quoteSigner", "type": "address" }],
|
"inputs": [{ "internalType": "address", "name": "quoteSigner", "type": "address" }],
|
||||||
"name": "setQuoteSigner",
|
"name": "setQuoteSigner",
|
||||||
@ -398,7 +410,7 @@
|
|||||||
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
|
{ "internalType": "uint32", "name": "deploymentNonce", "type": "uint32" },
|
||||||
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
||||||
],
|
],
|
||||||
"internalType": "struct ITransformERC20.Transformation[]",
|
"internalType": "struct ITransformERC20Feature.Transformation[]",
|
||||||
"name": "transformations",
|
"name": "transformations",
|
||||||
"type": "tuple[]"
|
"type": "tuple[]"
|
||||||
}
|
}
|
||||||
@ -552,6 +564,16 @@
|
|||||||
"targetImpl": "The address of an older implementation of the function."
|
"targetImpl": "The address of an older implementation of the function."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sellToUniswap(address[],uint256,uint256,bool)": {
|
||||||
|
"details": "Efficiently sell directly to uniswap/sushiswap.",
|
||||||
|
"params": {
|
||||||
|
"isSushi": "Use sushiswap if true.",
|
||||||
|
"minBuyAmount": "Minimum amount of `tokens[-1]` to buy.",
|
||||||
|
"sellAmount": "of `tokens[0]` Amount to sell.",
|
||||||
|
"tokens": "Sell path."
|
||||||
|
},
|
||||||
|
"returns": { "buyAmount": "Amount of `tokens[-1]` bought." }
|
||||||
|
},
|
||||||
"setQuoteSigner(address)": {
|
"setQuoteSigner(address)": {
|
||||||
"details": "Replace the optional signer for `transformERC20()` calldata. Only callable by the owner.",
|
"details": "Replace the optional signer for `transformERC20()` calldata. Only callable by the owner.",
|
||||||
"params": { "quoteSigner": "The address of the new calldata signer." }
|
"params": { "quoteSigner": "The address of the new calldata signer." }
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Add `exchangeProxy` to `ContractWrappers` type.",
|
"note": "Add `exchangeProxy` to `ContractWrappers` type.",
|
||||||
"pr": 2649
|
"pr": 2649
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Regenerate wrappers",
|
||||||
|
"pr": 2703
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1000,6 +1000,35 @@ export class IZeroExContract extends BaseContract {
|
|||||||
stateMutability: 'nonpayable',
|
stateMutability: 'nonpayable',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'tokens',
|
||||||
|
type: 'address[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sellAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minBuyAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'isSushi',
|
||||||
|
type: 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'sellToUniswap',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: 'buyAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: 'payable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
@ -2418,6 +2447,68 @@ export class IZeroExContract extends BaseContract {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Efficiently sell directly to uniswap/sushiswap.
|
||||||
|
* @param tokens Sell path.
|
||||||
|
* @param sellAmount of `tokens[0]` Amount to sell.
|
||||||
|
* @param minBuyAmount Minimum amount of `tokens[-1]` to buy.
|
||||||
|
* @param isSushi Use sushiswap if true.
|
||||||
|
*/
|
||||||
|
public sellToUniswap(
|
||||||
|
tokens: string[],
|
||||||
|
sellAmount: BigNumber,
|
||||||
|
minBuyAmount: BigNumber,
|
||||||
|
isSushi: boolean,
|
||||||
|
): ContractTxFunctionObj<BigNumber> {
|
||||||
|
const self = (this as any) as IZeroExContract;
|
||||||
|
assert.isArray('tokens', tokens);
|
||||||
|
assert.isBigNumber('sellAmount', sellAmount);
|
||||||
|
assert.isBigNumber('minBuyAmount', minBuyAmount);
|
||||||
|
assert.isBoolean('isSushi', isSushi);
|
||||||
|
const functionSignature = 'sellToUniswap(address[],uint256,uint256,bool)';
|
||||||
|
|
||||||
|
return {
|
||||||
|
async sendTransactionAsync(
|
||||||
|
txData?: Partial<TxData> | undefined,
|
||||||
|
opts: SendTransactionOpts = { shouldValidate: true },
|
||||||
|
): Promise<string> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...txData },
|
||||||
|
this.estimateGasAsync.bind(this),
|
||||||
|
);
|
||||||
|
if (opts.shouldValidate !== false) {
|
||||||
|
await this.callAsync(txDataWithDefaults);
|
||||||
|
}
|
||||||
|
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
awaitTransactionSuccessAsync(
|
||||||
|
txData?: Partial<TxData>,
|
||||||
|
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
|
||||||
|
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
|
||||||
|
},
|
||||||
|
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
|
||||||
|
data: this.getABIEncodedTransactionData(),
|
||||||
|
...txData,
|
||||||
|
});
|
||||||
|
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
|
||||||
|
BaseContract._assertCallParams(callData, defaultBlock);
|
||||||
|
const rawCallResult = await self._performCallAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...callData },
|
||||||
|
defaultBlock,
|
||||||
|
);
|
||||||
|
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||||
|
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
|
||||||
|
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
|
||||||
|
},
|
||||||
|
getABIEncodedTransactionData(): string {
|
||||||
|
return self._strictEncodeArguments(functionSignature, [tokens, sellAmount, minBuyAmount, isSushi]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Replace the optional signer for `transformERC20()` calldata.
|
* Replace the optional signer for `transformERC20()` calldata.
|
||||||
* Only callable by the owner.
|
* Only callable by the owner.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user