Feature/bunny hop (#2647)
* `@0x/contracts-erc20-bridge-sampler`: Add TwoHopSampler + refactor * `@0x/asset-swapper`: Refactor + add two-hop skeleton * Round out two-hop support in asset-swapper * Add BalancerSampler, use it for two-hop quotes * Fix bugs discovered from simbot * rebases are hard * Add intermediate token to MultiHop source breakdown * Fix market buy bugs * Use hybrid on-chain/off-chain sampling for Balancer * Another day, another rebase * Update changelogs * Address PR feedback, CI fixes * Address more PR feedback
This commit is contained in:
parent
4f78f55c2a
commit
bab34c2d21
@ -11,6 +11,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"build:ts": "tsc -b",
|
||||
"build:ci": "yarn build",
|
||||
"pre_build": "run-s compile contracts:gen generate_contract_wrappers contracts:copy",
|
||||
"test": "yarn run_mocha",
|
||||
|
@ -97,7 +97,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
|
||||
this.actor.deployment.tokens.erc20,
|
||||
4, // tslint:disable-line:custom-no-magic-numbers
|
||||
);
|
||||
)!;
|
||||
|
||||
// Maker and taker set balances/allowances to guarantee that the fill succeeds.
|
||||
// Amounts are chosen to be within each actor's balance (divided by 8, in case
|
||||
@ -146,7 +146,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
const [leftMakerToken, leftTakerToken, makerFeeToken, takerFeeToken] = Pseudorandom.sampleSize(
|
||||
this.actor.deployment.tokens.erc20,
|
||||
4, // tslint:disable-line:custom-no-magic-numbers
|
||||
);
|
||||
)!;
|
||||
const rightMakerToken = leftTakerToken;
|
||||
const rightTakerToken = leftMakerToken;
|
||||
|
||||
|
@ -144,7 +144,7 @@ function _getParameterNames(func: (...args: any[]) => any): string[] {
|
||||
.replace(/[/][/].*$/gm, '') // strip single-line comments
|
||||
.replace(/\s+/g, '') // strip white space
|
||||
.replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments
|
||||
.split('){', 1)[0]
|
||||
.split(/\){|\)=>/, 1)[0]
|
||||
.replace(/^[^(]*[(]/, '') // extract the parameters
|
||||
.replace(/=[^,]+/g, '') // strip any ES6 defaults
|
||||
.split(',')
|
||||
|
@ -61,6 +61,18 @@
|
||||
{
|
||||
"note": "Added `Mooniswap`",
|
||||
"pr": 2675
|
||||
},
|
||||
{
|
||||
"note": "Added two-hop support",
|
||||
"pr": 2647
|
||||
},
|
||||
{
|
||||
"note": "Move ERC20BridgeSampler interfaces into `interfaces` directory",
|
||||
"pr": 2647
|
||||
},
|
||||
{
|
||||
"note": "Use on-chain sampling (sometimes) for Balancer",
|
||||
"pr": 2647
|
||||
}
|
||||
]
|
||||
},
|
||||
|
143
packages/asset-swapper/contracts/src/BalancerSampler.sol
Normal file
143
packages/asset-swapper/contracts/src/BalancerSampler.sol
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IBalancer.sol";
|
||||
|
||||
|
||||
contract BalancerSampler {
|
||||
|
||||
/// @dev Base gas limit for Balancer calls.
|
||||
uint256 constant private BALANCER_CALL_GAS = 300e3; // 300k
|
||||
|
||||
struct BalancerState {
|
||||
uint256 takerTokenBalance;
|
||||
uint256 makerTokenBalance;
|
||||
uint256 takerTokenWeight;
|
||||
uint256 makerTokenWeight;
|
||||
uint256 swapFee;
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Balancer.
|
||||
/// @param poolAddress Address of the Balancer pool to query.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromBalancer(
|
||||
address poolAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
poolAddress.staticcall.gas(BALANCER_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
pool.calcOutGivenIn.selector,
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
takerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Balancer.
|
||||
/// @param poolAddress Address of the Balancer pool to query.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromBalancer(
|
||||
address poolAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
IBalancer pool = IBalancer(poolAddress);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
if (!pool.isBound(takerToken) || !pool.isBound(makerToken)) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
BalancerState memory poolState;
|
||||
poolState.takerTokenBalance = pool.getBalance(takerToken);
|
||||
poolState.makerTokenBalance = pool.getBalance(makerToken);
|
||||
poolState.takerTokenWeight = pool.getDenormalizedWeight(takerToken);
|
||||
poolState.makerTokenWeight = pool.getDenormalizedWeight(makerToken);
|
||||
poolState.swapFee = pool.getSwapFee();
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
poolAddress.staticcall.gas(BALANCER_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
pool.calcInGivenOut.selector,
|
||||
poolState.takerTokenBalance,
|
||||
poolState.takerTokenWeight,
|
||||
poolState.makerTokenBalance,
|
||||
poolState.makerTokenWeight,
|
||||
makerTokenAmounts[i],
|
||||
poolState.swapFee
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ICurve.sol";
|
||||
import "./interfaces/ICurve.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./BalancerSampler.sol";
|
||||
import "./CurveSampler.sol";
|
||||
import "./Eth2DaiSampler.sol";
|
||||
import "./KyberSampler.sol";
|
||||
@ -29,9 +30,11 @@ import "./MooniswapSampler.sol";
|
||||
import "./NativeOrderSampler.sol";
|
||||
import "./UniswapSampler.sol";
|
||||
import "./UniswapV2Sampler.sol";
|
||||
import "./TwoHopSampler.sol";
|
||||
|
||||
|
||||
contract ERC20BridgeSampler is
|
||||
BalancerSampler,
|
||||
CurveSampler,
|
||||
Eth2DaiSampler,
|
||||
KyberSampler,
|
||||
@ -40,6 +43,7 @@ contract ERC20BridgeSampler is
|
||||
MooniswapSampler,
|
||||
MultiBridgeSampler,
|
||||
NativeOrderSampler,
|
||||
TwoHopSampler,
|
||||
UniswapSampler,
|
||||
UniswapV2Sampler
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./interfaces/IEth2Dai.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
|
@ -20,10 +20,10 @@ pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
import "./IKyberNetworkProxy.sol";
|
||||
import "./IKyberStorage.sol";
|
||||
import "./IKyberHintHandler.sol";
|
||||
import "./interfaces/IKyberNetwork.sol";
|
||||
import "./interfaces/IKyberNetworkProxy.sol";
|
||||
import "./interfaces/IKyberStorage.sol";
|
||||
import "./interfaces/IKyberHintHandler.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
@ -20,8 +20,8 @@ pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./ILiquidityProvider.sol";
|
||||
import "./ILiquidityProviderRegistry.sol";
|
||||
import "./interfaces/ILiquidityProvider.sol";
|
||||
import "./interfaces/ILiquidityProviderRegistry.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
@ -48,21 +48,21 @@ contract LiquidityProviderSampler is
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
returns (uint256[] memory makerTokenAmounts, address providerAddress)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Query registry for provider address.
|
||||
address providerAddress = getLiquidityProviderFromRegistry(
|
||||
providerAddress = _getLiquidityProviderFromRegistry(
|
||||
registryAddress,
|
||||
takerToken,
|
||||
makerToken
|
||||
);
|
||||
// If provider doesn't exist, return all zeros.
|
||||
if (providerAddress == address(0)) {
|
||||
return makerTokenAmounts;
|
||||
return (makerTokenAmounts, providerAddress);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
@ -101,9 +101,14 @@ contract LiquidityProviderSampler is
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
returns (uint256[] memory takerTokenAmounts, address providerAddress)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
providerAddress = _getLiquidityProviderFromRegistry(
|
||||
registryAddress,
|
||||
takerToken,
|
||||
makerToken
|
||||
);
|
||||
takerTokenAmounts = _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, registryAddress),
|
||||
takerTokenData: abi.encode(takerToken, registryAddress),
|
||||
@ -119,12 +124,12 @@ contract LiquidityProviderSampler is
|
||||
/// @param takerToken Taker asset managed by liquidity provider.
|
||||
/// @param makerToken Maker asset managed by liquidity provider.
|
||||
/// @return providerAddress Address of the liquidity provider.
|
||||
function getLiquidityProviderFromRegistry(
|
||||
function _getLiquidityProviderFromRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken
|
||||
)
|
||||
public
|
||||
private
|
||||
view
|
||||
returns (address providerAddress)
|
||||
{
|
||||
@ -167,6 +172,7 @@ contract LiquidityProviderSampler is
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
(uint256[] memory amounts, ) = abi.decode(resultData, (uint256[], address));
|
||||
return amounts[0];
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./IMStable.sol";
|
||||
import "./interfaces/IMStable.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./IMultiBridge.sol";
|
||||
import "./interfaces/IMultiBridge.sol";
|
||||
|
||||
|
||||
contract MultiBridgeSampler {
|
||||
|
125
packages/asset-swapper/contracts/src/TwoHopSampler.sol
Normal file
125
packages/asset-swapper/contracts/src/TwoHopSampler.sol
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
|
||||
|
||||
contract TwoHopSampler {
|
||||
using LibBytes for bytes;
|
||||
|
||||
struct HopInfo {
|
||||
uint256 sourceIndex;
|
||||
bytes returnData;
|
||||
}
|
||||
|
||||
function sampleTwoHopSell(
|
||||
bytes[] memory firstHopCalls,
|
||||
bytes[] memory secondHopCalls,
|
||||
uint256 sellAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
uint256 buyAmount
|
||||
)
|
||||
{
|
||||
uint256 intermediateAssetAmount = 0;
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > intermediateAssetAmount) {
|
||||
intermediateAssetAmount = amount;
|
||||
firstHop.sourceIndex = i;
|
||||
firstHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intermediateAssetAmount == 0) {
|
||||
return (firstHop, secondHop, buyAmount);
|
||||
}
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > buyAmount) {
|
||||
buyAmount = amount;
|
||||
secondHop.sourceIndex = j;
|
||||
secondHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sampleTwoHopBuy(
|
||||
bytes[] memory firstHopCalls,
|
||||
bytes[] memory secondHopCalls,
|
||||
uint256 buyAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
uint256 sellAmount
|
||||
)
|
||||
{
|
||||
sellAmount = uint256(-1);
|
||||
uint256 intermediateAssetAmount = uint256(-1);
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
amount > 0 &&
|
||||
amount < intermediateAssetAmount
|
||||
) {
|
||||
intermediateAssetAmount = amount;
|
||||
secondHop.sourceIndex = j;
|
||||
secondHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intermediateAssetAmount == uint256(-1)) {
|
||||
return (firstHop, secondHop, sellAmount);
|
||||
}
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
amount > 0 &&
|
||||
amount < sellAmount
|
||||
) {
|
||||
sellAmount = amount;
|
||||
firstHop.sourceIndex = i;
|
||||
firstHop.returnData = returnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./IUniswapExchangeQuotes.sol";
|
||||
import "./interfaces/IUniswapExchangeQuotes.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IUniswapV2Router01.sol";
|
||||
import "./interfaces/IUniswapV2Router01.sol";
|
||||
|
||||
|
||||
contract UniswapV2Sampler is
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
|
||||
interface IBalancer {
|
||||
function isBound(address t) external view returns (bool);
|
||||
function getDenormalizedWeight(address token) external view returns (uint256);
|
||||
function getBalance(address token) external view returns (uint256);
|
||||
function getSwapFee() external view returns (uint256);
|
||||
function calcOutGivenIn(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountIn,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountOut);
|
||||
function calcInGivenOut(
|
||||
uint256 tokenBalanceIn,
|
||||
uint256 tokenWeightIn,
|
||||
uint256 tokenBalanceOut,
|
||||
uint256 tokenWeightOut,
|
||||
uint256 tokenAmountOut,
|
||||
uint256 swapFee
|
||||
) external pure returns (uint256 tokenAmountIn);
|
||||
}
|
@ -22,9 +22,9 @@ import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFacto
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "../src/ERC20BridgeSampler.sol";
|
||||
import "../src/IEth2Dai.sol";
|
||||
import "../src/IKyberNetworkProxy.sol";
|
||||
import "../src/IUniswapV2Router01.sol";
|
||||
import "../src/interfaces/IEth2Dai.sol";
|
||||
import "../src/interfaces/IKyberNetworkProxy.sol";
|
||||
import "../src/interfaces/IUniswapV2Router01.sol";
|
||||
|
||||
|
||||
library LibDeterministicQuotes {
|
||||
|
@ -9,6 +9,7 @@
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"build:ts": "tsc -b",
|
||||
"watch": "tsc -w -p tsconfig.json",
|
||||
"watch:contracts": "sol-compiler -w",
|
||||
"build:ci": "yarn build",
|
||||
@ -37,7 +38,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|ICurve|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|UniswapSampler|UniswapV2Sampler).json",
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
@ -60,6 +61,7 @@
|
||||
"@0x/base-contract": "^6.2.3",
|
||||
"@0x/contract-addresses": "^4.11.0",
|
||||
"@0x/contract-wrappers": "^13.8.0",
|
||||
"@0x/contracts-erc20-bridge-sampler": "^1.7.0",
|
||||
"@0x/json-schemas": "^5.1.0",
|
||||
"@0x/order-utils": "^10.3.0",
|
||||
"@0x/orderbook": "^2.2.7",
|
||||
|
@ -39,10 +39,8 @@ const PROTOCOL_FEE_MULTIPLIER = new BigNumber(70000);
|
||||
const MARKET_UTILS_AMOUNT_BUFFER_PERCENTAGE = 0.5;
|
||||
|
||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||
...{
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
},
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||
...DEFAULT_ORDER_PRUNER_OPTS,
|
||||
samplerGasLimit: 250e6,
|
||||
ethGasStationUrl: ETH_GAS_STATION_API_URL,
|
||||
|
@ -1,3 +1,9 @@
|
||||
export {
|
||||
AwaitTransactionSuccessOpts,
|
||||
ContractFunctionObj,
|
||||
ContractTxFunctionObj,
|
||||
SendTransactionOpts,
|
||||
} from '@0x/base-contract';
|
||||
export { ContractAddresses } from '@0x/contract-addresses';
|
||||
export { WSOpts } from '@0x/mesh-rpc-client';
|
||||
export {
|
||||
@ -28,6 +34,7 @@ export {
|
||||
AbiDefinition,
|
||||
BlockParam,
|
||||
BlockParamLiteral,
|
||||
CallData,
|
||||
CompilerOpts,
|
||||
CompilerSettings,
|
||||
CompilerSettingsMetadata,
|
||||
@ -66,6 +73,8 @@ export {
|
||||
StateMutability,
|
||||
SupportedProvider,
|
||||
TupleDataItem,
|
||||
TxData,
|
||||
TxDataPayable,
|
||||
Web3JsProvider,
|
||||
Web3JsV1Provider,
|
||||
Web3JsV2Provider,
|
||||
@ -107,6 +116,11 @@ export {
|
||||
SwapQuoterRfqtOpts,
|
||||
} from './types';
|
||||
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||
export {
|
||||
Parameters,
|
||||
SamplerContractCall,
|
||||
SamplerContractOperation,
|
||||
} from './utils/market_operation_utils/sampler_contract_operation';
|
||||
export {
|
||||
BancorFillData,
|
||||
BalancerFillData,
|
||||
@ -114,22 +128,30 @@ export {
|
||||
CurveFillData,
|
||||
CurveFunctionSelectors,
|
||||
CurveInfo,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
Fill,
|
||||
FillData,
|
||||
FillFlags,
|
||||
GetMarketOrdersRfqtOpts,
|
||||
LiquidityProviderFillData,
|
||||
MarketDepth,
|
||||
MarketDepthSide,
|
||||
MultiBridgeFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
NativeFillData,
|
||||
OptimizedMarketOrder,
|
||||
SourceInfo,
|
||||
SourceQuoteOperation,
|
||||
TokenAdjacencyGraph,
|
||||
UniswapV2FillData,
|
||||
} from './utils/market_operation_utils/types';
|
||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||
export {
|
||||
BridgeReportSource,
|
||||
MultiHopReportSource,
|
||||
NativeOrderbookReportSource,
|
||||
NativeRFQTReportSource,
|
||||
QuoteReport,
|
||||
@ -140,3 +162,4 @@ export { rfqtMocker } from './utils/rfqt_mocker';
|
||||
export { ERC20BridgeSamplerContract } from './wrappers';
|
||||
import { ERC20BridgeSource } from './utils/market_operation_utils/types';
|
||||
export type Native = ERC20BridgeSource.Native;
|
||||
export type MultiHop = ERC20BridgeSource.MultiHop;
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { ITransformERC20Contract } from '@0x/contract-wrappers';
|
||||
import {
|
||||
assetDataUtils,
|
||||
encodeAffiliateFeeTransformerData,
|
||||
encodeFillQuoteTransformerData,
|
||||
encodePayTakerTransformerData,
|
||||
encodeWethTransformerData,
|
||||
ERC20AssetData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
} from '@0x/order-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
@ -30,6 +27,7 @@ import {
|
||||
SwapQuoteGetOutputOpts,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { getTokenFromAssetData } from '../utils/utils';
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
||||
@ -108,19 +106,48 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
});
|
||||
}
|
||||
|
||||
const intermediateToken = quote.isTwoHop ? getTokenFromAssetData(quote.orders[0].makerAssetData) : NULL_ADDRESS;
|
||||
// This transformer will fill the quote.
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
sellToken,
|
||||
buyToken,
|
||||
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
|
||||
fillAmount: isBuyQuote(quote) ? quote.makerAssetFillAmount : quote.takerAssetFillAmount,
|
||||
maxOrderFillAmounts: [],
|
||||
orders: quote.orders,
|
||||
signatures: quote.orders.map(o => o.signature),
|
||||
}),
|
||||
});
|
||||
if (quote.isTwoHop) {
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
sellToken,
|
||||
buyToken: intermediateToken,
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
fillAmount: firstHopOrder.takerAssetAmount,
|
||||
maxOrderFillAmounts: [],
|
||||
orders: [firstHopOrder],
|
||||
signatures: [firstHopOrder.signature],
|
||||
}),
|
||||
});
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
sellToken: intermediateToken,
|
||||
buyToken,
|
||||
side: FillQuoteTransformerSide.Sell,
|
||||
fillAmount: MAX_UINT256,
|
||||
maxOrderFillAmounts: [],
|
||||
orders: [secondHopOrder],
|
||||
signatures: [secondHopOrder.signature],
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.fillQuoteTransformer,
|
||||
data: encodeFillQuoteTransformerData({
|
||||
sellToken,
|
||||
buyToken,
|
||||
side: isBuyQuote(quote) ? FillQuoteTransformerSide.Buy : FillQuoteTransformerSide.Sell,
|
||||
fillAmount: isBuyQuote(quote) ? quote.makerAssetFillAmount : quote.takerAssetFillAmount,
|
||||
maxOrderFillAmounts: [],
|
||||
orders: quote.orders,
|
||||
signatures: quote.orders.map(o => o.signature),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (isToETH) {
|
||||
// Create a WETH unwrapper if going to ETH.
|
||||
@ -159,7 +186,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
transforms.push({
|
||||
deploymentNonce: this.transformerNonces.payTakerTransformer,
|
||||
data: encodePayTakerTransformerData({
|
||||
tokens: [sellToken, buyToken, ETH_TOKEN_ADDRESS],
|
||||
tokens: [sellToken, buyToken, ETH_TOKEN_ADDRESS].concat(quote.isTwoHop ? intermediateToken : []),
|
||||
amounts: [],
|
||||
}),
|
||||
});
|
||||
@ -201,15 +228,6 @@ function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||
return quote.type === MarketOperation.Buy;
|
||||
}
|
||||
|
||||
function getTokenFromAssetData(assetData: string): string {
|
||||
const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
|
||||
if (data.assetProxyId !== AssetProxyId.ERC20) {
|
||||
throw new Error(`Unsupported exchange proxy quote asset type: ${data.assetProxyId}`);
|
||||
}
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
return (data as ERC20AssetData).tokenAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nonce for a transformer given its deployer.
|
||||
* If `deployer` is the null address, zero will always be returned.
|
||||
|
@ -2,7 +2,7 @@ import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/cont
|
||||
import { DevUtilsContract } from '@0x/contract-wrappers';
|
||||
import { schemas } from '@0x/json-schemas';
|
||||
import { assetDataUtils, SignedOrder } from '@0x/order-utils';
|
||||
import { APIOrder, MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
|
||||
import { MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { BlockParamLiteral, SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
@ -165,6 +165,7 @@ export class SwapQuoter {
|
||||
samplerGasLimit,
|
||||
liquidityProviderRegistryAddress,
|
||||
rfqt,
|
||||
tokenAdjacencyGraph,
|
||||
} = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
assert.isValidOrderbook('orderbook', orderbook);
|
||||
@ -210,6 +211,7 @@ export class SwapQuoter {
|
||||
exchangeAddress: this._contractAddresses.exchange,
|
||||
},
|
||||
liquidityProviderRegistryAddress,
|
||||
tokenAdjacencyGraph,
|
||||
);
|
||||
this._swapQuoteCalculator = new SwapQuoteCalculator(this._marketOperationUtils);
|
||||
}
|
||||
@ -422,7 +424,7 @@ export class SwapQuoter {
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||
let [sellOrders, buyOrders] =
|
||||
options.excludedSources && options.excludedSources.includes(ERC20BridgeSource.Native)
|
||||
? await Promise.resolve([[] as APIOrder[], [] as APIOrder[]])
|
||||
? [[], []]
|
||||
: await Promise.all([
|
||||
this.orderbook.getOrdersAsync(makerAssetData, takerAssetData),
|
||||
this.orderbook.getOrdersAsync(takerAssetData, makerAssetData),
|
||||
|
@ -2,7 +2,12 @@ import { BlockParam, ContractAddresses, GethCallOverrides } from '@0x/contract-w
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { GetMarketOrdersOpts, OptimizedMarketOrder } from './utils/market_operation_utils/types';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
GetMarketOrdersOpts,
|
||||
OptimizedMarketOrder,
|
||||
TokenAdjacencyGraph,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { QuoteReport } from './utils/quote_report_generator';
|
||||
import { LogFunction } from './utils/quote_requestor';
|
||||
|
||||
@ -141,8 +146,6 @@ export interface ExchangeProxyContractOpts {
|
||||
affiliateFee: AffiliateFee;
|
||||
}
|
||||
|
||||
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
|
||||
|
||||
export interface GetExtensionContractTypeOpts {
|
||||
takerAddress?: string;
|
||||
ethAmount?: BigNumber;
|
||||
@ -165,6 +168,7 @@ export interface SwapQuoteBase {
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
isTwoHop: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,6 +189,8 @@ export interface MarketBuySwapQuote extends SwapQuoteBase {
|
||||
type: MarketOperation.Buy;
|
||||
}
|
||||
|
||||
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
|
||||
|
||||
/**
|
||||
* feeTakerAssetAmount: The amount of takerAsset reserved for paying takerFees when swapping for desired assets.
|
||||
* takerAssetAmount: The amount of takerAsset swapped for desired makerAsset.
|
||||
@ -205,9 +211,15 @@ export interface SwapQuoteInfo {
|
||||
/**
|
||||
* percentage breakdown of each liquidity source used in quote
|
||||
*/
|
||||
export interface SwapQuoteOrdersBreakdown {
|
||||
[source: string]: BigNumber;
|
||||
}
|
||||
export type SwapQuoteOrdersBreakdown = Partial<
|
||||
{ [key in Exclude<ERC20BridgeSource, typeof ERC20BridgeSource.MultiHop>]: BigNumber } & {
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: BigNumber;
|
||||
intermediateToken: string;
|
||||
hops: ERC20BridgeSource[];
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* nativeExclusivelyRFQT: if set to `true`, Swap quote will exclude Open Orderbook liquidity.
|
||||
@ -272,6 +284,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
||||
ethGasStationUrl?: string;
|
||||
rfqt?: SwapQuoterRfqtOpts;
|
||||
samplerOverrides?: SamplerOverrides;
|
||||
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,12 +19,21 @@ export const assert = {
|
||||
sharedAssert.isHexString(`${variableName}.takerAssetData`, swapQuote.takerAssetData);
|
||||
sharedAssert.isHexString(`${variableName}.makerAssetData`, swapQuote.makerAssetData);
|
||||
sharedAssert.doesConformToSchema(`${variableName}.orders`, swapQuote.orders, schemas.signedOrdersSchema);
|
||||
assert.isValidSwapQuoteOrders(
|
||||
`${variableName}.orders`,
|
||||
swapQuote.orders,
|
||||
swapQuote.makerAssetData,
|
||||
swapQuote.takerAssetData,
|
||||
);
|
||||
if (swapQuote.isTwoHop) {
|
||||
assert.isValidTwoHopSwapQuoteOrders(
|
||||
`${variableName}.orders`,
|
||||
swapQuote.orders,
|
||||
swapQuote.makerAssetData,
|
||||
swapQuote.takerAssetData,
|
||||
);
|
||||
} else {
|
||||
assert.isValidSwapQuoteOrders(
|
||||
`${variableName}.orders`,
|
||||
swapQuote.orders,
|
||||
swapQuote.makerAssetData,
|
||||
swapQuote.takerAssetData,
|
||||
);
|
||||
}
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.bestCaseQuoteInfo`, swapQuote.bestCaseQuoteInfo);
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.worstCaseQuoteInfo`, swapQuote.worstCaseQuoteInfo);
|
||||
if (swapQuote.type === MarketOperation.Buy) {
|
||||
@ -54,6 +63,28 @@ export const assert = {
|
||||
);
|
||||
});
|
||||
},
|
||||
isValidTwoHopSwapQuoteOrders(
|
||||
variableName: string,
|
||||
orders: SignedOrder[],
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
): void {
|
||||
assert.assert(orders.length === 2, `Expected ${variableName}.length to be 2 for a two-hop quote`);
|
||||
assert.assert(
|
||||
isAssetDataEquivalent(takerAssetData, orders[0].takerAssetData),
|
||||
`Expected ${variableName}[0].takerAssetData to be ${takerAssetData} but found ${orders[0].takerAssetData}`,
|
||||
);
|
||||
assert.assert(
|
||||
isAssetDataEquivalent(makerAssetData, orders[1].makerAssetData),
|
||||
`Expected ${variableName}[1].makerAssetData to be ${makerAssetData} but found ${orders[1].makerAssetData}`,
|
||||
);
|
||||
assert.assert(
|
||||
isAssetDataEquivalent(orders[0].makerAssetData, orders[1].takerAssetData),
|
||||
`Expected ${variableName}[0].makerAssetData (${
|
||||
orders[0].makerAssetData
|
||||
}) to equal ${variableName}[1].takerAssetData (${orders[1].takerAssetData})`,
|
||||
);
|
||||
},
|
||||
isValidOrdersForSwapQuoter<T extends Order>(variableName: string, orders: T[]): void {
|
||||
_.every(orders, (order: T, index: number) => {
|
||||
assert.assert(
|
||||
|
@ -2,6 +2,10 @@ import { BigNumber } from '@0x/utils';
|
||||
import { bmath, getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
||||
import { Decimal } from 'decimal.js';
|
||||
|
||||
import { ERC20BridgeSource } from './types';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
export interface BalancerPool {
|
||||
id: string;
|
||||
balanceIn: BigNumber;
|
||||
@ -21,15 +25,15 @@ interface CacheValue {
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const FIVE_SECONDS_MS = 5 * 1000;
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const DEFAULT_TIMEOUT_MS = 1000;
|
||||
const MAX_POOLS_FETCHED = 2;
|
||||
const MAX_POOLS_FETCHED = 3;
|
||||
const Decimal20 = Decimal.clone({ precision: 20 });
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
|
||||
export class BalancerPoolsCache {
|
||||
constructor(
|
||||
private readonly _cache: { [key: string]: CacheValue } = {},
|
||||
public cacheExpiryMs: number = FIVE_SECONDS_MS,
|
||||
private readonly maxPoolsFetched: number = MAX_POOLS_FETCHED,
|
||||
) {}
|
||||
|
||||
@ -42,10 +46,52 @@ export class BalancerPoolsCache {
|
||||
return Promise.race([this._getPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||
}
|
||||
|
||||
protected async _getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
|
||||
public getCachedPoolAddressesForPair(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs?: number,
|
||||
): string[] | undefined {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
const minTimestamp = Date.now() - this.cacheExpiryMs;
|
||||
if (cacheExpiryMs === undefined) {
|
||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||
}
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
return undefined;
|
||||
} else {
|
||||
return value.pools.map(pool => pool.id);
|
||||
}
|
||||
}
|
||||
|
||||
public howToSampleBalancer(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
excludedSources: ERC20BridgeSource[],
|
||||
): { onChain: boolean; offChain: boolean } {
|
||||
// If Balancer is excluded as a source, do not sample.
|
||||
if (excludedSources.includes(ERC20BridgeSource.Balancer)) {
|
||||
return { onChain: false, offChain: false };
|
||||
}
|
||||
const cachedBalancerPools = this.getCachedPoolAddressesForPair(takerToken, makerToken, ONE_DAY_MS);
|
||||
// Sample Balancer on-chain (i.e. via the ERC20BridgeSampler contract) if:
|
||||
// - Cached values are not stale
|
||||
// - There is at least one Balancer pool for this pair
|
||||
const onChain = cachedBalancerPools !== undefined && cachedBalancerPools.length > 0;
|
||||
// Sample Balancer off-chain (i.e. via GraphQL query + `computeBalancerBuy/SellQuote`)
|
||||
// if cached values are stale
|
||||
const offChain = cachedBalancerPools === undefined;
|
||||
return { onChain, offChain };
|
||||
}
|
||||
|
||||
protected async _getPoolsForPairAsync(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
cacheExpiryMs: number = FIVE_SECONDS_MS,
|
||||
): Promise<BalancerPool[]> {
|
||||
const key = JSON.stringify([takerToken, makerToken]);
|
||||
const value = this._cache[key];
|
||||
const minTimestamp = Date.now() - cacheExpiryMs;
|
||||
if (value === undefined || value.timestamp < minTimestamp) {
|
||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||
const timestamp = Date.now();
|
||||
|
@ -31,11 +31,7 @@ export class BancorService {
|
||||
return this._sdk;
|
||||
}
|
||||
|
||||
public async getQuoteAsync(
|
||||
fromToken: string,
|
||||
toToken: string,
|
||||
amount: BigNumber = new BigNumber(1),
|
||||
): Promise<Quote<BancorFillData>> {
|
||||
public async getQuoteAsync(fromToken: string, toToken: string, amount: BigNumber): Promise<Quote<BancorFillData>> {
|
||||
const sdk = await this.getSDKAsync();
|
||||
const blockchain = sdk._core.blockchains[BlockchainType.Ethereum] as Ethereum;
|
||||
const sourceDecimals = await getDecimals(blockchain, fromToken);
|
||||
|
@ -138,6 +138,7 @@ export const ONE_ETHER = new BigNumber(1e18);
|
||||
export const NEGATIVE_INF = new BigNumber('-Infinity');
|
||||
export const POSITIVE_INF = new BigNumber('Infinity');
|
||||
export const ZERO_AMOUNT = new BigNumber(0);
|
||||
export const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
||||
export const ONE_HOUR_IN_SECONDS = 60 * 60;
|
||||
export const ONE_SECOND_MS = 1000;
|
||||
export const NULL_BYTES = '0x';
|
||||
|
@ -4,7 +4,7 @@ import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
|
||||
import { fillableAmountsUtils } from '../../utils/fillable_amounts_utils';
|
||||
|
||||
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
||||
import { CollapsedFill, DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillFlags } from './types';
|
||||
import { CollapsedFill, DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillFlags, MultiHopFillData } from './types';
|
||||
|
||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||
|
||||
@ -155,6 +155,22 @@ function dexQuotesToPaths(
|
||||
return paths;
|
||||
}
|
||||
|
||||
export function getTwoHopAdjustedRate(
|
||||
side: MarketOperation,
|
||||
twoHopQuote: DexSample<MultiHopFillData>,
|
||||
targetInput: BigNumber,
|
||||
ethToOutputRate: BigNumber,
|
||||
fees: FeeSchedule = {},
|
||||
): BigNumber {
|
||||
const { output, input, fillData } = twoHopQuote;
|
||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||
return ZERO_AMOUNT;
|
||||
}
|
||||
const penalty = ethToOutputRate.times(fees[ERC20BridgeSource.MultiHop]!(fillData));
|
||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
|
||||
}
|
||||
|
||||
function sourceToFillFlags(source: ERC20BridgeSource): number {
|
||||
switch (source) {
|
||||
case ERC20BridgeSource.Uniswap:
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import { ZERO_AMOUNT } from '@0x/order-utils';
|
||||
import { RFQTIndicativeQuote } from '@0x/quote-server';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
||||
@ -9,11 +8,20 @@ import { MarketOperation } from '../../types';
|
||||
import { QuoteRequestor } from '../quote_requestor';
|
||||
import { difference } from '../utils';
|
||||
|
||||
import { QuoteReportGenerator } from './../quote_report_generator';
|
||||
import { BUY_SOURCES, DEFAULT_GET_MARKET_ORDERS_OPTS, FEE_QUOTE_SOURCES, ONE_ETHER, SELL_SOURCES } from './constants';
|
||||
import { generateQuoteReport } from './../quote_report_generator';
|
||||
import {
|
||||
BUY_SOURCES,
|
||||
DEFAULT_GET_MARKET_ORDERS_OPTS,
|
||||
FEE_QUOTE_SOURCES,
|
||||
ONE_ETHER,
|
||||
SELL_SOURCES,
|
||||
ZERO_AMOUNT,
|
||||
} from './constants';
|
||||
import { createFillPaths, getPathAdjustedRate, getPathAdjustedSlippage } from './fills';
|
||||
import { getBestTwoHopQuote } from './multihop_utils';
|
||||
import {
|
||||
createOrdersFromPath,
|
||||
createOrdersFromTwoHopSample,
|
||||
createSignedOrdersFromRfqtIndicativeQuotes,
|
||||
createSignedOrdersWithFillableAmounts,
|
||||
getNativeOrderTokens,
|
||||
@ -28,10 +36,13 @@ import {
|
||||
GetMarketOrdersOpts,
|
||||
MarketSideLiquidity,
|
||||
OptimizedMarketOrder,
|
||||
OptimizedOrdersAndQuoteReport,
|
||||
OptimizerResult,
|
||||
OrderDomain,
|
||||
TokenAdjacencyGraph,
|
||||
} from './types';
|
||||
|
||||
// tslint:disable:boolean-naming
|
||||
|
||||
/**
|
||||
* Returns a indicative quotes or an empty array if RFQT is not enabled or requested
|
||||
* @param makerAssetData the maker asset data
|
||||
@ -70,6 +81,7 @@ export class MarketOperationUtils {
|
||||
private readonly contractAddresses: ContractAddresses,
|
||||
private readonly _orderDomain: OrderDomain,
|
||||
private readonly _liquidityProviderRegistry: string = NULL_ADDRESS,
|
||||
private readonly _tokenAdjacencyGraph: TokenAdjacencyGraph = {},
|
||||
) {
|
||||
this._wethAddress = contractAddresses.etherToken.toLowerCase();
|
||||
this._multiBridge = contractAddresses.multiBridge.toLowerCase();
|
||||
@ -94,54 +106,62 @@ export class MarketOperationUtils {
|
||||
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
||||
const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||
|
||||
const {
|
||||
onChain: sampleBalancerOnChain,
|
||||
offChain: sampleBalancerOffChain,
|
||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
|
||||
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
// Get native order fillable amounts.
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||
// Get the custom liquidity provider from registry.
|
||||
DexOrderSampler.ops.getLiquidityProviderFromRegistry(
|
||||
this._liquidityProviderRegistry,
|
||||
makerToken,
|
||||
takerToken,
|
||||
),
|
||||
this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||
// Get ETH -> maker token price.
|
||||
await DexOrderSampler.ops.getMedianSellRateAsync(
|
||||
this._sampler.getMedianSellRate(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
makerToken,
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
this._sampler.bancorService,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
await DexOrderSampler.ops.getMedianSellRateAsync(
|
||||
this._sampler.getMedianSellRate(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
takerToken,
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
),
|
||||
// Get sell quotes for taker -> maker.
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
this._sampler.getSellQuotes(
|
||||
difference(
|
||||
SELL_SOURCES.concat(this._optionalSources()),
|
||||
_opts.excludedSources.concat(ERC20BridgeSource.Balancer),
|
||||
_opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
|
||||
),
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
this._sampler.bancorService,
|
||||
),
|
||||
_opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
|
||||
? DexOrderSampler.constant([])
|
||||
: this._sampler.getTwoHopSellQuotes(
|
||||
difference(
|
||||
SELL_SOURCES.concat(this._optionalSources()),
|
||||
_opts.excludedSources.concat(ERC20BridgeSource.MultiBridge),
|
||||
),
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerAmount,
|
||||
this._tokenAdjacencyGraph,
|
||||
this._wethAddress,
|
||||
this._liquidityProviderRegistry,
|
||||
),
|
||||
);
|
||||
|
||||
const rfqtPromise = getRfqtIndicativeQuotesAsync(
|
||||
@ -152,45 +172,33 @@ export class MarketOperationUtils {
|
||||
_opts,
|
||||
);
|
||||
|
||||
const balancerPromise = DexOrderSampler.ops
|
||||
.getSellQuotesAsync(
|
||||
difference([ERC20BridgeSource.Balancer], _opts.excludedSources),
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
this._sampler.bancorService,
|
||||
)
|
||||
.then(async r => this._sampler.executeAsync(r));
|
||||
const offChainBalancerPromise = sampleBalancerOffChain
|
||||
? this._sampler.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const offChainBancorPromise = _opts.excludedSources.includes(ERC20BridgeSource.Bancor)
|
||||
? Promise.resolve([])
|
||||
: this._sampler.getBancorSellQuotesOffChainAsync(makerToken, takerToken, [takerAmount]);
|
||||
|
||||
const [
|
||||
[orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes],
|
||||
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
||||
rfqtIndicativeQuotes,
|
||||
[balancerQuotes],
|
||||
] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]);
|
||||
offChainBalancerQuotes,
|
||||
offChainBancorQuotes,
|
||||
] = await Promise.all([samplerPromise, rfqtPromise, offChainBalancerPromise, offChainBancorPromise]);
|
||||
|
||||
// Attach the LiquidityProvider address to the sample fillData
|
||||
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.LiquidityProvider) || []).forEach(
|
||||
q => (q.fillData = { poolAddress: liquidityProviderAddress }),
|
||||
);
|
||||
// Attach the MultiBridge address to the sample fillData
|
||||
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach(
|
||||
q => (q.fillData = { poolAddress: this._multiBridge }),
|
||||
);
|
||||
return {
|
||||
side: MarketOperation.Sell,
|
||||
inputAmount: takerAmount,
|
||||
inputToken: takerToken,
|
||||
outputToken: makerToken,
|
||||
dexQuotes: dexQuotes.concat(balancerQuotes),
|
||||
dexQuotes: dexQuotes.concat([...offChainBalancerQuotes, offChainBancorQuotes]),
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
ethToOutputRate: ethToMakerAssetRate,
|
||||
ethToInputRate: ethToTakerAssetRate,
|
||||
rfqtIndicativeQuotes,
|
||||
twoHopQuotes,
|
||||
};
|
||||
}
|
||||
|
||||
@ -213,69 +221,72 @@ export class MarketOperationUtils {
|
||||
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
|
||||
const sampleAmounts = getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase);
|
||||
|
||||
const {
|
||||
onChain: sampleBalancerOnChain,
|
||||
offChain: sampleBalancerOffChain,
|
||||
} = this._sampler.balancerPoolsCache.howToSampleBalancer(takerToken, makerToken, _opts.excludedSources);
|
||||
|
||||
// Call the sampler contract.
|
||||
const samplerPromise = this._sampler.executeAsync(
|
||||
// Get native order fillable amounts.
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||
// Get the custom liquidity provider from registry.
|
||||
DexOrderSampler.ops.getLiquidityProviderFromRegistry(
|
||||
this._liquidityProviderRegistry,
|
||||
makerToken,
|
||||
takerToken,
|
||||
),
|
||||
// Get ETH -> maker token price.
|
||||
await DexOrderSampler.ops.getMedianSellRateAsync(
|
||||
this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
|
||||
// Get ETH -> makerToken token price.
|
||||
this._sampler.getMedianSellRate(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
makerToken,
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
),
|
||||
// Get ETH -> taker token price.
|
||||
await DexOrderSampler.ops.getMedianSellRateAsync(
|
||||
this._sampler.getMedianSellRate(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
takerToken,
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._multiBridge,
|
||||
this._sampler.bancorService,
|
||||
),
|
||||
// Get buy quotes for taker -> maker.
|
||||
await DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
this._sampler.getBuyQuotes(
|
||||
difference(
|
||||
BUY_SOURCES.concat(
|
||||
this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [],
|
||||
),
|
||||
_opts.excludedSources.concat([ERC20BridgeSource.Balancer]),
|
||||
_opts.excludedSources.concat(sampleBalancerOnChain ? [] : ERC20BridgeSource.Balancer),
|
||||
),
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._sampler.bancorService,
|
||||
),
|
||||
_opts.excludedSources.includes(ERC20BridgeSource.MultiHop)
|
||||
? DexOrderSampler.constant([])
|
||||
: this._sampler.getTwoHopBuyQuotes(
|
||||
difference(
|
||||
BUY_SOURCES.concat(
|
||||
this._liquidityProviderRegistry !== NULL_ADDRESS
|
||||
? [ERC20BridgeSource.LiquidityProvider]
|
||||
: [],
|
||||
),
|
||||
_opts.excludedSources,
|
||||
),
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerAmount,
|
||||
this._tokenAdjacencyGraph,
|
||||
this._wethAddress,
|
||||
this._liquidityProviderRegistry,
|
||||
),
|
||||
);
|
||||
|
||||
const balancerPromise = this._sampler.executeAsync(
|
||||
await DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
difference([ERC20BridgeSource.Balancer], _opts.excludedSources),
|
||||
makerToken,
|
||||
takerToken,
|
||||
sampleAmounts,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
this._liquidityProviderRegistry,
|
||||
this._sampler.bancorService,
|
||||
),
|
||||
);
|
||||
const offChainBalancerPromise = sampleBalancerOffChain
|
||||
? this._sampler.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, sampleAmounts)
|
||||
: Promise.resolve([]);
|
||||
|
||||
const rfqtPromise = getRfqtIndicativeQuotesAsync(
|
||||
nativeOrders[0].makerAssetData,
|
||||
@ -285,14 +296,10 @@ export class MarketOperationUtils {
|
||||
_opts,
|
||||
);
|
||||
const [
|
||||
[orderFillableAmounts, liquidityProviderAddress, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes],
|
||||
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
|
||||
rfqtIndicativeQuotes,
|
||||
[balancerQuotes],
|
||||
] = await Promise.all([samplerPromise, rfqtPromise, balancerPromise]);
|
||||
// Attach the LiquidityProvider address to the sample fillData
|
||||
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.LiquidityProvider) || []).forEach(
|
||||
q => (q.fillData = { poolAddress: liquidityProviderAddress }),
|
||||
);
|
||||
offChainBalancerQuotes,
|
||||
] = await Promise.all([samplerPromise, rfqtPromise, offChainBalancerPromise]);
|
||||
// Attach the MultiBridge address to the sample fillData
|
||||
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach(
|
||||
q => (q.fillData = { poolAddress: this._multiBridge }),
|
||||
@ -302,12 +309,13 @@ export class MarketOperationUtils {
|
||||
inputAmount: makerAmount,
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
dexQuotes: dexQuotes.concat(balancerQuotes),
|
||||
dexQuotes: dexQuotes.concat(offChainBalancerQuotes),
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
ethToOutputRate: ethToTakerAssetRate,
|
||||
ethToInputRate: ethToMakerAssetRate,
|
||||
rfqtIndicativeQuotes,
|
||||
twoHopQuotes,
|
||||
};
|
||||
}
|
||||
|
||||
@ -323,7 +331,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedOrder[],
|
||||
takerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<OptimizedOrdersAndQuoteReport> {
|
||||
): Promise<OptimizerResult> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const marketSideLiquidity = await this.getMarketSellLiquidityAsync(nativeOrders, takerAmount, _opts);
|
||||
return this._generateOptimizedOrdersAsync(marketSideLiquidity, {
|
||||
@ -349,7 +357,7 @@ export class MarketOperationUtils {
|
||||
nativeOrders: SignedOrder[],
|
||||
makerAmount: BigNumber,
|
||||
opts?: Partial<GetMarketOrdersOpts>,
|
||||
): Promise<OptimizedOrdersAndQuoteReport> {
|
||||
): Promise<OptimizerResult> {
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
const marketSideLiquidity = await this.getMarketBuyLiquidityAsync(nativeOrders, makerAmount, _opts);
|
||||
return this._generateOptimizedOrdersAsync(marketSideLiquidity, {
|
||||
@ -384,35 +392,29 @@ export class MarketOperationUtils {
|
||||
}
|
||||
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
|
||||
|
||||
const sources = difference(BUY_SOURCES, _opts.excludedSources);
|
||||
const sources = difference(BUY_SOURCES, _opts.excludedSources.concat(ERC20BridgeSource.Balancer));
|
||||
const ops = [
|
||||
...batchNativeOrders.map(orders =>
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
|
||||
this._sampler.getOrderFillableMakerAmounts(orders, this.contractAddresses.exchange),
|
||||
),
|
||||
...(await Promise.all(
|
||||
batchNativeOrders.map(async orders =>
|
||||
DexOrderSampler.ops.getMedianSellRateAsync(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
getNativeOrderTokens(orders[0])[1],
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
),
|
||||
...batchNativeOrders.map(orders =>
|
||||
this._sampler.getMedianSellRate(
|
||||
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
|
||||
getNativeOrderTokens(orders[0])[1],
|
||||
this._wethAddress,
|
||||
ONE_ETHER,
|
||||
this._wethAddress,
|
||||
),
|
||||
)),
|
||||
...(await Promise.all(
|
||||
batchNativeOrders.map(async (orders, i) =>
|
||||
DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
sources,
|
||||
getNativeOrderTokens(orders[0])[0],
|
||||
getNativeOrderTokens(orders[0])[1],
|
||||
[makerAmounts[i]],
|
||||
this._wethAddress,
|
||||
this._sampler.balancerPoolsCache,
|
||||
),
|
||||
),
|
||||
...batchNativeOrders.map((orders, i) =>
|
||||
this._sampler.getBuyQuotes(
|
||||
sources,
|
||||
getNativeOrderTokens(orders[0])[0],
|
||||
getNativeOrderTokens(orders[0])[1],
|
||||
[makerAmounts[i]],
|
||||
this._wethAddress,
|
||||
),
|
||||
)),
|
||||
),
|
||||
];
|
||||
|
||||
const executeResults = await this._sampler.executeBatchAsync(ops);
|
||||
@ -444,6 +446,7 @@ export class MarketOperationUtils {
|
||||
rfqtIndicativeQuotes: [],
|
||||
inputToken: makerToken,
|
||||
outputToken: takerToken,
|
||||
twoHopQuotes: [],
|
||||
},
|
||||
{
|
||||
bridgeSlippage: _opts.bridgeSlippage,
|
||||
@ -476,7 +479,7 @@ export class MarketOperationUtils {
|
||||
shouldBatchBridgeOrders?: boolean;
|
||||
quoteRequestor?: QuoteRequestor;
|
||||
},
|
||||
): Promise<OptimizedOrdersAndQuoteReport> {
|
||||
): Promise<OptimizerResult> {
|
||||
const {
|
||||
inputToken,
|
||||
outputToken,
|
||||
@ -488,8 +491,20 @@ export class MarketOperationUtils {
|
||||
dexQuotes,
|
||||
ethToOutputRate,
|
||||
ethToInputRate,
|
||||
twoHopQuotes,
|
||||
} = marketSideLiquidity;
|
||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
||||
|
||||
const orderOpts = {
|
||||
side,
|
||||
inputToken,
|
||||
outputToken,
|
||||
orderDomain: this._orderDomain,
|
||||
contractAddresses: this.contractAddresses,
|
||||
bridgeSlippage: opts.bridgeSlippage || 0,
|
||||
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
|
||||
};
|
||||
|
||||
// Convert native orders and dex quotes into fill paths.
|
||||
const paths = createFillPaths({
|
||||
side,
|
||||
@ -505,12 +520,33 @@ export class MarketOperationUtils {
|
||||
excludedSources: opts.excludedSources,
|
||||
feeSchedule: opts.feeSchedule,
|
||||
});
|
||||
|
||||
// Find the optimal path.
|
||||
let optimalPath = (await findOptimalPathAsync(side, paths, inputAmount, opts.runLimit)) || [];
|
||||
if (optimalPath.length === 0) {
|
||||
throw new Error(AggregationError.NoOptimalPath);
|
||||
}
|
||||
// Generate a fallback path if native orders are in the optimal paath.
|
||||
const optimalPathRate = getPathAdjustedRate(side, optimalPath, inputAmount);
|
||||
|
||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||
marketSideLiquidity,
|
||||
opts.feeSchedule,
|
||||
);
|
||||
if (bestTwoHopRate.isGreaterThan(optimalPathRate)) {
|
||||
const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote!, orderOpts);
|
||||
const twoHopQuoteReport = generateQuoteReport(
|
||||
side,
|
||||
_.flatten(dexQuotes),
|
||||
twoHopQuotes,
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
bestTwoHopQuote!,
|
||||
opts.quoteRequestor,
|
||||
);
|
||||
return { optimizedOrders: twoHopOrders, quoteReport: twoHopQuoteReport, isTwoHop: true };
|
||||
}
|
||||
|
||||
// Generate a fallback path if native orders are in the optimal path.
|
||||
const nativeSubPath = optimalPath.filter(f => f.source === ERC20BridgeSource.Native);
|
||||
if (opts.allowFallback && nativeSubPath.length !== 0) {
|
||||
// We create a fallback path that is exclusive of Native liquidity
|
||||
@ -519,12 +555,7 @@ export class MarketOperationUtils {
|
||||
const nonNativeOptimalPath =
|
||||
(await findOptimalPathAsync(side, nonNativePaths, inputAmount, opts.runLimit)) || [];
|
||||
// Calculate the slippage of on-chain sources compared to the most optimal path
|
||||
const fallbackSlippage = getPathAdjustedSlippage(
|
||||
side,
|
||||
nonNativeOptimalPath,
|
||||
inputAmount,
|
||||
getPathAdjustedRate(side, optimalPath, inputAmount),
|
||||
);
|
||||
const fallbackSlippage = getPathAdjustedSlippage(side, nonNativeOptimalPath, inputAmount, optimalPathRate);
|
||||
if (nativeSubPath.length === optimalPath.length || fallbackSlippage <= maxFallbackSlippage) {
|
||||
// If the last fill is Native and penultimate is not, then the intention was to partial fill
|
||||
// In this case we drop it entirely as we can't handle a failure at the end and we don't
|
||||
@ -542,24 +573,17 @@ export class MarketOperationUtils {
|
||||
optimalPath = [...nativeSubPath.filter(f => f !== lastNativeFillIfExists), ...nonNativeOptimalPath];
|
||||
}
|
||||
}
|
||||
const optimizedOrders = createOrdersFromPath(optimalPath, {
|
||||
side,
|
||||
inputToken,
|
||||
outputToken,
|
||||
orderDomain: this._orderDomain,
|
||||
contractAddresses: this.contractAddresses,
|
||||
bridgeSlippage: opts.bridgeSlippage || 0,
|
||||
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
|
||||
});
|
||||
const quoteReport = new QuoteReportGenerator(
|
||||
const optimizedOrders = createOrdersFromPath(optimalPath, orderOpts);
|
||||
const quoteReport = generateQuoteReport(
|
||||
side,
|
||||
_.flatten(dexQuotes),
|
||||
twoHopQuotes,
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
_.flatten(optimizedOrders.map(o => o.fills)),
|
||||
_.flatten(optimizedOrders.map(order => order.fills)),
|
||||
opts.quoteRequestor,
|
||||
).generateReport();
|
||||
return { optimizedOrders, quoteReport };
|
||||
);
|
||||
return { optimizedOrders, quoteReport, isTwoHop: false };
|
||||
}
|
||||
|
||||
private _optionalSources(): ERC20BridgeSource[] {
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ZERO_AMOUNT } from './constants';
|
||||
import { getTwoHopAdjustedRate } from './fills';
|
||||
import { DexSample, FeeSchedule, MarketSideLiquidity, MultiHopFillData, TokenAdjacencyGraph } from './types';
|
||||
|
||||
/**
|
||||
* Given a token pair, returns the intermediate tokens to consider for two-hop routes.
|
||||
*/
|
||||
export function getIntermediateTokens(
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
tokenAdjacencyGraph: TokenAdjacencyGraph,
|
||||
wethAddress: string,
|
||||
): string[] {
|
||||
let intermediateTokens = [];
|
||||
if (makerToken === wethAddress) {
|
||||
intermediateTokens = _.get(tokenAdjacencyGraph, takerToken, [] as string[]);
|
||||
} else if (takerToken === wethAddress) {
|
||||
intermediateTokens = _.get(tokenAdjacencyGraph, makerToken, [] as string[]);
|
||||
} else {
|
||||
intermediateTokens = _.union(
|
||||
_.intersection(_.get(tokenAdjacencyGraph, takerToken, []), _.get(tokenAdjacencyGraph, makerToken, [])),
|
||||
[wethAddress],
|
||||
);
|
||||
}
|
||||
return intermediateTokens.filter(
|
||||
token => token.toLowerCase() !== makerToken.toLowerCase() && token.toLowerCase() !== takerToken.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best two-hop quote and the fee-adjusted rate of that quote.
|
||||
*/
|
||||
export function getBestTwoHopQuote(
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
feeSchedule?: FeeSchedule,
|
||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||
const { side, inputAmount, ethToOutputRate, twoHopQuotes } = marketSideLiquidity;
|
||||
return twoHopQuotes
|
||||
.map(quote => getTwoHopAdjustedRate(side, quote, inputAmount, ethToOutputRate, feeSchedule))
|
||||
.reduce(
|
||||
(prev, curr, i) =>
|
||||
curr.isGreaterThan(prev.adjustedRate) ? { adjustedRate: curr, quote: twoHopQuotes[i] } : prev,
|
||||
{
|
||||
adjustedRate: ZERO_AMOUNT,
|
||||
quote: undefined as DexSample<MultiHopFillData> | undefined,
|
||||
},
|
||||
);
|
||||
}
|
@ -8,6 +8,7 @@ import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
|
||||
|
||||
import {
|
||||
ERC20_PROXY_ID,
|
||||
MAX_UINT256,
|
||||
NULL_ADDRESS,
|
||||
NULL_BYTES,
|
||||
ONE_HOUR_IN_SECONDS,
|
||||
@ -23,10 +24,12 @@ import {
|
||||
BancorFillData,
|
||||
CollapsedFill,
|
||||
CurveFillData,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
Fill,
|
||||
LiquidityProviderFillData,
|
||||
MultiBridgeFillData,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
OptimizedMarketOrder,
|
||||
OrderDomain,
|
||||
@ -150,6 +153,7 @@ export interface CreateOrderFromPathOpts {
|
||||
|
||||
// Convert sell fills into orders.
|
||||
export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder[] {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
const collapsedPath = collapsePath(path);
|
||||
const orders: OptimizedMarketOrder[] = [];
|
||||
for (let i = 0; i < collapsedPath.length; ) {
|
||||
@ -168,7 +172,7 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts
|
||||
}
|
||||
// Always use DexForwarderBridge unless configured not to
|
||||
if (!opts.shouldBatchBridgeOrders) {
|
||||
orders.push(createBridgeOrder(contiguousBridgeFills[0], opts));
|
||||
orders.push(createBridgeOrder(contiguousBridgeFills[0], makerToken, takerToken, opts));
|
||||
i += 1;
|
||||
} else {
|
||||
orders.push(createBatchedBridgeOrder(contiguousBridgeFills, opts));
|
||||
@ -178,9 +182,36 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts
|
||||
return orders;
|
||||
}
|
||||
|
||||
export function createOrdersFromTwoHopSample(
|
||||
sample: DexSample<MultiHopFillData>,
|
||||
opts: CreateOrderFromPathOpts,
|
||||
): OptimizedMarketOrder[] {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData!;
|
||||
const firstHopFill: CollapsedFill = {
|
||||
sourcePathId: '',
|
||||
source: firstHopSource.source,
|
||||
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||
subFills: [],
|
||||
fillData: firstHopSource.fillData,
|
||||
};
|
||||
const secondHopFill: CollapsedFill = {
|
||||
sourcePathId: '',
|
||||
source: secondHopSource.source,
|
||||
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||
subFills: [],
|
||||
fillData: secondHopSource.fillData,
|
||||
};
|
||||
return [
|
||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts),
|
||||
createBridgeOrder(secondHopFill, makerToken, intermediateToken, opts),
|
||||
];
|
||||
}
|
||||
|
||||
function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPathOpts): string {
|
||||
const source = fill.source;
|
||||
switch (source) {
|
||||
switch (fill.source) {
|
||||
case ERC20BridgeSource.Eth2Dai:
|
||||
return opts.contractAddresses.eth2DaiBridge;
|
||||
case ERC20BridgeSource.Kyber:
|
||||
@ -209,8 +240,12 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
|
||||
throw new Error(AggregationError.NoBridgeForSource);
|
||||
}
|
||||
|
||||
function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): OptimizedMarketOrder {
|
||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||
function createBridgeOrder(
|
||||
fill: CollapsedFill,
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
opts: CreateOrderFromPathOpts,
|
||||
): OptimizedMarketOrder {
|
||||
const bridgeAddress = getBridgeAddressFromFill(fill, opts);
|
||||
|
||||
let makerAssetData;
|
||||
@ -277,7 +312,7 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts):
|
||||
takerAssetAmount: slippedTakerAssetAmount,
|
||||
fillableMakerAssetAmount: slippedMakerAssetAmount,
|
||||
fillableTakerAssetAmount: slippedTakerAssetAmount,
|
||||
...createCommonBridgeOrderFields(opts),
|
||||
...createCommonBridgeOrderFields(opts.orderDomain),
|
||||
};
|
||||
}
|
||||
|
||||
@ -290,7 +325,7 @@ function createBatchedBridgeOrder(fills: CollapsedFill[], opts: CreateOrderFromP
|
||||
calls: [],
|
||||
};
|
||||
for (const fill of fills) {
|
||||
const bridgeOrder = createBridgeOrder(fill, opts);
|
||||
const bridgeOrder = createBridgeOrder(fill, makerToken, takerToken, opts);
|
||||
totalMakerAssetAmount = totalMakerAssetAmount.plus(bridgeOrder.makerAssetAmount);
|
||||
totalTakerAssetAmount = totalTakerAssetAmount.plus(bridgeOrder.takerAssetAmount);
|
||||
const { bridgeAddress, bridgeData: orderBridgeData } = assetDataUtils.decodeAssetDataOrThrow(
|
||||
@ -318,7 +353,7 @@ function createBatchedBridgeOrder(fills: CollapsedFill[], opts: CreateOrderFromP
|
||||
takerAssetAmount: totalTakerAssetAmount,
|
||||
fillableMakerAssetAmount: totalMakerAssetAmount,
|
||||
fillableTakerAssetAmount: totalTakerAssetAmount,
|
||||
...createCommonBridgeOrderFields(opts),
|
||||
...createCommonBridgeOrderFields(opts.orderDomain),
|
||||
};
|
||||
}
|
||||
|
||||
@ -395,7 +430,7 @@ function getSlippedBridgeAssetAmounts(fill: CollapsedFill, opts: CreateOrderFrom
|
||||
// Taker asset amount.
|
||||
opts.side === MarketOperation.Sell
|
||||
? fill.input
|
||||
: fill.output.times(opts.bridgeSlippage + 1).integerValue(BigNumber.ROUND_UP),
|
||||
: BigNumber.min(fill.output.times(opts.bridgeSlippage + 1).integerValue(BigNumber.ROUND_UP), MAX_UINT256),
|
||||
];
|
||||
}
|
||||
|
||||
@ -414,7 +449,7 @@ type CommonBridgeOrderFields = Pick<
|
||||
>
|
||||
>;
|
||||
|
||||
function createCommonBridgeOrderFields(opts: CreateOrderFromPathOpts): CommonBridgeOrderFields {
|
||||
function createCommonBridgeOrderFields(orderDomain: OrderDomain): CommonBridgeOrderFields {
|
||||
return {
|
||||
takerAddress: NULL_ADDRESS,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
@ -428,7 +463,7 @@ function createCommonBridgeOrderFields(opts: CreateOrderFromPathOpts): CommonBri
|
||||
takerFee: ZERO_AMOUNT,
|
||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
||||
signature: WALLET_SIGNATURE,
|
||||
...opts.orderDomain,
|
||||
...orderDomain,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { BalancerPoolsCache } from './balancer_utils';
|
||||
import { BancorService } from './bancor_service';
|
||||
import { samplerOperations } from './sampler_operations';
|
||||
import { SamplerOperations } from './sampler_operations';
|
||||
import { BatchedOperation } from './types';
|
||||
|
||||
/**
|
||||
@ -30,19 +30,15 @@ type BatchedOperationResult<T> = T extends BatchedOperation<infer TResult> ? TRe
|
||||
/**
|
||||
* Encapsulates interactions with the `ERC20BridgeSampler` contract.
|
||||
*/
|
||||
export class DexOrderSampler {
|
||||
/**
|
||||
* Composable operations that can be batched in a single transaction,
|
||||
* for use with `DexOrderSampler.executeAsync()`.
|
||||
*/
|
||||
public static ops = samplerOperations;
|
||||
|
||||
export class DexOrderSampler extends SamplerOperations {
|
||||
constructor(
|
||||
private readonly _samplerContract: ERC20BridgeSamplerContract,
|
||||
_samplerContract: ERC20BridgeSamplerContract,
|
||||
private readonly _samplerOverrides?: SamplerOverrides,
|
||||
public bancorService?: BancorService,
|
||||
public balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
|
||||
) {}
|
||||
bancorService?: BancorService,
|
||||
balancerPoolsCache?: BalancerPoolsCache,
|
||||
) {
|
||||
super(_samplerContract, bancorService, balancerPoolsCache);
|
||||
}
|
||||
|
||||
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
|
||||
|
||||
@ -142,16 +138,14 @@ export class DexOrderSampler {
|
||||
* Takes an arbitrary length array, but is not typesafe.
|
||||
*/
|
||||
public async executeBatchAsync<T extends Array<BatchedOperation<any>>>(ops: T): Promise<any[]> {
|
||||
const callDatas = ops.map(o => o.encodeCall(this._samplerContract));
|
||||
const callDatas = ops.map(o => o.encodeCall());
|
||||
const { overrides, block } = this._samplerOverrides
|
||||
? this._samplerOverrides
|
||||
: { overrides: undefined, block: undefined };
|
||||
|
||||
// All operations are NOOPs
|
||||
if (callDatas.every(cd => cd === NULL_BYTES)) {
|
||||
return Promise.all(
|
||||
callDatas.map(async (_callData, i) => ops[i].handleCallResultsAsync(this._samplerContract, NULL_BYTES)),
|
||||
);
|
||||
return callDatas.map((_callData, i) => ops[i].handleCallResults(NULL_BYTES));
|
||||
}
|
||||
// Execute all non-empty calldatas.
|
||||
const rawCallResults = await this._samplerContract
|
||||
@ -159,11 +153,9 @@ export class DexOrderSampler {
|
||||
.callAsync({ overrides }, block);
|
||||
// Return the parsed results.
|
||||
let rawCallResultsIdx = 0;
|
||||
return Promise.all(
|
||||
callDatas.map(async (callData, i) => {
|
||||
const result = callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : NULL_BYTES;
|
||||
return ops[i].handleCallResultsAsync(this._samplerContract, result);
|
||||
}),
|
||||
);
|
||||
return callDatas.map((callData, i) => {
|
||||
const result = callData !== NULL_BYTES ? rawCallResults[rawCallResultsIdx++] : NULL_BYTES;
|
||||
return ops[i].handleCallResults(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
import { ContractFunctionObj } from '@0x/base-contract';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
|
||||
import { ERC20BridgeSource, FillData, SourceInfo, SourceQuoteOperation } from './types';
|
||||
|
||||
export type Parameters<T> = T extends (...args: infer TArgs) => any ? TArgs : never;
|
||||
|
||||
export interface SamplerContractCall<
|
||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
||||
TFillData extends FillData = FillData
|
||||
> {
|
||||
contract: ERC20BridgeSamplerContract;
|
||||
function: TFunc;
|
||||
params: Parameters<TFunc>;
|
||||
callback?: (callResults: string, fillData: TFillData) => BigNumber[];
|
||||
}
|
||||
|
||||
export class SamplerContractOperation<
|
||||
TFunc extends (...args: any[]) => ContractFunctionObj<any>,
|
||||
TFillData extends FillData = FillData
|
||||
> implements SourceQuoteOperation<TFillData> {
|
||||
public readonly source: ERC20BridgeSource;
|
||||
public fillData: TFillData;
|
||||
private readonly _samplerContract: ERC20BridgeSamplerContract;
|
||||
private readonly _samplerFunction: TFunc;
|
||||
private readonly _params: Parameters<TFunc>;
|
||||
private readonly _callback?: (callResults: string, fillData: TFillData) => BigNumber[];
|
||||
|
||||
constructor(opts: SourceInfo<TFillData> & SamplerContractCall<TFunc, TFillData>) {
|
||||
this.source = opts.source;
|
||||
this.fillData = opts.fillData || ({} as TFillData); // tslint:disable-line:no-object-literal-type-assertion
|
||||
this._samplerContract = opts.contract;
|
||||
this._samplerFunction = opts.function;
|
||||
this._params = opts.params;
|
||||
this._callback = opts.callback;
|
||||
}
|
||||
|
||||
public encodeCall(): string {
|
||||
return this._samplerFunction
|
||||
.bind(this._samplerContract)(...this._params)
|
||||
.getABIEncodedTransactionData();
|
||||
}
|
||||
public handleCallResults(callResults: string): BigNumber[] {
|
||||
if (this._callback !== undefined) {
|
||||
return this._callback(callResults, this.fillData);
|
||||
} else {
|
||||
return this._samplerContract.getABIDecodedReturnData<BigNumber[]>(this._samplerFunction.name, callResults);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,6 @@ import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../../types';
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
|
||||
import { QuoteReport } from '../quote_report_generator';
|
||||
|
||||
/**
|
||||
@ -41,6 +40,7 @@ export enum ERC20BridgeSource {
|
||||
Bancor = 'Bancor',
|
||||
MStable = 'mStable',
|
||||
Mooniswap = 'Mooniswap',
|
||||
MultiHop = 'MultiHop',
|
||||
}
|
||||
|
||||
// tslint:disable: enum-naming
|
||||
@ -72,6 +72,11 @@ export interface CurveInfo {
|
||||
// Internal `fillData` field for `Fill` objects.
|
||||
export interface FillData {}
|
||||
|
||||
export interface SourceInfo<TFillData extends FillData = FillData> {
|
||||
source: ERC20BridgeSource;
|
||||
fillData?: TFillData;
|
||||
}
|
||||
|
||||
// `FillData` for native fills.
|
||||
export interface NativeFillData extends FillData {
|
||||
order: SignedOrderWithFillableAmounts;
|
||||
@ -108,14 +113,23 @@ export interface Quote<TFillData = FillData> {
|
||||
fillData?: TFillData;
|
||||
}
|
||||
|
||||
export interface HopInfo {
|
||||
sourceIndex: BigNumber;
|
||||
returnData: string;
|
||||
}
|
||||
|
||||
export interface MultiHopFillData extends FillData {
|
||||
firstHopSource: SourceQuoteOperation;
|
||||
secondHopSource: SourceQuoteOperation;
|
||||
intermediateToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual DEX sample from the sampler contract.
|
||||
*/
|
||||
export interface DexSample<TFillData extends FillData = FillData> {
|
||||
source: ERC20BridgeSource;
|
||||
export interface DexSample<TFillData extends FillData = FillData> extends SourceInfo<TFillData> {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
fillData?: TFillData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +145,7 @@ export enum FillFlags {
|
||||
/**
|
||||
* Represents a node on a fill path.
|
||||
*/
|
||||
export interface Fill<TFillData extends FillData = FillData> {
|
||||
export interface Fill<TFillData extends FillData = FillData> extends SourceInfo<TFillData> {
|
||||
// Unique ID of the original source path this fill belongs to.
|
||||
// This is generated when the path is generated and is useful to distinguish
|
||||
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
||||
@ -148,25 +162,16 @@ export interface Fill<TFillData extends FillData = FillData> {
|
||||
parent?: Fill;
|
||||
// The index of the fill in the original path.
|
||||
index: number;
|
||||
// The source of the fill. See `ERC20BridgeSource`.
|
||||
source: ERC20BridgeSource;
|
||||
// Data associated with this this Fill object. Used to reconstruct orders
|
||||
// from paths.
|
||||
fillData?: TFillData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents continguous fills on a path that have been merged together.
|
||||
*/
|
||||
export interface CollapsedFill<TFillData extends FillData = FillData> {
|
||||
export interface CollapsedFill<TFillData extends FillData = FillData> extends SourceInfo<TFillData> {
|
||||
// Unique ID of the original source path this fill belongs to.
|
||||
// This is generated when the path is generated and is useful to distinguish
|
||||
// paths that have the same `source` IDs but are distinct (e.g., Curves).
|
||||
sourcePathId: string;
|
||||
/**
|
||||
* The source DEX.
|
||||
*/
|
||||
source: ERC20BridgeSource;
|
||||
/**
|
||||
* Total input amount (sum of `subFill`s)
|
||||
*/
|
||||
@ -182,8 +187,6 @@ export interface CollapsedFill<TFillData extends FillData = FillData> {
|
||||
input: BigNumber;
|
||||
output: BigNumber;
|
||||
}>;
|
||||
|
||||
fillData?: TFillData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,17 +276,19 @@ export interface GetMarketOrdersOpts {
|
||||
* A composable operation the be run in `DexOrderSampler.executeAsync()`.
|
||||
*/
|
||||
export interface BatchedOperation<TResult> {
|
||||
encodeCall(contract: ERC20BridgeSamplerContract): string;
|
||||
handleCallResultsAsync(contract: ERC20BridgeSamplerContract, callResults: string): Promise<TResult>;
|
||||
encodeCall(): string;
|
||||
handleCallResults(callResults: string): TResult;
|
||||
}
|
||||
|
||||
export interface SourceQuoteOperation<TFillData extends FillData = FillData>
|
||||
extends BatchedOperation<Array<Quote<TFillData>>> {
|
||||
source: ERC20BridgeSource;
|
||||
extends BatchedOperation<BigNumber[]>,
|
||||
SourceInfo<TFillData> {
|
||||
readonly source: ERC20BridgeSource;
|
||||
}
|
||||
|
||||
export interface OptimizedOrdersAndQuoteReport {
|
||||
export interface OptimizerResult {
|
||||
optimizedOrders: OptimizedMarketOrder[];
|
||||
isTwoHop: boolean;
|
||||
quoteReport: QuoteReport;
|
||||
}
|
||||
|
||||
@ -305,4 +310,9 @@ export interface MarketSideLiquidity {
|
||||
ethToOutputRate: BigNumber;
|
||||
ethToInputRate: BigNumber;
|
||||
rfqtIndicativeQuotes: RFQTIndicativeQuote[];
|
||||
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
|
||||
}
|
||||
|
||||
export interface TokenAdjacencyGraph {
|
||||
[token: string]: string[];
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import { orderHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ERC20BridgeSource, SignedOrder } from '..';
|
||||
import { MarketOperation } from '../types';
|
||||
|
||||
import { CollapsedFill, DexSample, NativeCollapsedFill } from './market_operation_utils/types';
|
||||
import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
} from './market_operation_utils/types';
|
||||
import { QuoteRequestor } from './quote_requestor';
|
||||
|
||||
export interface BridgeReportSource {
|
||||
@ -14,6 +20,13 @@ export interface BridgeReportSource {
|
||||
takerAmount: BigNumber;
|
||||
}
|
||||
|
||||
export interface MultiHopReportSource {
|
||||
liquiditySource: ERC20BridgeSource.MultiHop;
|
||||
makerAmount: BigNumber;
|
||||
takerAmount: BigNumber;
|
||||
hopSources: ERC20BridgeSource[];
|
||||
}
|
||||
|
||||
interface NativeReportSourceBase {
|
||||
liquiditySource: ERC20BridgeSource.Native;
|
||||
makerAmount: BigNumber;
|
||||
@ -29,7 +42,11 @@ export interface NativeRFQTReportSource extends NativeReportSourceBase {
|
||||
isRfqt: true;
|
||||
makerUri: string;
|
||||
}
|
||||
export type QuoteReportSource = BridgeReportSource | NativeOrderbookReportSource | NativeRFQTReportSource;
|
||||
export type QuoteReportSource =
|
||||
| BridgeReportSource
|
||||
| NativeOrderbookReportSource
|
||||
| NativeRFQTReportSource
|
||||
| MultiHopReportSource;
|
||||
|
||||
export interface QuoteReport {
|
||||
sourcesConsidered: QuoteReportSource[];
|
||||
@ -47,115 +64,152 @@ const nativeOrderFromCollapsedFill = (cf: CollapsedFill): SignedOrder | undefine
|
||||
}
|
||||
};
|
||||
|
||||
export class QuoteReportGenerator {
|
||||
private readonly _dexQuotes: DexSample[];
|
||||
private readonly _nativeOrders: SignedOrder[];
|
||||
private readonly _orderHashesToFillableAmounts: { [orderHash: string]: BigNumber };
|
||||
private readonly _marketOperation: MarketOperation;
|
||||
private readonly _collapsedFills: CollapsedFill[];
|
||||
private readonly _quoteRequestor?: QuoteRequestor;
|
||||
|
||||
constructor(
|
||||
marketOperation: MarketOperation,
|
||||
dexQuotes: DexSample[],
|
||||
nativeOrders: SignedOrder[],
|
||||
orderFillableAmounts: BigNumber[],
|
||||
collapsedFills: CollapsedFill[],
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
) {
|
||||
this._dexQuotes = dexQuotes;
|
||||
this._nativeOrders = nativeOrders;
|
||||
this._marketOperation = marketOperation;
|
||||
this._quoteRequestor = quoteRequestor;
|
||||
this._collapsedFills = collapsedFills;
|
||||
|
||||
// convert order fillable amount array to easy to look up hash
|
||||
if (orderFillableAmounts.length !== nativeOrders.length) {
|
||||
// length mismatch, abort
|
||||
this._orderHashesToFillableAmounts = {};
|
||||
return;
|
||||
}
|
||||
const orderHashesToFillableAmounts: { [orderHash: string]: BigNumber } = {};
|
||||
nativeOrders.forEach((nativeOrder, idx) => {
|
||||
orderHashesToFillableAmounts[orderHashUtils.getOrderHash(nativeOrder)] = orderFillableAmounts[idx];
|
||||
});
|
||||
this._orderHashesToFillableAmounts = orderHashesToFillableAmounts;
|
||||
/**
|
||||
* Generates a report of sources considered while computing the optimized
|
||||
* swap quote, and the sources ultimately included in the computed quote.
|
||||
*/
|
||||
export function generateQuoteReport(
|
||||
marketOperation: MarketOperation,
|
||||
dexQuotes: DexSample[],
|
||||
multiHopQuotes: Array<DexSample<MultiHopFillData>>,
|
||||
nativeOrders: SignedOrder[],
|
||||
orderFillableAmounts: BigNumber[],
|
||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): QuoteReport {
|
||||
// convert order fillable amount array to easy to look up hash
|
||||
if (orderFillableAmounts.length !== nativeOrders.length) {
|
||||
// length mismatch, abort
|
||||
throw new Error('orderFillableAmounts must be the same length as nativeOrders');
|
||||
}
|
||||
const orderHashesToFillableAmounts: { [orderHash: string]: BigNumber } = {};
|
||||
nativeOrders.forEach((nativeOrder, idx) => {
|
||||
orderHashesToFillableAmounts[orderHashUtils.getOrderHash(nativeOrder)] = orderFillableAmounts[idx];
|
||||
});
|
||||
|
||||
public generateReport(): QuoteReport {
|
||||
const dexReportSourcesConsidered = this._dexQuotes.map(dq => this._dexSampleToReportSource(dq));
|
||||
const nativeOrderSourcesConsidered = this._nativeOrders.map(no => this._nativeOrderToReportSource(no));
|
||||
const dexReportSourcesConsidered = dexQuotes.map(quote => _dexSampleToReportSource(quote, marketOperation));
|
||||
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
|
||||
_nativeOrderToReportSource(
|
||||
order,
|
||||
orderHashesToFillableAmounts[orderHashUtils.getOrderHash(order)],
|
||||
quoteRequestor,
|
||||
),
|
||||
);
|
||||
const multiHopSourcesConsidered = multiHopQuotes.map(quote =>
|
||||
_multiHopSampleToReportSource(quote, marketOperation),
|
||||
);
|
||||
const sourcesConsidered = [
|
||||
...dexReportSourcesConsidered,
|
||||
...nativeOrderSourcesConsidered,
|
||||
...multiHopSourcesConsidered,
|
||||
];
|
||||
|
||||
const sourcesConsidered = [...dexReportSourcesConsidered, ...nativeOrderSourcesConsidered];
|
||||
const sourcesDelivered = this._collapsedFills.map(collapsedFill => {
|
||||
let sourcesDelivered;
|
||||
if (Array.isArray(liquidityDelivered)) {
|
||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
||||
const foundNativeOrder = nativeOrderFromCollapsedFill(collapsedFill);
|
||||
if (foundNativeOrder) {
|
||||
return this._nativeOrderToReportSource(foundNativeOrder);
|
||||
return _nativeOrderToReportSource(
|
||||
foundNativeOrder,
|
||||
orderHashesToFillableAmounts[orderHashUtils.getOrderHash(foundNativeOrder)],
|
||||
quoteRequestor,
|
||||
);
|
||||
} else {
|
||||
return this._dexSampleToReportSource(collapsedFill);
|
||||
return _dexSampleToReportSource(collapsedFill, marketOperation);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourcesDelivered = [_multiHopSampleToReportSource(liquidityDelivered, marketOperation)];
|
||||
}
|
||||
return {
|
||||
sourcesConsidered,
|
||||
sourcesDelivered,
|
||||
};
|
||||
}
|
||||
|
||||
function _dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeReportSource {
|
||||
const liquiditySource = ds.source;
|
||||
|
||||
if (liquiditySource === ERC20BridgeSource.Native) {
|
||||
throw new Error(`Unexpected liquidity source Native`);
|
||||
}
|
||||
|
||||
// input and output map to different values
|
||||
// based on the market operation
|
||||
if (marketOperation === MarketOperation.Buy) {
|
||||
return {
|
||||
sourcesConsidered,
|
||||
sourcesDelivered,
|
||||
makerAmount: ds.input,
|
||||
takerAmount: ds.output,
|
||||
liquiditySource,
|
||||
};
|
||||
}
|
||||
|
||||
private _dexSampleToReportSource(ds: DexSample): BridgeReportSource {
|
||||
const liquiditySource = ds.source;
|
||||
|
||||
if (liquiditySource === ERC20BridgeSource.Native) {
|
||||
throw new Error(`Unexpected liquidity source Native`);
|
||||
}
|
||||
|
||||
// input and output map to different values
|
||||
// based on the market operation
|
||||
if (this._marketOperation === MarketOperation.Buy) {
|
||||
return {
|
||||
makerAmount: ds.input,
|
||||
takerAmount: ds.output,
|
||||
liquiditySource,
|
||||
};
|
||||
} else if (this._marketOperation === MarketOperation.Sell) {
|
||||
return {
|
||||
makerAmount: ds.output,
|
||||
takerAmount: ds.input,
|
||||
liquiditySource,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${this._marketOperation}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _nativeOrderToReportSource(nativeOrder: SignedOrder): NativeRFQTReportSource | NativeOrderbookReportSource {
|
||||
const orderHash = orderHashUtils.getOrderHash(nativeOrder);
|
||||
|
||||
const nativeOrderBase: NativeReportSourceBase = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: nativeOrder.makerAssetAmount,
|
||||
takerAmount: nativeOrder.takerAssetAmount,
|
||||
fillableTakerAmount: this._orderHashesToFillableAmounts[orderHash],
|
||||
nativeOrder,
|
||||
orderHash,
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return {
|
||||
makerAmount: ds.output,
|
||||
takerAmount: ds.input,
|
||||
liquiditySource,
|
||||
};
|
||||
|
||||
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||
const foundRfqtMakerUri = this._quoteRequestor && this._quoteRequestor.getMakerUriForOrderHash(orderHash);
|
||||
if (foundRfqtMakerUri) {
|
||||
const rfqtSource: NativeRFQTReportSource = {
|
||||
...nativeOrderBase,
|
||||
isRfqt: true,
|
||||
makerUri: foundRfqtMakerUri,
|
||||
};
|
||||
return rfqtSource;
|
||||
} else {
|
||||
// if it's not an rfqt order, treat as normal
|
||||
const regularNativeOrder: NativeOrderbookReportSource = {
|
||||
...nativeOrderBase,
|
||||
isRfqt: false,
|
||||
};
|
||||
return regularNativeOrder;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
}
|
||||
}
|
||||
|
||||
function _multiHopSampleToReportSource(
|
||||
ds: DexSample<MultiHopFillData>,
|
||||
marketOperation: MarketOperation,
|
||||
): MultiHopReportSource {
|
||||
const { firstHopSource: firstHop, secondHopSource: secondHop } = ds.fillData!;
|
||||
// input and output map to different values
|
||||
// based on the market operation
|
||||
if (marketOperation === MarketOperation.Buy) {
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.MultiHop,
|
||||
makerAmount: ds.input,
|
||||
takerAmount: ds.output,
|
||||
hopSources: [firstHop.source, secondHop.source],
|
||||
};
|
||||
} else if (marketOperation === MarketOperation.Sell) {
|
||||
return {
|
||||
liquiditySource: ERC20BridgeSource.MultiHop,
|
||||
makerAmount: ds.output,
|
||||
takerAmount: ds.input,
|
||||
hopSources: [firstHop.source, secondHop.source],
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unexpected marketOperation ${marketOperation}`);
|
||||
}
|
||||
}
|
||||
|
||||
function _nativeOrderToReportSource(
|
||||
nativeOrder: SignedOrder,
|
||||
fillableAmount: BigNumber,
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): NativeRFQTReportSource | NativeOrderbookReportSource {
|
||||
const orderHash = orderHashUtils.getOrderHash(nativeOrder);
|
||||
|
||||
const nativeOrderBase: NativeReportSourceBase = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: nativeOrder.makerAssetAmount,
|
||||
takerAmount: nativeOrder.takerAssetAmount,
|
||||
fillableTakerAmount: fillableAmount,
|
||||
nativeOrder,
|
||||
orderHash,
|
||||
};
|
||||
|
||||
// if we find this is an rfqt order, label it as such and associate makerUri
|
||||
const foundRfqtMakerUri = quoteRequestor && quoteRequestor.getMakerUriForOrderHash(orderHash);
|
||||
if (foundRfqtMakerUri) {
|
||||
const rfqtSource: NativeRFQTReportSource = {
|
||||
...nativeOrderBase,
|
||||
isRfqt: true,
|
||||
makerUri: foundRfqtMakerUri,
|
||||
};
|
||||
return rfqtSource;
|
||||
} else {
|
||||
// if it's not an rfqt order, treat as normal
|
||||
const regularNativeOrder: NativeOrderbookReportSource = {
|
||||
...nativeOrderBase,
|
||||
isRfqt: false,
|
||||
};
|
||||
return regularNativeOrder;
|
||||
}
|
||||
}
|
||||
|
@ -355,10 +355,8 @@ export class QuoteRequestor {
|
||||
switch (quoteType) {
|
||||
case 'firm':
|
||||
return 'quote';
|
||||
break;
|
||||
case 'indicative':
|
||||
return 'price';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected quote type ${quoteType}`);
|
||||
}
|
||||
|
@ -100,10 +100,11 @@ export function simulateBestCaseFill(quoteInfo: QuoteFillInfo): QuoteFillResult
|
||||
...DEFAULT_SIMULATED_FILL_QUOTE_INFO_OPTS,
|
||||
...quoteInfo.opts,
|
||||
};
|
||||
const protocolFeePerFillOrder = quoteInfo.gasPrice.times(opts.protocolFeeMultiplier);
|
||||
const result = fillQuoteOrders(
|
||||
createBestCaseFillOrderCalls(quoteInfo),
|
||||
quoteInfo.fillAmount,
|
||||
quoteInfo.gasPrice.times(opts.protocolFeeMultiplier),
|
||||
protocolFeePerFillOrder,
|
||||
opts.gasSchedule,
|
||||
);
|
||||
return fromIntermediateQuoteFillResult(result, quoteInfo);
|
||||
|
@ -3,6 +3,7 @@ import { AssetProxyId, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
CalculateSwapQuoteOpts,
|
||||
MarketBuySwapQuote,
|
||||
@ -16,8 +17,14 @@ import {
|
||||
|
||||
import { MarketOperationUtils } from './market_operation_utils';
|
||||
import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders';
|
||||
import { FeeSchedule, FillData, GetMarketOrdersOpts, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||
import { isSupportedAssetDataInOrders } from './utils';
|
||||
import {
|
||||
ERC20BridgeSource,
|
||||
FeeSchedule,
|
||||
FillData,
|
||||
GetMarketOrdersOpts,
|
||||
OptimizedMarketOrder,
|
||||
} from './market_operation_utils/types';
|
||||
import { getTokenFromAssetData, isSupportedAssetDataInOrders } from './utils';
|
||||
|
||||
import { QuoteReport } from './quote_report_generator';
|
||||
import { QuoteFillResult, simulateBestCaseFill, simulateWorstCaseFill } from './quote_simulation';
|
||||
@ -121,8 +128,9 @@ export class SwapQuoteCalculator {
|
||||
}
|
||||
// since prunedOrders do not have fillState, we will add a buffer of fillable orders to consider that some native are orders are partially filled
|
||||
|
||||
let optimizedOrders: OptimizedMarketOrder[] | undefined;
|
||||
let optimizedOrders: OptimizedMarketOrder[];
|
||||
let quoteReport: QuoteReport | undefined;
|
||||
let isTwoHop = false;
|
||||
|
||||
{
|
||||
// Scale fees by gas price.
|
||||
@ -149,6 +157,7 @@ export class SwapQuoteCalculator {
|
||||
);
|
||||
optimizedOrders = buyResult.optimizedOrders;
|
||||
quoteReport = buyResult.quoteReport;
|
||||
isTwoHop = buyResult.isTwoHop;
|
||||
} else {
|
||||
const sellResult = await this._marketOperationUtils.getMarketSellOrdersAsync(
|
||||
prunedOrders,
|
||||
@ -157,22 +166,34 @@ export class SwapQuoteCalculator {
|
||||
);
|
||||
optimizedOrders = sellResult.optimizedOrders;
|
||||
quoteReport = sellResult.quoteReport;
|
||||
isTwoHop = sellResult.isTwoHop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assetData information for the result
|
||||
const { makerAssetData, takerAssetData } = prunedOrders[0];
|
||||
return createSwapQuote(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
optimizedOrders,
|
||||
operation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
quoteReport,
|
||||
);
|
||||
return isTwoHop
|
||||
? createTwoHopSwapQuote(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
optimizedOrders,
|
||||
operation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
quoteReport,
|
||||
)
|
||||
: createSwapQuote(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
optimizedOrders,
|
||||
operation,
|
||||
assetFillAmount,
|
||||
gasPrice,
|
||||
opts.gasSchedule,
|
||||
quoteReport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +232,74 @@ function createSwapQuote(
|
||||
sourceBreakdown: getSwapQuoteOrdersBreakdown(bestCaseFillResult.fillAmountBySource),
|
||||
orders: optimizedOrders,
|
||||
quoteReport,
|
||||
isTwoHop: false,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
return {
|
||||
...quoteBase,
|
||||
type: MarketOperation.Buy,
|
||||
makerAssetFillAmount: assetFillAmount,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...quoteBase,
|
||||
type: MarketOperation.Sell,
|
||||
takerAssetFillAmount: assetFillAmount,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function createTwoHopSwapQuote(
|
||||
makerAssetData: string,
|
||||
takerAssetData: string,
|
||||
optimizedOrders: OptimizedMarketOrder[],
|
||||
operation: MarketOperation,
|
||||
assetFillAmount: BigNumber,
|
||||
gasPrice: BigNumber,
|
||||
gasSchedule: FeeSchedule,
|
||||
quoteReport?: QuoteReport,
|
||||
): SwapQuote {
|
||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
||||
const [firstHopFill] = firstHopOrder.fills;
|
||||
const [secondHopFill] = secondHopOrder.fills;
|
||||
const gas = new BigNumber(
|
||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
||||
firstHopSource: _.pick(firstHopFill, 'source', 'fillData'),
|
||||
secondHopSource: _.pick(secondHopFill, 'source', 'fillData'),
|
||||
}),
|
||||
).toNumber();
|
||||
|
||||
const quoteBase = {
|
||||
takerAssetData,
|
||||
makerAssetData,
|
||||
gasPrice,
|
||||
bestCaseQuoteInfo: {
|
||||
makerAssetAmount: operation === MarketOperation.Sell ? secondHopFill.output : secondHopFill.input,
|
||||
takerAssetAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
|
||||
totalTakerAssetAmount: operation === MarketOperation.Sell ? firstHopFill.input : firstHopFill.output,
|
||||
feeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
},
|
||||
worstCaseQuoteInfo: {
|
||||
makerAssetAmount: secondHopOrder.makerAssetAmount,
|
||||
takerAssetAmount: firstHopOrder.takerAssetAmount,
|
||||
totalTakerAssetAmount: firstHopOrder.takerAssetAmount,
|
||||
feeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||
gas,
|
||||
},
|
||||
sourceBreakdown: {
|
||||
[ERC20BridgeSource.MultiHop]: {
|
||||
proportion: new BigNumber(1),
|
||||
intermediateToken: getTokenFromAssetData(secondHopOrder.takerAssetData),
|
||||
hops: [firstHopFill.source, secondHopFill.source],
|
||||
},
|
||||
},
|
||||
orders: optimizedOrders,
|
||||
quoteReport,
|
||||
isTwoHop: true,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
@ -218,14 +307,12 @@ function createSwapQuote(
|
||||
...quoteBase,
|
||||
type: MarketOperation.Buy,
|
||||
makerAssetFillAmount: assetFillAmount,
|
||||
quoteReport,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...quoteBase,
|
||||
type: MarketOperation.Sell,
|
||||
takerAssetFillAmount: assetFillAmount,
|
||||
quoteReport,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -234,7 +321,7 @@ function getSwapQuoteOrdersBreakdown(fillAmountBySource: { [source: string]: Big
|
||||
const totalFillAmount = BigNumber.sum(...Object.values(fillAmountBySource));
|
||||
const breakdown: SwapQuoteOrdersBreakdown = {};
|
||||
Object.entries(fillAmountBySource).forEach(([source, fillAmount]) => {
|
||||
breakdown[source] = fillAmount.div(totalFillAmount);
|
||||
breakdown[source as keyof SwapQuoteOrdersBreakdown] = fillAmount.div(totalFillAmount);
|
||||
});
|
||||
return breakdown;
|
||||
}
|
||||
|
@ -113,3 +113,12 @@ export function isERC20EquivalentAssetData(assetData: AssetData): assetData is E
|
||||
export function difference<T>(a: T[], b: T[]): T[] {
|
||||
return a.filter(x => b.indexOf(x) === -1);
|
||||
}
|
||||
|
||||
export function getTokenFromAssetData(assetData: string): string {
|
||||
const data = assetDataUtils.decodeAssetDataOrThrow(assetData);
|
||||
if (data.assetProxyId !== AssetProxyId.ERC20 && data.assetProxyId !== AssetProxyId.ERC20Bridge) {
|
||||
throw new Error(`Unsupported exchange proxy quote asset type: ${data.assetProxyId}`);
|
||||
}
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
return (data as ERC20AssetData).tokenAddress;
|
||||
}
|
||||
|
@ -6,11 +6,13 @@
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
||||
import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.json';
|
||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
|
||||
import * as DummyLiquidityProviderRegistry from '../test/generated-artifacts/DummyLiquidityProviderRegistry.json';
|
||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json';
|
||||
import * as IBalancer from '../test/generated-artifacts/IBalancer.json';
|
||||
import * as ICurve from '../test/generated-artifacts/ICurve.json';
|
||||
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
|
||||
import * as IKyberHintHandler from '../test/generated-artifacts/IKyberHintHandler.json';
|
||||
@ -33,15 +35,27 @@ import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSamp
|
||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
||||
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
||||
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
||||
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
|
||||
export const artifacts = {
|
||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||
BalancerSampler: BalancerSampler as ContractArtifact,
|
||||
CurveSampler: CurveSampler as ContractArtifact,
|
||||
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
|
||||
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
Eth2DaiSampler: Eth2DaiSampler as ContractArtifact,
|
||||
IMooniswap: IMooniswap as ContractArtifact,
|
||||
KyberSampler: KyberSampler as ContractArtifact,
|
||||
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
|
||||
MStableSampler: MStableSampler as ContractArtifact,
|
||||
MooniswapSampler: MooniswapSampler as ContractArtifact,
|
||||
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
|
||||
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
|
||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||
IBalancer: IBalancer as ContractArtifact,
|
||||
ICurve: ICurve as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberHintHandler: IKyberHintHandler as ContractArtifact,
|
||||
@ -51,19 +65,11 @@ export const artifacts = {
|
||||
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
|
||||
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
|
||||
IMStable: IMStable as ContractArtifact,
|
||||
IMooniswap: IMooniswap as ContractArtifact,
|
||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||
KyberSampler: KyberSampler as ContractArtifact,
|
||||
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
|
||||
MStableSampler: MStableSampler as ContractArtifact,
|
||||
MooniswapSampler: MooniswapSampler as ContractArtifact,
|
||||
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
|
||||
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
|
||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
|
||||
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
|
||||
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
|
||||
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
|
||||
};
|
||||
|
@ -37,6 +37,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const INTERMEDIATE_TOKEN = randomAddress();
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
@ -262,6 +263,33 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
await testContract.enableFailTrigger().awaitTransactionSuccessAsync({ value: 1 });
|
||||
}
|
||||
|
||||
function expectQuotesWithinRange(
|
||||
quotes: BigNumber[],
|
||||
expectedQuotes: BigNumber[],
|
||||
maxSlippage: BigNumber | number,
|
||||
): void {
|
||||
quotes.forEach((_q, i) => {
|
||||
// If we're within 1 base unit of a low decimal token
|
||||
// then that's as good as we're going to get (and slippage is "high")
|
||||
if (
|
||||
expectedQuotes[i].isZero() ||
|
||||
BigNumber.max(expectedQuotes[i], quotes[i])
|
||||
.minus(BigNumber.min(expectedQuotes[i], quotes[i]))
|
||||
.eq(1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const slippage = quotes[i]
|
||||
.dividedBy(expectedQuotes[i])
|
||||
.minus(1)
|
||||
.decimalPlaces(4);
|
||||
expect(slippage, `quote[${i}]: ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.gte(0);
|
||||
expect(slippage, `quote[${i}] ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.lte(
|
||||
new BigNumber(maxSlippage),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
describe('getOrderFillableTakerAssetAmounts()', () => {
|
||||
it('returns the expected amount for each order', async () => {
|
||||
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
|
||||
@ -385,32 +413,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const quotes = await testContract.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync();
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
const expectQuotesWithinRange = (
|
||||
quotes: BigNumber[],
|
||||
expectedQuotes: BigNumber[],
|
||||
maxSlippage: BigNumber | number,
|
||||
) => {
|
||||
quotes.forEach((_q, i) => {
|
||||
// If we're within 1 base unit of a low decimal token
|
||||
// then that's as good as we're going to get (and slippage is "high")
|
||||
if (
|
||||
expectedQuotes[i].isZero() ||
|
||||
BigNumber.max(expectedQuotes[i], quotes[i])
|
||||
.minus(BigNumber.min(expectedQuotes[i], quotes[i]))
|
||||
.eq(1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const slippage = quotes[i]
|
||||
.dividedBy(expectedQuotes[i])
|
||||
.minus(1)
|
||||
.decimalPlaces(4);
|
||||
expect(slippage, `quote[${i}]: ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.gte(0);
|
||||
expect(slippage, `quote[${i}] ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.lte(
|
||||
new BigNumber(maxSlippage),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
@ -803,7 +805,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLiquidityProviderFromRegistry', () => {
|
||||
describe('liquidity provider', () => {
|
||||
const xAsset = randomAddress();
|
||||
const yAsset = randomAddress();
|
||||
const sampleAmounts = getSampleAmounts(yAsset);
|
||||
@ -829,42 +831,28 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
.awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('should be able to get the liquidity provider', async () => {
|
||||
const xyLiquidityProvider = await testContract
|
||||
.getLiquidityProviderFromRegistry(registryContract.address, xAsset, yAsset)
|
||||
.callAsync();
|
||||
const yxLiquidityProvider = await testContract
|
||||
.getLiquidityProviderFromRegistry(registryContract.address, yAsset, xAsset)
|
||||
.callAsync();
|
||||
const unknownLiquidityProvider = await testContract
|
||||
.getLiquidityProviderFromRegistry(registryContract.address, yAsset, randomAddress())
|
||||
.callAsync();
|
||||
|
||||
expect(xyLiquidityProvider).to.eq(liquidityProvider.address);
|
||||
expect(yxLiquidityProvider).to.eq(liquidityProvider.address);
|
||||
expect(unknownLiquidityProvider).to.eq(constants.NULL_ADDRESS);
|
||||
});
|
||||
|
||||
it('should be able to query sells from the liquidity provider', async () => {
|
||||
const result = await testContract
|
||||
const [quotes, providerAddress] = await testContract
|
||||
.sampleSellsFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
|
||||
.callAsync();
|
||||
result.forEach((value, idx) => {
|
||||
quotes.forEach((value, idx) => {
|
||||
expect(value).is.bignumber.eql(sampleAmounts[idx].minus(1));
|
||||
});
|
||||
expect(providerAddress).to.equal(liquidityProvider.address);
|
||||
});
|
||||
|
||||
it('should be able to query buys from the liquidity provider', async () => {
|
||||
const result = await testContract
|
||||
const [quotes, providerAddress] = await testContract
|
||||
.sampleBuysFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
|
||||
.callAsync();
|
||||
result.forEach((value, idx) => {
|
||||
quotes.forEach((value, idx) => {
|
||||
expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1));
|
||||
});
|
||||
expect(providerAddress).to.equal(liquidityProvider.address);
|
||||
});
|
||||
|
||||
it('should just return zeros if the liquidity provider cannot be found', async () => {
|
||||
const result = await testContract
|
||||
const [quotes, providerAddress] = await testContract
|
||||
.sampleBuysFromLiquidityProviderRegistry(
|
||||
registryContract.address,
|
||||
yAsset,
|
||||
@ -872,18 +860,20 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
sampleAmounts,
|
||||
)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
quotes.forEach(value => {
|
||||
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
|
||||
});
|
||||
expect(providerAddress).to.equal(constants.NULL_ADDRESS);
|
||||
});
|
||||
|
||||
it('should just return zeros if the registry does not exist', async () => {
|
||||
const result = await testContract
|
||||
const [quotes, providerAddress] = await testContract
|
||||
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
quotes.forEach(value => {
|
||||
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
|
||||
});
|
||||
expect(providerAddress).to.equal(constants.NULL_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1033,4 +1023,116 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('TwoHopSampler', () => {
|
||||
before(async () => {
|
||||
await testContract
|
||||
.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN, INTERMEDIATE_TOKEN])
|
||||
.awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('sampleTwoHopSell', async () => {
|
||||
// tslint:disable-next-line no-unnecessary-type-assertion
|
||||
const sellAmount = _.last(getSampleAmounts(TAKER_TOKEN))!;
|
||||
const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN];
|
||||
const uniswapV2FirstHop = testContract
|
||||
.sampleSellsFromUniswapV2(uniswapV2FirstHopPath, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN];
|
||||
const uniswapV2SecondHop = testContract
|
||||
.sampleSellsFromUniswapV2(uniswapV2SecondHopPath, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const eth2DaiFirstHop = testContract
|
||||
.sampleSellsFromEth2Dai(TAKER_TOKEN, INTERMEDIATE_TOKEN, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
const eth2DaiSecondHop = testContract
|
||||
.sampleSellsFromEth2Dai(INTERMEDIATE_TOKEN, MAKER_TOKEN, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const firstHopQuotes = [
|
||||
getDeterministicSellQuote(ETH2DAI_SALT, TAKER_TOKEN, INTERMEDIATE_TOKEN, sellAmount),
|
||||
getDeterministicUniswapV2SellQuote(uniswapV2FirstHopPath, sellAmount),
|
||||
];
|
||||
const expectedIntermediateAssetAmount = BigNumber.max(...firstHopQuotes);
|
||||
const secondHopQuotes = [
|
||||
getDeterministicSellQuote(
|
||||
ETH2DAI_SALT,
|
||||
INTERMEDIATE_TOKEN,
|
||||
MAKER_TOKEN,
|
||||
expectedIntermediateAssetAmount,
|
||||
),
|
||||
getDeterministicUniswapV2SellQuote(uniswapV2SecondHopPath, expectedIntermediateAssetAmount),
|
||||
];
|
||||
const expectedBuyAmount = BigNumber.max(...secondHopQuotes);
|
||||
|
||||
const [firstHop, secondHop, buyAmount] = await testContract
|
||||
.sampleTwoHopSell(
|
||||
[eth2DaiFirstHop, uniswapV2FirstHop],
|
||||
[eth2DaiSecondHop, uniswapV2SecondHop],
|
||||
sellAmount,
|
||||
)
|
||||
.callAsync();
|
||||
expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal(
|
||||
firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)),
|
||||
);
|
||||
expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal(
|
||||
secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedBuyAmount)),
|
||||
);
|
||||
expect(buyAmount, 'Two hop buy amount').to.bignumber.equal(expectedBuyAmount);
|
||||
});
|
||||
it('sampleTwoHopBuy', async () => {
|
||||
// tslint:disable-next-line no-unnecessary-type-assertion
|
||||
const buyAmount = _.last(getSampleAmounts(MAKER_TOKEN))!;
|
||||
const uniswapV2FirstHopPath = [TAKER_TOKEN, INTERMEDIATE_TOKEN];
|
||||
const uniswapV2FirstHop = testContract
|
||||
.sampleBuysFromUniswapV2(uniswapV2FirstHopPath, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const uniswapV2SecondHopPath = [INTERMEDIATE_TOKEN, randomAddress(), MAKER_TOKEN];
|
||||
const uniswapV2SecondHop = testContract
|
||||
.sampleBuysFromUniswapV2(uniswapV2SecondHopPath, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const eth2DaiFirstHop = testContract
|
||||
.sampleBuysFromEth2Dai(TAKER_TOKEN, INTERMEDIATE_TOKEN, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
const eth2DaiSecondHop = testContract
|
||||
.sampleBuysFromEth2Dai(INTERMEDIATE_TOKEN, MAKER_TOKEN, [constants.ZERO_AMOUNT])
|
||||
.getABIEncodedTransactionData();
|
||||
|
||||
const secondHopQuotes = [
|
||||
getDeterministicBuyQuote(ETH2DAI_SALT, INTERMEDIATE_TOKEN, MAKER_TOKEN, buyAmount),
|
||||
getDeterministicUniswapV2BuyQuote(uniswapV2SecondHopPath, buyAmount),
|
||||
];
|
||||
const expectedIntermediateAssetAmount = BigNumber.min(...secondHopQuotes);
|
||||
|
||||
const firstHopQuotes = [
|
||||
getDeterministicBuyQuote(
|
||||
ETH2DAI_SALT,
|
||||
TAKER_TOKEN,
|
||||
INTERMEDIATE_TOKEN,
|
||||
expectedIntermediateAssetAmount,
|
||||
),
|
||||
getDeterministicUniswapV2BuyQuote(uniswapV2FirstHopPath, expectedIntermediateAssetAmount),
|
||||
];
|
||||
const expectedSellAmount = BigNumber.min(...firstHopQuotes);
|
||||
|
||||
const [firstHop, secondHop, sellAmount] = await testContract
|
||||
.sampleTwoHopBuy(
|
||||
[eth2DaiFirstHop, uniswapV2FirstHop],
|
||||
[eth2DaiSecondHop, uniswapV2SecondHop],
|
||||
buyAmount,
|
||||
)
|
||||
.callAsync();
|
||||
expect(firstHop.sourceIndex, 'First hop source index').to.bignumber.equal(
|
||||
firstHopQuotes.findIndex(quote => quote.isEqualTo(expectedSellAmount)),
|
||||
);
|
||||
expect(secondHop.sourceIndex, 'Second hop source index').to.bignumber.equal(
|
||||
secondHopQuotes.findIndex(quote => quote.isEqualTo(expectedIntermediateAssetAmount)),
|
||||
);
|
||||
expect(sellAmount, 'Two hop sell amount').to.bignumber.equal(expectedSellAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
computeBalancerSellQuote,
|
||||
} from '../src/utils/market_operation_utils/balancer_utils';
|
||||
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
|
||||
import { ERC20BridgeSource, FillData } from '../src/utils/market_operation_utils/types';
|
||||
import { ERC20BridgeSource } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
import { MockBalancerPoolsCache } from './utils/mock_balancer_pools_cache';
|
||||
import { MockBancorService } from './utils/mock_bancor_service';
|
||||
@ -108,7 +108,7 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(ORDERS, exchangeAddress),
|
||||
dexOrderSampler.getOrderFillableMakerAmounts(ORDERS, exchangeAddress),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
@ -124,7 +124,7 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(ORDERS, exchangeAddress),
|
||||
dexOrderSampler.getOrderFillableTakerAmounts(ORDERS, exchangeAddress),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
|
||||
});
|
||||
@ -144,36 +144,32 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getKyberSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
dexOrderSampler.getKyberSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getLiquidityProviderSellQuotes()', async () => {
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerToken = randomAddress();
|
||||
const registry = randomAddress();
|
||||
const poolAddress = randomAddress();
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
|
||||
expect(registryAddress).to.eq(registry);
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
return [toBaseUnitAmount(1001)];
|
||||
return [[toBaseUnitAmount(1001)], poolAddress];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [result] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
dexOrderSampler.getSellQuotes(
|
||||
[ERC20BridgeSource.LiquidityProvider],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
registry,
|
||||
),
|
||||
);
|
||||
@ -183,7 +179,7 @@ describe('DexSampler tests', () => {
|
||||
source: 'LiquidityProvider',
|
||||
output: toBaseUnitAmount(1001),
|
||||
input: toBaseUnitAmount(1000),
|
||||
fillData: undefined,
|
||||
fillData: { poolAddress },
|
||||
},
|
||||
],
|
||||
]);
|
||||
@ -193,23 +189,23 @@ describe('DexSampler tests', () => {
|
||||
const expectedMakerToken = randomAddress();
|
||||
const expectedTakerToken = randomAddress();
|
||||
const registry = randomAddress();
|
||||
const poolAddress = randomAddress();
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
|
||||
expect(registryAddress).to.eq(registry);
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
return [toBaseUnitAmount(999)];
|
||||
return [[toBaseUnitAmount(999)], poolAddress];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [result] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
dexOrderSampler.getBuyQuotes(
|
||||
[ERC20BridgeSource.LiquidityProvider],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
registry,
|
||||
),
|
||||
);
|
||||
@ -219,7 +215,7 @@ describe('DexSampler tests', () => {
|
||||
source: 'LiquidityProvider',
|
||||
output: toBaseUnitAmount(999),
|
||||
input: toBaseUnitAmount(1000),
|
||||
fillData: undefined,
|
||||
fillData: { poolAddress },
|
||||
},
|
||||
],
|
||||
]);
|
||||
@ -246,13 +242,12 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [result] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
dexOrderSampler.getSellQuotes(
|
||||
[ERC20BridgeSource.MultiBridge],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
randomAddress(),
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
randomAddress(),
|
||||
multiBridge,
|
||||
),
|
||||
@ -263,7 +258,7 @@ describe('DexSampler tests', () => {
|
||||
source: 'MultiBridge',
|
||||
output: toBaseUnitAmount(1001),
|
||||
input: toBaseUnitAmount(1000),
|
||||
fillData: undefined,
|
||||
fillData: { poolAddress: multiBridge },
|
||||
},
|
||||
],
|
||||
]);
|
||||
@ -284,13 +279,9 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getEth2DaiSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
dexOrderSampler.getEth2DaiSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapSellQuotes()', async () => {
|
||||
@ -308,13 +299,9 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapSellQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
dexOrderSampler.getUniswapSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapV2SellQuotes()', async () => {
|
||||
@ -331,12 +318,12 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapV2SellQuotes(
|
||||
dexOrderSampler.getUniswapV2SellQuotes(
|
||||
[expectedMakerToken, expectedTakerToken],
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedMakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getEth2DaiBuyQuotes()', async () => {
|
||||
@ -354,13 +341,9 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getEth2DaiBuyQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
dexOrderSampler.getEth2DaiBuyQuotes(expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedTakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getUniswapBuyQuotes()', async () => {
|
||||
@ -378,13 +361,9 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapBuyQuotes(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
),
|
||||
dexOrderSampler.getUniswapBuyQuotes(expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
|
||||
);
|
||||
expect(fillableAmounts.map(q => q.amount)).to.deep.eq(expectedTakerFillAmounts);
|
||||
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
});
|
||||
|
||||
interface RatesBySource {
|
||||
@ -440,13 +419,12 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
dexOrderSampler.getSellQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
),
|
||||
);
|
||||
const expectedQuotes = sources.map(s =>
|
||||
@ -457,7 +435,7 @@ describe('DexSampler tests', () => {
|
||||
fillData:
|
||||
s === ERC20BridgeSource.UniswapV2
|
||||
? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
|
||||
: ((undefined as any) as FillData),
|
||||
: {},
|
||||
})),
|
||||
);
|
||||
const uniswapV2ETHQuotes = [
|
||||
@ -488,19 +466,14 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
new MockSamplerContract({}),
|
||||
undefined, // sampler overrides
|
||||
undefined, // bancor service
|
||||
undefined,
|
||||
undefined,
|
||||
balancerPoolsCache,
|
||||
);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
[ERC20BridgeSource.Balancer],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
),
|
||||
const quotes = await dexOrderSampler.getBalancerSellQuotesOffChainAsync(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
);
|
||||
const expectedQuotes = pools.map(p =>
|
||||
expectedTakerFillAmounts.map(a => ({
|
||||
@ -535,28 +508,17 @@ describe('DexSampler tests', () => {
|
||||
bancorService,
|
||||
undefined, // balancer cache
|
||||
);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getSellQuotesAsync(
|
||||
[ERC20BridgeSource.Bancor],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
wethAddress,
|
||||
undefined, // balancer pools cache
|
||||
undefined, // liquidity provider registry address
|
||||
undefined, // multibridge address
|
||||
bancorService,
|
||||
),
|
||||
const quotes = await dexOrderSampler.getBancorSellQuotesOffChainAsync(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
);
|
||||
const expectedQuotes = [
|
||||
expectedTakerFillAmounts.map(a => ({
|
||||
source: ERC20BridgeSource.Bancor,
|
||||
input: a,
|
||||
output: a.multipliedBy(rate),
|
||||
fillData: { path: [expectedTakerToken, expectedMakerToken], networkAddress },
|
||||
})),
|
||||
];
|
||||
expect(quotes).to.have.lengthOf(1); // one set per pool
|
||||
const expectedQuotes = expectedTakerFillAmounts.map(a => ({
|
||||
source: ERC20BridgeSource.Bancor,
|
||||
input: a,
|
||||
output: a.multipliedBy(rate),
|
||||
fillData: { path: [expectedTakerToken, expectedMakerToken], networkAddress },
|
||||
}));
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
it('getBuyQuotes()', async () => {
|
||||
@ -596,13 +558,12 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
dexOrderSampler.getBuyQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
),
|
||||
);
|
||||
const expectedQuotes = sources.map(s =>
|
||||
@ -613,7 +574,7 @@ describe('DexSampler tests', () => {
|
||||
fillData:
|
||||
s === ERC20BridgeSource.UniswapV2
|
||||
? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
|
||||
: ((undefined as any) as FillData),
|
||||
: {},
|
||||
})),
|
||||
);
|
||||
const uniswapV2ETHQuotes = [
|
||||
@ -644,19 +605,14 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(
|
||||
new MockSamplerContract({}),
|
||||
undefined, // sampler overrides
|
||||
undefined, // bancor service
|
||||
undefined,
|
||||
undefined,
|
||||
balancerPoolsCache,
|
||||
);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
await DexOrderSampler.ops.getBuyQuotesAsync(
|
||||
[ERC20BridgeSource.Balancer],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
wethAddress,
|
||||
dexOrderSampler.balancerPoolsCache,
|
||||
),
|
||||
const quotes = await dexOrderSampler.getBalancerBuyQuotesOffChainAsync(
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
);
|
||||
const expectedQuotes = pools.map(p =>
|
||||
expectedMakerFillAmounts.map(a => ({
|
||||
@ -689,8 +645,8 @@ describe('DexSampler tests', () => {
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getOrderFillableMakerAmounts(ORDERS, exchangeAddress),
|
||||
DexOrderSampler.ops.getOrderFillableTakerAmounts(ORDERS, exchangeAddress),
|
||||
dexOrderSampler.getOrderFillableMakerAmounts(ORDERS, exchangeAddress),
|
||||
dexOrderSampler.getOrderFillableTakerAmounts(ORDERS, exchangeAddress),
|
||||
);
|
||||
expect(fillableMakerAmounts).to.deep.eq(expectedFillableMakerAmounts);
|
||||
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
|
||||
|
@ -37,6 +37,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
const CHAIN_ID = 1;
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const INTERMEDIATE_TOKEN = randomAddress();
|
||||
const TRANSFORMER_DEPLOYER = randomAddress();
|
||||
const contractAddresses = {
|
||||
...getContractAddressesForChainOrThrow(CHAIN_ID),
|
||||
@ -124,6 +125,18 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
} as any;
|
||||
}
|
||||
|
||||
function getRandomTwoHopQuote(side: MarketOperation): MarketBuySwapQuote | MarketSellSwapQuote {
|
||||
const intermediateTokenAssetData = createAssetData(INTERMEDIATE_TOKEN);
|
||||
return {
|
||||
...getRandomQuote(side),
|
||||
orders: [
|
||||
{ ...getRandomOrder(), makerAssetData: intermediateTokenAssetData },
|
||||
{ ...getRandomOrder(), takerAssetData: intermediateTokenAssetData },
|
||||
],
|
||||
isTwoHop: true,
|
||||
} as any;
|
||||
}
|
||||
|
||||
function getRandomSellQuote(): MarketSellSwapQuote {
|
||||
return getRandomQuote(MarketOperation.Sell) as MarketSellSwapQuote;
|
||||
}
|
||||
@ -298,5 +311,52 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
}),
|
||||
).to.eventually.be.rejectedWith('Affiliate fees denominated in sell token are not yet supported');
|
||||
});
|
||||
it('Uses two `FillQuoteTransformer`s if given two-hop sell quote', async () => {
|
||||
const quote = getRandomTwoHopQuote(MarketOperation.Sell) as MarketSellSwapQuote;
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||
extensionContractOpts: { isTwoHop: true },
|
||||
});
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
|
||||
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
|
||||
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
|
||||
expect(callArgs.minOutputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||
expect(callArgs.transformations).to.be.length(3);
|
||||
expect(
|
||||
callArgs.transformations[0].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.fillQuoteTransformer,
|
||||
);
|
||||
expect(
|
||||
callArgs.transformations[1].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.fillQuoteTransformer,
|
||||
);
|
||||
expect(
|
||||
callArgs.transformations[2].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.payTakerTransformer,
|
||||
);
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
const firstHopFillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[0].data);
|
||||
expect(firstHopFillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Sell);
|
||||
expect(firstHopFillQuoteTransformerData.fillAmount).to.bignumber.eq(firstHopOrder.takerAssetAmount);
|
||||
expect(firstHopFillQuoteTransformerData.orders).to.deep.eq(cleanOrders([firstHopOrder]));
|
||||
expect(firstHopFillQuoteTransformerData.signatures).to.deep.eq([firstHopOrder.signature]);
|
||||
expect(firstHopFillQuoteTransformerData.sellToken).to.eq(TAKER_TOKEN);
|
||||
expect(firstHopFillQuoteTransformerData.buyToken).to.eq(INTERMEDIATE_TOKEN);
|
||||
const secondHopFillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[1].data);
|
||||
expect(secondHopFillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Sell);
|
||||
expect(secondHopFillQuoteTransformerData.fillAmount).to.bignumber.eq(contractConstants.MAX_UINT256);
|
||||
expect(secondHopFillQuoteTransformerData.orders).to.deep.eq(cleanOrders([secondHopOrder]));
|
||||
expect(secondHopFillQuoteTransformerData.signatures).to.deep.eq([secondHopOrder.signature]);
|
||||
expect(secondHopFillQuoteTransformerData.sellToken).to.eq(INTERMEDIATE_TOKEN);
|
||||
expect(secondHopFillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[2].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([
|
||||
TAKER_TOKEN,
|
||||
MAKER_TOKEN,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
INTERMEDIATE_TOKEN,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ import * as TypeMoq from 'typemoq';
|
||||
|
||||
import { MarketOperation, QuoteRequestor, RfqtRequestOpts, SignedOrderWithFillableAmounts } from '../src';
|
||||
import { getRfqtIndicativeQuotesAsync, MarketOperationUtils } from '../src/utils/market_operation_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 { createFillPaths } from '../src/utils/market_operation_utils/fills';
|
||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||
@ -31,15 +32,6 @@ const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
||||
describe('MarketOperationUtils tests', () => {
|
||||
const CHAIN_ID = 1;
|
||||
const contractAddresses = { ...getContractAddressesForChainOrThrow(CHAIN_ID), multiBridge: NULL_ADDRESS };
|
||||
let originalSamplerOperations: any;
|
||||
|
||||
before(() => {
|
||||
originalSamplerOperations = DexOrderSampler.ops;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
DexOrderSampler.ops = originalSamplerOperations;
|
||||
});
|
||||
|
||||
function createOrder(overrides?: Partial<SignedOrder>): SignedOrder {
|
||||
return {
|
||||
@ -153,7 +145,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
liquidityProviderAddress?: string,
|
||||
) => Promise<DexSample[][]>;
|
||||
) => DexSample[][];
|
||||
|
||||
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
return (
|
||||
@ -163,7 +155,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
fillAmounts: BigNumber[],
|
||||
_wethAddress: string,
|
||||
) => {
|
||||
return Promise.resolve(sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s])));
|
||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
|
||||
};
|
||||
}
|
||||
|
||||
@ -181,7 +173,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
_balancerPoolsCache?: any,
|
||||
liquidityProviderAddress?: string,
|
||||
) => {
|
||||
liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress;
|
||||
@ -206,9 +197,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
fillAmounts: BigNumber[],
|
||||
_wethAddress: string,
|
||||
) => {
|
||||
return Promise.resolve(
|
||||
sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r)))),
|
||||
);
|
||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))));
|
||||
};
|
||||
}
|
||||
|
||||
@ -221,12 +210,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
liquidityProviderAddress?: string,
|
||||
) => BigNumber;
|
||||
|
||||
type GetLiquidityProviderFromRegistryOperation = (
|
||||
registryAddress: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
) => string;
|
||||
|
||||
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
|
||||
return (
|
||||
_sources: ERC20BridgeSource[],
|
||||
@ -239,34 +222,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
};
|
||||
}
|
||||
|
||||
function getLiquidityProviderFromRegistry(): GetLiquidityProviderFromRegistryOperation {
|
||||
return (_registryAddress: string, _takerToken: string, _makerToken: string): string => {
|
||||
return NULL_ADDRESS;
|
||||
};
|
||||
}
|
||||
|
||||
function getLiquidityProviderFromRegistryAndReturnCallParameters(
|
||||
liquidityProviderAddress: string = NULL_ADDRESS,
|
||||
): [
|
||||
{ registryAddress?: string; takerToken?: string; makerToken?: string },
|
||||
GetLiquidityProviderFromRegistryOperation
|
||||
] {
|
||||
const callArgs: { registryAddress?: string; takerToken?: string; makerToken?: string } = {
|
||||
registryAddress: undefined,
|
||||
takerToken: undefined,
|
||||
makerToken: undefined,
|
||||
};
|
||||
const fn = (registryAddress: string, takerToken: string, makerToken: string): string => {
|
||||
callArgs.makerToken = makerToken;
|
||||
callArgs.takerToken = takerToken;
|
||||
if (registryAddress !== constants.NULL_ADDRESS) {
|
||||
callArgs.registryAddress = registryAddress;
|
||||
}
|
||||
return liquidityProviderAddress;
|
||||
};
|
||||
return [callArgs, fn];
|
||||
}
|
||||
|
||||
function createDecreasingRates(count: number): BigNumber[] {
|
||||
const rates: BigNumber[] = [];
|
||||
const initialRate = getRandomFloat(1e-3, 1e2);
|
||||
@ -317,6 +272,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
fromTokenIdx: 0,
|
||||
toTokenIdx: 1,
|
||||
},
|
||||
[ERC20BridgeSource.LiquidityProvider]: { poolAddress: randomAddress() },
|
||||
};
|
||||
|
||||
const DEFAULT_OPS = {
|
||||
@ -326,19 +282,44 @@ describe('MarketOperationUtils tests', () => {
|
||||
getOrderFillableMakerAmounts(orders: SignedOrder[]): BigNumber[] {
|
||||
return orders.map(o => o.makerAssetAmount);
|
||||
},
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(1),
|
||||
getLiquidityProviderFromRegistry: getLiquidityProviderFromRegistry(),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||
getMedianSellRate: createGetMedianSellRate(1),
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
_makerToken: string,
|
||||
_takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Balancer,
|
||||
takerFillAmounts,
|
||||
createDecreasingRates(takerFillAmounts.length),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Balancer],
|
||||
),
|
||||
],
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
_makerToken: string,
|
||||
_takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => [
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Balancer,
|
||||
makerFillAmounts,
|
||||
createDecreasingRates(makerFillAmounts.length).map(r => new BigNumber(1).div(r)),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Balancer],
|
||||
),
|
||||
],
|
||||
getBancorSellQuotesOffChainAsync: (_makerToken: string, _takerToken: string, takerFillAmounts: BigNumber[]) =>
|
||||
createSamplesFromRates(
|
||||
ERC20BridgeSource.Bancor,
|
||||
takerFillAmounts,
|
||||
createDecreasingRates(takerFillAmounts.length),
|
||||
DEFAULT_FILL_DATA[ERC20BridgeSource.Bancor],
|
||||
),
|
||||
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||
};
|
||||
|
||||
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
|
||||
DexOrderSampler.ops = {
|
||||
...DEFAULT_OPS,
|
||||
...ops,
|
||||
} as any;
|
||||
}
|
||||
|
||||
const MOCK_SAMPLER = ({
|
||||
async executeAsync(...ops: any[]): Promise<any[]> {
|
||||
return ops;
|
||||
@ -346,8 +327,14 @@ describe('MarketOperationUtils tests', () => {
|
||||
async executeBatchAsync(ops: any[]): Promise<any[]> {
|
||||
return ops;
|
||||
},
|
||||
balancerPoolsCache: new BalancerPoolsCache(),
|
||||
} as any) as DexOrderSampler;
|
||||
|
||||
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
|
||||
Object.assign(MOCK_SAMPLER, DEFAULT_OPS);
|
||||
Object.assign(MOCK_SAMPLER, ops);
|
||||
}
|
||||
|
||||
describe('getRfqtIndicativeQuotesAsync', () => {
|
||||
const partialRfqt: RfqtRequestOpts = {
|
||||
apiKey: 'foo',
|
||||
@ -434,6 +421,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
ERC20BridgeSource.Balancer,
|
||||
ERC20BridgeSource.MStable,
|
||||
ERC20BridgeSource.Mooniswap,
|
||||
ERC20BridgeSource.Bancor,
|
||||
],
|
||||
allowFallback: false,
|
||||
shouldBatchBridgeOrders: false,
|
||||
@ -447,9 +435,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const numSamples = _.random(1, NUM_SAMPLES);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@ -462,9 +450,17 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
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, {
|
||||
@ -480,7 +476,15 @@ describe('MarketOperationUtils tests', () => {
|
||||
DEFAULT_RATES,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: fn,
|
||||
getSellQuotes: fn,
|
||||
getBalancerSellQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
takerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
|
||||
},
|
||||
});
|
||||
const registryAddress = randomAddress();
|
||||
const newMarketOperationUtils = new MarketOperationUtils(
|
||||
@ -503,9 +507,17 @@ describe('MarketOperationUtils tests', () => {
|
||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
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, {
|
||||
@ -576,7 +588,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Kyber] = [0, 0, 0, 0]; // unused
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -614,8 +626,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -652,8 +664,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -678,8 +690,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.Native]: [0.95, 0.2, 0.2, 0.1],
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -703,7 +715,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -724,7 +736,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
|
||||
rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -741,7 +753,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
it('is able to create a order from LiquidityProvider', async () => {
|
||||
const registryAddress = randomAddress();
|
||||
const liquidityProviderAddress = randomAddress();
|
||||
const liquidityProviderAddress = (DEFAULT_FILL_DATA[ERC20BridgeSource.LiquidityProvider] as any)
|
||||
.poolAddress;
|
||||
const xAsset = randomAddress();
|
||||
const yAsset = randomAddress();
|
||||
const toSell = fromTokenUnitAmount(10);
|
||||
@ -752,14 +765,10 @@ describe('MarketOperationUtils tests', () => {
|
||||
[ERC20BridgeSource.LiquidityProvider]: createDecreasingRates(5),
|
||||
},
|
||||
);
|
||||
const [
|
||||
getLiquidityProviderParams,
|
||||
getLiquidityProviderFn,
|
||||
] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityProviderAddress);
|
||||
|
||||
replaceSamplerOps({
|
||||
getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT],
|
||||
getSellQuotesAsync: getSellQuotesFn,
|
||||
getLiquidityProviderFromRegistry: getLiquidityProviderFn,
|
||||
getSellQuotes: getSellQuotesFn,
|
||||
});
|
||||
|
||||
const sampler = new MarketOperationUtils(
|
||||
@ -776,7 +785,12 @@ describe('MarketOperationUtils tests', () => {
|
||||
}),
|
||||
],
|
||||
Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
{ excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0, shouldBatchBridgeOrders: false },
|
||||
{
|
||||
excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor),
|
||||
numSamples: 4,
|
||||
bridgeSlippage: 0,
|
||||
shouldBatchBridgeOrders: false,
|
||||
},
|
||||
);
|
||||
const result = ordersAndReport.optimizedOrders;
|
||||
expect(result.length).to.eql(1);
|
||||
@ -791,9 +805,6 @@ describe('MarketOperationUtils tests', () => {
|
||||
expect(result[0].takerAssetAmount).to.bignumber.eql(toSell);
|
||||
expect(getSellQuotesParams.sources).contains(ERC20BridgeSource.LiquidityProvider);
|
||||
expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress);
|
||||
expect(getLiquidityProviderParams.registryAddress).is.eql(registryAddress);
|
||||
expect(getLiquidityProviderParams.makerToken).is.eql(yAsset);
|
||||
expect(getLiquidityProviderParams.takerToken).is.eql(xAsset);
|
||||
});
|
||||
|
||||
it('batches contiguous bridge sources', async () => {
|
||||
@ -803,7 +814,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Curve] = [0.48, 0.01, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketSellOrdersAsync(
|
||||
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -860,9 +871,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const numSamples = _.random(1, 16);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@ -875,9 +886,17 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
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, {
|
||||
@ -893,7 +912,15 @@ describe('MarketOperationUtils tests', () => {
|
||||
DEFAULT_RATES,
|
||||
);
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: fn,
|
||||
getBuyQuotes: fn,
|
||||
getBalancerBuyQuotesOffChainAsync: (
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
makerFillAmounts: BigNumber[],
|
||||
) => {
|
||||
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
|
||||
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
|
||||
},
|
||||
});
|
||||
const registryAddress = randomAddress();
|
||||
const newMarketOperationUtils = new MarketOperationUtils(
|
||||
@ -916,9 +943,17 @@ describe('MarketOperationUtils tests', () => {
|
||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sourcesPolled.concat(sources.slice());
|
||||
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
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, {
|
||||
@ -988,7 +1023,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -1026,8 +1061,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -1063,8 +1098,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
),
|
||||
};
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -1087,7 +1122,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -1107,7 +1142,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
@ -1128,7 +1163,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.02, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Uniswap] = [0.48, 0.01, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
const improvedOrdersResponse = await marketOperationUtils.getMarketBuyOrdersAsync(
|
||||
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
|
||||
|
@ -12,15 +12,17 @@ import {
|
||||
CollapsedFill,
|
||||
DexSample,
|
||||
ERC20BridgeSource,
|
||||
MultiHopFillData,
|
||||
NativeCollapsedFill,
|
||||
} from '../src/utils/market_operation_utils/types';
|
||||
import { QuoteRequestor } from '../src/utils/quote_requestor';
|
||||
|
||||
import {
|
||||
BridgeReportSource,
|
||||
generateQuoteReport,
|
||||
MultiHopReportSource,
|
||||
NativeOrderbookReportSource,
|
||||
NativeRFQTReportSource,
|
||||
QuoteReportGenerator,
|
||||
QuoteReportSource,
|
||||
} from './../src/utils/quote_report_generator';
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
@ -47,309 +49,378 @@ const collapsedFillFromNativeOrder = (order: SignedOrder): NativeCollapsedFill =
|
||||
};
|
||||
};
|
||||
|
||||
describe('QuoteReportGenerator', async () => {
|
||||
describe('generateReport', async () => {
|
||||
it('should generate report properly for sell', () => {
|
||||
const marketOperation: MarketOperation = MarketOperation.Sell;
|
||||
describe('generateQuoteReport', async () => {
|
||||
it('should generate report properly for sell', () => {
|
||||
const marketOperation: MarketOperation = MarketOperation.Sell;
|
||||
|
||||
const kyberSample1: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
};
|
||||
const kyberSample2: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const uniswapSample2: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10005),
|
||||
output: new BigNumber(10006),
|
||||
};
|
||||
const dexQuotes: DexSample[] = [kyberSample1, kyberSample2, uniswapSample1, uniswapSample2];
|
||||
const kyberSample1: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
};
|
||||
const kyberSample2: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const uniswapSample2: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10005),
|
||||
output: new BigNumber(10006),
|
||||
};
|
||||
const dexQuotes: DexSample[] = [kyberSample1, kyberSample2, uniswapSample1, uniswapSample2];
|
||||
|
||||
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder1',
|
||||
takerAssetAmount: orderbookOrder1FillableAmount,
|
||||
});
|
||||
const orderbookOrder2FillableAmount = new BigNumber(99);
|
||||
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder2',
|
||||
takerAssetAmount: orderbookOrder2FillableAmount.plus(99),
|
||||
});
|
||||
const rfqtOrder1FillableAmount = new BigNumber(100);
|
||||
const rfqtOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'rfqtOrder1',
|
||||
takerAssetAmount: rfqtOrder1FillableAmount,
|
||||
});
|
||||
const rfqtOrder2FillableAmount = new BigNumber(1001);
|
||||
const rfqtOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'rfqtOrder2',
|
||||
takerAssetAmount: rfqtOrder2FillableAmount.plus(100),
|
||||
});
|
||||
const nativeOrders: SignedOrder[] = [orderbookOrder1, rfqtOrder1, rfqtOrder2, orderbookOrder2];
|
||||
const orderFillableAmounts: BigNumber[] = [
|
||||
orderbookOrder1FillableAmount,
|
||||
rfqtOrder1FillableAmount,
|
||||
rfqtOrder2FillableAmount,
|
||||
orderbookOrder2FillableAmount,
|
||||
];
|
||||
|
||||
// generate path
|
||||
const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const kyber2Fill: CollapsedFill = { ...kyberSample2, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2);
|
||||
const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2);
|
||||
const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, kyber2Fill];
|
||||
|
||||
// quote generator mock
|
||||
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(orderbookOrder2)))
|
||||
.returns(() => {
|
||||
return undefined;
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder1)))
|
||||
.returns(() => {
|
||||
return 'https://rfqt1.provider.club';
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder2)))
|
||||
.returns(() => {
|
||||
return 'https://rfqt2.provider.club';
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
|
||||
const orderReport = new QuoteReportGenerator(
|
||||
marketOperation,
|
||||
dexQuotes,
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
pathGenerated,
|
||||
quoteRequestor.object,
|
||||
).generateReport();
|
||||
|
||||
const rfqtOrder1Source: NativeRFQTReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: rfqtOrder1.makerAssetAmount,
|
||||
takerAmount: rfqtOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(rfqtOrder1),
|
||||
nativeOrder: rfqtOrder1,
|
||||
fillableTakerAmount: rfqtOrder1FillableAmount,
|
||||
isRfqt: true,
|
||||
makerUri: 'https://rfqt1.provider.club',
|
||||
};
|
||||
const rfqtOrder2Source: NativeRFQTReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: rfqtOrder2.makerAssetAmount,
|
||||
takerAmount: rfqtOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(rfqtOrder2),
|
||||
nativeOrder: rfqtOrder2,
|
||||
fillableTakerAmount: rfqtOrder2FillableAmount,
|
||||
isRfqt: true,
|
||||
makerUri: 'https://rfqt2.provider.club',
|
||||
};
|
||||
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||
nativeOrder: orderbookOrder1,
|
||||
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||
nativeOrder: orderbookOrder2,
|
||||
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const uniswap1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample1.output,
|
||||
takerAmount: uniswapSample1.input,
|
||||
};
|
||||
const uniswap2Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample2.output,
|
||||
takerAmount: uniswapSample2.input,
|
||||
};
|
||||
const kyber1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.output,
|
||||
takerAmount: kyberSample1.input,
|
||||
};
|
||||
const kyber2Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample2.output,
|
||||
takerAmount: kyberSample2.input,
|
||||
};
|
||||
|
||||
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||
kyber1Source,
|
||||
kyber2Source,
|
||||
uniswap1Source,
|
||||
uniswap2Source,
|
||||
orderbookOrder1Source,
|
||||
rfqtOrder1Source,
|
||||
rfqtOrder2Source,
|
||||
orderbookOrder2Source,
|
||||
];
|
||||
|
||||
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||
|
||||
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||
expect(actualSourcesConsidered).to.eql(
|
||||
expectedSourceConsidered,
|
||||
`sourceConsidered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
|
||||
const expectedSourcesDelivered: QuoteReportSource[] = [
|
||||
rfqtOrder2Source,
|
||||
orderbookOrder2Source,
|
||||
uniswap2Source,
|
||||
kyber2Source,
|
||||
];
|
||||
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||
|
||||
// remove fillable values
|
||||
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||
'fillableMakerAssetAmount',
|
||||
'fillableTakerAssetAmount',
|
||||
'fillableTakerFeeAmount',
|
||||
]) as SignedOrder;
|
||||
}
|
||||
|
||||
expect(actualSourceDelivered).to.eql(
|
||||
expectedSourceDelivered,
|
||||
`sourceDelivered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
|
||||
quoteRequestor.verifyAll();
|
||||
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder1',
|
||||
takerAssetAmount: orderbookOrder1FillableAmount,
|
||||
});
|
||||
it('should handle properly for buy without quoteRequestor', () => {
|
||||
const marketOperation: MarketOperation = MarketOperation.Buy;
|
||||
const kyberSample1: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const dexQuotes: DexSample[] = [kyberSample1, uniswapSample1];
|
||||
const orderbookOrder2FillableAmount = new BigNumber(99);
|
||||
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder2',
|
||||
takerAssetAmount: orderbookOrder2FillableAmount.plus(99),
|
||||
});
|
||||
const rfqtOrder1FillableAmount = new BigNumber(100);
|
||||
const rfqtOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'rfqtOrder1',
|
||||
takerAssetAmount: rfqtOrder1FillableAmount,
|
||||
});
|
||||
const rfqtOrder2FillableAmount = new BigNumber(1001);
|
||||
const rfqtOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'rfqtOrder2',
|
||||
takerAssetAmount: rfqtOrder2FillableAmount.plus(100),
|
||||
});
|
||||
const nativeOrders: SignedOrder[] = [orderbookOrder1, rfqtOrder1, rfqtOrder2, orderbookOrder2];
|
||||
const orderFillableAmounts: BigNumber[] = [
|
||||
orderbookOrder1FillableAmount,
|
||||
rfqtOrder1FillableAmount,
|
||||
rfqtOrder2FillableAmount,
|
||||
orderbookOrder2FillableAmount,
|
||||
];
|
||||
|
||||
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder1',
|
||||
takerAssetAmount: orderbookOrder1FillableAmount.plus(101),
|
||||
});
|
||||
const orderbookOrder2FillableAmount = new BigNumber(5000);
|
||||
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder2',
|
||||
takerAssetAmount: orderbookOrder2FillableAmount.plus(101),
|
||||
});
|
||||
const nativeOrders: SignedOrder[] = [orderbookOrder1, orderbookOrder2];
|
||||
const orderFillableAmounts: BigNumber[] = [orderbookOrder1FillableAmount, orderbookOrder2FillableAmount];
|
||||
// generate path
|
||||
const uniswap2Fill: CollapsedFill = { ...uniswapSample2, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const kyber2Fill: CollapsedFill = { ...kyberSample2, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2);
|
||||
const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2);
|
||||
const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, kyber2Fill];
|
||||
|
||||
// generate path
|
||||
const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1);
|
||||
const uniswap1Fill: CollapsedFill = { ...uniswapSample1, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, kyber1Fill];
|
||||
// quote generator mock
|
||||
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(orderbookOrder2)))
|
||||
.returns(() => {
|
||||
return undefined;
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder1)))
|
||||
.returns(() => {
|
||||
return 'https://rfqt1.provider.club';
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
quoteRequestor
|
||||
.setup(qr => qr.getMakerUriForOrderHash(orderHashUtils.getOrderHash(rfqtOrder2)))
|
||||
.returns(() => {
|
||||
return 'https://rfqt2.provider.club';
|
||||
})
|
||||
.verifiable(TypeMoq.Times.atLeastOnce());
|
||||
|
||||
const orderReport = new QuoteReportGenerator(
|
||||
marketOperation,
|
||||
dexQuotes,
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
pathGenerated,
|
||||
).generateReport();
|
||||
const orderReport = generateQuoteReport(
|
||||
marketOperation,
|
||||
dexQuotes,
|
||||
[],
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
pathGenerated,
|
||||
quoteRequestor.object,
|
||||
);
|
||||
|
||||
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||
nativeOrder: orderbookOrder1,
|
||||
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||
nativeOrder: orderbookOrder2,
|
||||
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const uniswap1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample1.input,
|
||||
takerAmount: uniswapSample1.output,
|
||||
};
|
||||
const kyber1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.input,
|
||||
takerAmount: kyberSample1.output,
|
||||
};
|
||||
const rfqtOrder1Source: NativeRFQTReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: rfqtOrder1.makerAssetAmount,
|
||||
takerAmount: rfqtOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(rfqtOrder1),
|
||||
nativeOrder: rfqtOrder1,
|
||||
fillableTakerAmount: rfqtOrder1FillableAmount,
|
||||
isRfqt: true,
|
||||
makerUri: 'https://rfqt1.provider.club',
|
||||
};
|
||||
const rfqtOrder2Source: NativeRFQTReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: rfqtOrder2.makerAssetAmount,
|
||||
takerAmount: rfqtOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(rfqtOrder2),
|
||||
nativeOrder: rfqtOrder2,
|
||||
fillableTakerAmount: rfqtOrder2FillableAmount,
|
||||
isRfqt: true,
|
||||
makerUri: 'https://rfqt2.provider.club',
|
||||
};
|
||||
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||
nativeOrder: orderbookOrder1,
|
||||
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||
nativeOrder: orderbookOrder2,
|
||||
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const uniswap1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample1.output,
|
||||
takerAmount: uniswapSample1.input,
|
||||
};
|
||||
const uniswap2Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample2.output,
|
||||
takerAmount: uniswapSample2.input,
|
||||
};
|
||||
const kyber1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.output,
|
||||
takerAmount: kyberSample1.input,
|
||||
};
|
||||
const kyber2Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample2.output,
|
||||
takerAmount: kyberSample2.input,
|
||||
};
|
||||
|
||||
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||
kyber1Source,
|
||||
uniswap1Source,
|
||||
orderbookOrder1Source,
|
||||
orderbookOrder2Source,
|
||||
];
|
||||
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||
expect(actualSourcesConsidered).to.eql(
|
||||
expectedSourceConsidered,
|
||||
`sourceConsidered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||
kyber1Source,
|
||||
kyber2Source,
|
||||
uniswap1Source,
|
||||
uniswap2Source,
|
||||
orderbookOrder1Source,
|
||||
rfqtOrder1Source,
|
||||
rfqtOrder2Source,
|
||||
orderbookOrder2Source,
|
||||
];
|
||||
|
||||
const expectedSourcesDelivered: QuoteReportSource[] = [orderbookOrder1Source, uniswap1Source, kyber1Source];
|
||||
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||
|
||||
// remove fillable values
|
||||
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||
'fillableMakerAssetAmount',
|
||||
'fillableTakerAssetAmount',
|
||||
'fillableTakerFeeAmount',
|
||||
]) as SignedOrder;
|
||||
}
|
||||
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||
expect(actualSourcesConsidered).to.eql(
|
||||
expectedSourceConsidered,
|
||||
`sourceConsidered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
|
||||
expect(actualSourceDelivered).to.eql(
|
||||
expectedSourceDelivered,
|
||||
`sourceDelivered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
const expectedSourcesDelivered: QuoteReportSource[] = [
|
||||
rfqtOrder2Source,
|
||||
orderbookOrder2Source,
|
||||
uniswap2Source,
|
||||
kyber2Source,
|
||||
];
|
||||
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||
|
||||
// remove fillable values
|
||||
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||
'fillableMakerAssetAmount',
|
||||
'fillableTakerAssetAmount',
|
||||
'fillableTakerFeeAmount',
|
||||
]) as SignedOrder;
|
||||
}
|
||||
|
||||
expect(actualSourceDelivered).to.eql(expectedSourceDelivered, `sourceDelivered incorrect at index ${idx}`);
|
||||
});
|
||||
|
||||
quoteRequestor.verifyAll();
|
||||
});
|
||||
it('should handle properly for buy without quoteRequestor', () => {
|
||||
const marketOperation: MarketOperation = MarketOperation.Buy;
|
||||
const kyberSample1: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
};
|
||||
const uniswapSample1: DexSample = {
|
||||
source: ERC20BridgeSource.UniswapV2,
|
||||
input: new BigNumber(10003),
|
||||
output: new BigNumber(10004),
|
||||
};
|
||||
const dexQuotes: DexSample[] = [kyberSample1, uniswapSample1];
|
||||
|
||||
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder1',
|
||||
takerAssetAmount: orderbookOrder1FillableAmount.plus(101),
|
||||
});
|
||||
const orderbookOrder2FillableAmount = new BigNumber(5000);
|
||||
const orderbookOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder2',
|
||||
takerAssetAmount: orderbookOrder2FillableAmount.plus(101),
|
||||
});
|
||||
const nativeOrders: SignedOrder[] = [orderbookOrder1, orderbookOrder2];
|
||||
const orderFillableAmounts: BigNumber[] = [orderbookOrder1FillableAmount, orderbookOrder2FillableAmount];
|
||||
|
||||
// generate path
|
||||
const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1);
|
||||
const uniswap1Fill: CollapsedFill = { ...uniswapSample1, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const kyber1Fill: CollapsedFill = { ...kyberSample1, subFills: [], sourcePathId: hexUtils.random() };
|
||||
const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, kyber1Fill];
|
||||
|
||||
const orderReport = generateQuoteReport(
|
||||
marketOperation,
|
||||
dexQuotes,
|
||||
[],
|
||||
nativeOrders,
|
||||
orderFillableAmounts,
|
||||
pathGenerated,
|
||||
);
|
||||
|
||||
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||
nativeOrder: orderbookOrder1,
|
||||
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const orderbookOrder2Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder2.makerAssetAmount,
|
||||
takerAmount: orderbookOrder2.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder2),
|
||||
nativeOrder: orderbookOrder2,
|
||||
fillableTakerAmount: orderbookOrder2FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const uniswap1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.UniswapV2,
|
||||
makerAmount: uniswapSample1.input,
|
||||
takerAmount: uniswapSample1.output,
|
||||
};
|
||||
const kyber1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.input,
|
||||
takerAmount: kyberSample1.output,
|
||||
};
|
||||
|
||||
const expectedSourcesConsidered: QuoteReportSource[] = [
|
||||
kyber1Source,
|
||||
uniswap1Source,
|
||||
orderbookOrder1Source,
|
||||
orderbookOrder2Source,
|
||||
];
|
||||
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||
expect(actualSourcesConsidered).to.eql(
|
||||
expectedSourceConsidered,
|
||||
`sourceConsidered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
|
||||
const expectedSourcesDelivered: QuoteReportSource[] = [orderbookOrder1Source, uniswap1Source, kyber1Source];
|
||||
expect(orderReport.sourcesDelivered.length).to.eql(expectedSourcesDelivered.length);
|
||||
orderReport.sourcesDelivered.forEach((actualSourceDelivered, idx) => {
|
||||
const expectedSourceDelivered = expectedSourcesDelivered[idx];
|
||||
|
||||
// remove fillable values
|
||||
if (actualSourceDelivered.liquiditySource === ERC20BridgeSource.Native) {
|
||||
actualSourceDelivered.nativeOrder = _.omit(actualSourceDelivered.nativeOrder, [
|
||||
'fillableMakerAssetAmount',
|
||||
'fillableTakerAssetAmount',
|
||||
'fillableTakerFeeAmount',
|
||||
]) as SignedOrder;
|
||||
}
|
||||
|
||||
expect(actualSourceDelivered).to.eql(expectedSourceDelivered, `sourceDelivered incorrect at index ${idx}`);
|
||||
});
|
||||
});
|
||||
it('should correctly generate report for a two-hop quote', () => {
|
||||
const marketOperation: MarketOperation = MarketOperation.Sell;
|
||||
const kyberSample1: DexSample = {
|
||||
source: ERC20BridgeSource.Kyber,
|
||||
input: new BigNumber(10000),
|
||||
output: new BigNumber(10001),
|
||||
};
|
||||
|
||||
const orderbookOrder1FillableAmount = new BigNumber(1000);
|
||||
const orderbookOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||
signature: 'orderbookOrder1',
|
||||
takerAssetAmount: orderbookOrder1FillableAmount.plus(101),
|
||||
});
|
||||
|
||||
const twoHopSample: DexSample<MultiHopFillData> = {
|
||||
source: ERC20BridgeSource.MultiHop,
|
||||
input: new BigNumber(3005),
|
||||
output: new BigNumber(3006),
|
||||
fillData: {
|
||||
intermediateToken: hexUtils.random(20),
|
||||
firstHopSource: {
|
||||
source: ERC20BridgeSource.Balancer,
|
||||
encodeCall: () => '',
|
||||
handleCallResults: _callResults => [new BigNumber(1337)],
|
||||
},
|
||||
secondHopSource: {
|
||||
source: ERC20BridgeSource.Curve,
|
||||
encodeCall: () => '',
|
||||
handleCallResults: _callResults => [new BigNumber(1337)],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const orderReport = generateQuoteReport(
|
||||
marketOperation,
|
||||
[kyberSample1],
|
||||
[twoHopSample],
|
||||
[orderbookOrder1],
|
||||
[orderbookOrder1FillableAmount],
|
||||
twoHopSample,
|
||||
);
|
||||
const orderbookOrder1Source: NativeOrderbookReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Native,
|
||||
makerAmount: orderbookOrder1.makerAssetAmount,
|
||||
takerAmount: orderbookOrder1.takerAssetAmount,
|
||||
orderHash: orderHashUtils.getOrderHash(orderbookOrder1),
|
||||
nativeOrder: orderbookOrder1,
|
||||
fillableTakerAmount: orderbookOrder1FillableAmount,
|
||||
isRfqt: false,
|
||||
};
|
||||
const kyber1Source: BridgeReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.Kyber,
|
||||
makerAmount: kyberSample1.output,
|
||||
takerAmount: kyberSample1.input,
|
||||
};
|
||||
const twoHopSource: MultiHopReportSource = {
|
||||
liquiditySource: ERC20BridgeSource.MultiHop,
|
||||
makerAmount: twoHopSample.output,
|
||||
takerAmount: twoHopSample.input,
|
||||
hopSources: [ERC20BridgeSource.Balancer, ERC20BridgeSource.Curve],
|
||||
};
|
||||
|
||||
const expectedSourcesConsidered: QuoteReportSource[] = [kyber1Source, orderbookOrder1Source, twoHopSource];
|
||||
expect(orderReport.sourcesConsidered.length).to.eql(expectedSourcesConsidered.length);
|
||||
orderReport.sourcesConsidered.forEach((actualSourcesConsidered, idx) => {
|
||||
const expectedSourceConsidered = expectedSourcesConsidered[idx];
|
||||
expect(actualSourcesConsidered).to.eql(
|
||||
expectedSourceConsidered,
|
||||
`sourceConsidered incorrect at index ${idx}`,
|
||||
);
|
||||
});
|
||||
|
||||
expect(orderReport.sourcesDelivered.length).to.eql(1);
|
||||
expect(orderReport.sourcesDelivered[0]).to.deep.equal(twoHopSource);
|
||||
});
|
||||
});
|
||||
|
@ -29,7 +29,7 @@ export type SampleSellsLPHandler = (
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
) => [SampleResults, string];
|
||||
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleSellsMBHandler = (
|
||||
multiBridgeAddress: string,
|
||||
@ -40,7 +40,7 @@ export type SampleSellsMBHandler = (
|
||||
) => SampleResults;
|
||||
|
||||
const DUMMY_PROVIDER = {
|
||||
sendAsync: (...args: any[]): any => {
|
||||
sendAsync: (..._args: any[]): any => {
|
||||
/* no-op */
|
||||
},
|
||||
};
|
||||
@ -73,7 +73,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
public batchCall(callDatas: string[]): ContractFunctionObj<string[]> {
|
||||
return {
|
||||
...super.batchCall(callDatas),
|
||||
callAsync: async (...callArgs: any[]) => callDatas.map(callData => this._callEncodedFunction(callData)),
|
||||
callAsync: async (..._callArgs: any[]) => callDatas.map(callData => this._callEncodedFunction(callData)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromKyberNetwork,
|
||||
this._handlers.sampleSellsFromKyberNetwork,
|
||||
@ -121,7 +121,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromEth2Dai,
|
||||
this._handlers.sampleSellsFromEth2Dai,
|
||||
@ -135,7 +135,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswap,
|
||||
this._handlers.sampleSellsFromUniswap,
|
||||
@ -145,10 +145,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromUniswapV2(
|
||||
path: string[],
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
public sampleSellsFromUniswapV2(path: string[], takerAssetAmounts: BigNumber[]): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswapV2,
|
||||
this._handlers.sampleSellsFromUniswapV2,
|
||||
@ -162,7 +159,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<[BigNumber[], string]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromLiquidityProviderRegistry,
|
||||
this._handlers.sampleSellsFromLiquidityProviderRegistry,
|
||||
@ -179,7 +176,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
intermediateToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromMultiBridge,
|
||||
this._handlers.sampleSellsFromMultiBridge,
|
||||
@ -195,7 +192,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromEth2Dai,
|
||||
this._handlers.sampleBuysFromEth2Dai,
|
||||
@ -209,7 +206,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswap,
|
||||
this._handlers.sampleBuysFromUniswap,
|
||||
@ -219,10 +216,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
);
|
||||
}
|
||||
|
||||
public sampleBuysFromUniswapV2(
|
||||
path: string[],
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
public sampleBuysFromUniswapV2(path: string[], makerAssetAmounts: BigNumber[]): ContractFunctionObj<BigNumber[]> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswapV2,
|
||||
this._handlers.sampleBuysFromUniswapV2,
|
||||
@ -241,7 +235,12 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
if (handler && this.getSelector(name) === selector) {
|
||||
const args = this.getABIDecodedTransactionData<any>(name, callData);
|
||||
const result = (handler as any)(...args);
|
||||
return this._lookupAbiEncoder(this.getFunctionSignature(name)).encodeReturnValues([result]);
|
||||
const encoder = this._lookupAbiEncoder(this.getFunctionSignature(name));
|
||||
if (encoder.getReturnValueDataItem().components!.length === 1) {
|
||||
return encoder.encodeReturnValues([result]);
|
||||
} else {
|
||||
return encoder.encodeReturnValues(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selector === this.getSelector('batchCall')) {
|
||||
@ -260,7 +259,7 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
|
||||
): ContractFunctionObj<TResult> {
|
||||
return {
|
||||
...superFn.call(this, ...args),
|
||||
callAsync: async (...callArgs: any[]): Promise<TResult> => {
|
||||
callAsync: async (..._callArgs: any[]): Promise<TResult> => {
|
||||
if (!handler) {
|
||||
throw new Error(`${superFn.name} handler undefined`);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
|
||||
bestCaseQuoteInfo: quoteInfo,
|
||||
worstCaseQuoteInfo: quoteInfo,
|
||||
sourceBreakdown: breakdown,
|
||||
isTwoHop: false,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
|
@ -4,11 +4,13 @@
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/approximate_buys';
|
||||
export * from '../test/generated-wrappers/balancer_sampler';
|
||||
export * from '../test/generated-wrappers/curve_sampler';
|
||||
export * from '../test/generated-wrappers/dummy_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/dummy_liquidity_provider_registry';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/eth2_dai_sampler';
|
||||
export * from '../test/generated-wrappers/i_balancer';
|
||||
export * from '../test/generated-wrappers/i_curve';
|
||||
export * from '../test/generated-wrappers/i_eth2_dai';
|
||||
export * from '../test/generated-wrappers/i_kyber_hint_handler';
|
||||
@ -31,5 +33,6 @@ export * from '../test/generated-wrappers/native_order_sampler';
|
||||
export * from '../test/generated-wrappers/sampler_utils';
|
||||
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/test_native_order_sampler';
|
||||
export * from '../test/generated-wrappers/two_hop_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_v2_sampler';
|
||||
|
@ -9,11 +9,13 @@
|
||||
"generated-artifacts/ILiquidityProvider.json",
|
||||
"generated-artifacts/ILiquidityProviderRegistry.json",
|
||||
"test/generated-artifacts/ApproximateBuys.json",
|
||||
"test/generated-artifacts/BalancerSampler.json",
|
||||
"test/generated-artifacts/CurveSampler.json",
|
||||
"test/generated-artifacts/DummyLiquidityProvider.json",
|
||||
"test/generated-artifacts/DummyLiquidityProviderRegistry.json",
|
||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/Eth2DaiSampler.json",
|
||||
"test/generated-artifacts/IBalancer.json",
|
||||
"test/generated-artifacts/ICurve.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberHintHandler.json",
|
||||
@ -36,6 +38,7 @@
|
||||
"test/generated-artifacts/SamplerUtils.json",
|
||||
"test/generated-artifacts/TestERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/TestNativeOrderSampler.json",
|
||||
"test/generated-artifacts/TwoHopSampler.json",
|
||||
"test/generated-artifacts/UniswapSampler.json",
|
||||
"test/generated-artifacts/UniswapV2Sampler.json"
|
||||
]
|
||||
|
@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Update `ERC20BridgeSampler` artifact",
|
||||
"pr": 2633
|
||||
},
|
||||
{
|
||||
"note": "Remove `ERC20BridgeSampler` artifact",
|
||||
"pr": 2647
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1,475 +0,0 @@
|
||||
{
|
||||
"schemaVersion": "2.0.0",
|
||||
"contractName": "IERC20BridgeSampler",
|
||||
"compilerOutput": {
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{ "internalType": "bytes[]", "name": "callDatas", "type": "bytes[]" }],
|
||||
"name": "batchCall",
|
||||
"outputs": [{ "internalType": "bytes[]", "name": "callResults", "type": "bytes[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "registryAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" }
|
||||
],
|
||||
"name": "getLiquidityProviderFromRegistry",
|
||||
"outputs": [{ "internalType": "address", "name": "providerAddress", "type": "address" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[]",
|
||||
"name": "orders",
|
||||
"type": "tuple[]"
|
||||
},
|
||||
{ "internalType": "bytes[]", "name": "orderSignatures", "type": "bytes[]" },
|
||||
{ "internalType": "address", "name": "devUtilsAddress", "type": "address" }
|
||||
],
|
||||
"name": "getOrderFillableMakerAssetAmounts",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableMakerAssetAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "makerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "senderAddress", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
|
||||
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct LibOrder.Order[]",
|
||||
"name": "orders",
|
||||
"type": "tuple[]"
|
||||
},
|
||||
{ "internalType": "bytes[]", "name": "orderSignatures", "type": "bytes[]" },
|
||||
{ "internalType": "address", "name": "devUtilsAddress", "type": "address" }
|
||||
],
|
||||
"name": "getOrderFillableTakerAssetAmounts",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256[]", "name": "orderFillableTakerAssetAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "curveAddress", "type": "address" },
|
||||
{ "internalType": "int128", "name": "fromTokenIdx", "type": "int128" },
|
||||
{ "internalType": "int128", "name": "toTokenIdx", "type": "int128" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuysFromCurve",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuysFromEth2Dai",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "uint256", "name": "targetSlippageBps", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "maxIterations", "type": "uint256" }
|
||||
],
|
||||
"internalType": "struct IERC20BridgeSampler.FakeBuyOptions",
|
||||
"name": "opts",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "sampleBuysFromKyberNetwork",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "registryAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "uint256", "name": "targetSlippageBps", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "maxIterations", "type": "uint256" }
|
||||
],
|
||||
"internalType": "struct IERC20BridgeSampler.FakeBuyOptions",
|
||||
"name": "opts",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "sampleBuysFromLiquidityProviderRegistry",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuysFromUniswap",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address[]", "name": "path", "type": "address[]" },
|
||||
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleBuysFromUniswapV2",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "curveAddress", "type": "address" },
|
||||
{ "internalType": "int128", "name": "fromTokenIdx", "type": "int128" },
|
||||
{ "internalType": "int128", "name": "toTokenIdx", "type": "int128" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromCurve",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromEth2Dai",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromKyberNetwork",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "registryAddress", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromLiquidityProviderRegistry",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "multibridge", "type": "address" },
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "intermediateToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromMultiBridge",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "takerToken", "type": "address" },
|
||||
{ "internalType": "address", "name": "makerToken", "type": "address" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromUniswap",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{ "internalType": "address[]", "name": "path", "type": "address[]" },
|
||||
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
|
||||
],
|
||||
"name": "sampleSellsFromUniswapV2",
|
||||
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"devdoc": {
|
||||
"methods": {
|
||||
"batchCall(bytes[])": {
|
||||
"details": "Call multiple public functions on this contract in a single transaction.",
|
||||
"params": { "callDatas": "ABI-encoded call data for each function call." },
|
||||
"return": "callResults ABI-encoded results data for each call."
|
||||
},
|
||||
"getLiquidityProviderFromRegistry(address,address,address)": {
|
||||
"details": "Returns the address of a liquidity provider for the given market (takerToken, makerToken), from a registry of liquidity providers. Returns address(0) if no such provider exists in the registry.",
|
||||
"params": {
|
||||
"makerToken": "Maker asset managed by liquidity provider.",
|
||||
"takerToken": "Taker asset managed by liquidity provider."
|
||||
},
|
||||
"return": "providerAddress Address of the liquidity provider."
|
||||
},
|
||||
"getOrderFillableMakerAssetAmounts((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],address)": {
|
||||
"details": "Queries the fillable maker asset amounts of native orders.",
|
||||
"params": {
|
||||
"devUtilsAddress": "Address to the DevUtils contract.",
|
||||
"orderSignatures": "Signatures for each respective order in `orders`.",
|
||||
"orders": "Native orders to query."
|
||||
},
|
||||
"return": "orderFillableMakerAssetAmounts How much maker asset can be filled by each order in `orders`."
|
||||
},
|
||||
"getOrderFillableTakerAssetAmounts((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],address)": {
|
||||
"details": "Queries the fillable taker asset amounts of native orders.",
|
||||
"params": {
|
||||
"devUtilsAddress": "Address to the DevUtils contract.",
|
||||
"orderSignatures": "Signatures for each respective order in `orders`.",
|
||||
"orders": "Native orders to query."
|
||||
},
|
||||
"return": "orderFillableTakerAssetAmounts How much taker asset can be filled by each order in `orders`."
|
||||
},
|
||||
"sampleBuysFromCurve(address,int128,int128,uint256[])": {
|
||||
"details": "Sample buy quotes from Curve.",
|
||||
"params": {
|
||||
"curveAddress": "Address of the Curve contract.",
|
||||
"fromTokenIdx": "Index of the taker token (what to sell).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"toTokenIdx": "Index of the maker token (what to buy)."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromEth2Dai(address,address,uint256[])": {
|
||||
"details": "Sample buy quotes from Eth2Dai/Oasis.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"takerToken": "Address of the taker token (what to sell)."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromKyberNetwork(address,address,uint256[],(uint256,uint256))": {
|
||||
"details": "Sample buy quotes from Kyber.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"opts": "`FakeBuyOptions` specifying target slippage and max iterations.",
|
||||
"takerToken": "Address of the taker token (what to sell)."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[],(uint256,uint256))": {
|
||||
"details": "Sample buy quotes from an arbitrary on-chain liquidity provider.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"opts": "`FakeBuyOptions` specifying target slippage and max iterations.",
|
||||
"registryAddress": "Address of the liquidity provider registry contract.",
|
||||
"takerToken": "Address of the taker token (what to sell)."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromUniswap(address,address,uint256[])": {
|
||||
"details": "Sample buy quotes from Uniswap.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"takerToken": "Address of the taker token (what to sell)."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleBuysFromUniswapV2(address[],uint256[])": {
|
||||
"details": "Sample buy quotes from UniswapV2.",
|
||||
"params": {
|
||||
"makerTokenAmounts": "Maker token buy amount for each sample.",
|
||||
"path": "Token route."
|
||||
},
|
||||
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
|
||||
},
|
||||
"sampleSellsFromCurve(address,int128,int128,uint256[])": {
|
||||
"details": "Sample sell quotes from Curve.",
|
||||
"params": {
|
||||
"curveAddress": "Address of the Curve contract.",
|
||||
"fromTokenIdx": "Index of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample.",
|
||||
"toTokenIdx": "Index of the maker token (what to buy)."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromEth2Dai(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Eth2Dai/Oasis.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromKyberNetwork(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Kyber.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromLiquidityProviderRegistry(address,address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from an arbitrary on-chain liquidity provider.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"registryAddress": "Address of the liquidity provider registry contract.",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromMultiBridge(address,address,address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from MultiBridge.",
|
||||
"params": {
|
||||
"intermediateToken": "The address of the intermediate token to use in an indirect route.",
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"multibridge": "Address of the MultiBridge contract.",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromUniswap(address,address,uint256[])": {
|
||||
"details": "Sample sell quotes from Uniswap.",
|
||||
"params": {
|
||||
"makerToken": "Address of the maker token (what to buy).",
|
||||
"takerToken": "Address of the taker token (what to sell).",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
},
|
||||
"sampleSellsFromUniswapV2(address[],uint256[])": {
|
||||
"details": "Sample sell quotes from UniswapV2.",
|
||||
"params": {
|
||||
"path": "Token route.",
|
||||
"takerTokenAmounts": "Taker token sell amount for each sample."
|
||||
},
|
||||
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
|
||||
}
|
||||
}
|
||||
},
|
||||
"evm": { "bytecode": { "object": "0x" }, "deployedBytecode": { "object": "0x" } }
|
||||
},
|
||||
"compiler": {
|
||||
"name": "solc",
|
||||
"version": "0.5.17+commit.d19bba13",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"devdoc",
|
||||
"evm.bytecode.object",
|
||||
"evm.bytecode.sourceMap",
|
||||
"evm.deployedBytecode.object",
|
||||
"evm.deployedBytecode.sourceMap"
|
||||
]
|
||||
}
|
||||
},
|
||||
"evmVersion": "istanbul"
|
||||
}
|
||||
},
|
||||
"chains": {}
|
||||
}
|
@ -32,7 +32,7 @@
|
||||
"wrappers:generate": "abi-gen --abis ${npm_package_config_abis} --output src/generated-wrappers --backend ethers"
|
||||
},
|
||||
"config": {
|
||||
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice|ITransformERC20|IZeroEx).json"
|
||||
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice|ITransformERC20|IZeroEx).json"
|
||||
},
|
||||
"gitpkg": {
|
||||
"registry": "git@github.com:0xProject/gitpkg-registry.git"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es6",
|
||||
"lib": ["es2017", "dom", "esnext.asynciterable", "es2018.promise"],
|
||||
"experimentalDecorators": true,
|
||||
"downlevelIteration": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user