Compare commits
4 Commits
protocol@2
...
protocol@e
Author | SHA1 | Date | |
---|---|---|---|
|
e77958425f | ||
|
6af4d71573 | ||
|
ee985240fb | ||
|
2aadbda527 |
18
CODEOWNERS
18
CODEOWNERS
@@ -1,18 +1,20 @@
|
|||||||
# See https://help.github.com/articles/about-codeowners/
|
# See https://help.github.com/articles/about-codeowners/
|
||||||
|
|
||||||
# for more info about CODEOWNERS file
|
# for more info about CODEOWNERS file
|
||||||
|
|
||||||
# It uses the same pattern rule for gitignore file
|
# It uses the same pattern rule for gitignore file
|
||||||
|
|
||||||
# https://git-scm.com/docs/gitignore#_pattern_format
|
# https://git-scm.com/docs/gitignore#_pattern_format
|
||||||
|
|
||||||
# Website
|
packages/asset-swapper/ @dekz @mzhu25 @dextracker @kh-chang
|
||||||
packages/asset-swapper/ @BMillman19 @fragosti @dave4506
|
|
||||||
packages/instant/ @BMillman19 @fragosti @dave4506
|
|
||||||
|
|
||||||
# Dev tools & setup
|
# Dev tools & setup
|
||||||
.circleci/ @dorothy-zbornak
|
|
||||||
packages/contract-addresses/ @abandeali1
|
.circleci/ @dekz @mzhu25
|
||||||
packages/contract-artifacts/ @abandeali1
|
packages/contract-addresses/ @dekz @mzhu25 @dextracker @kh-chang
|
||||||
packages/order-utils/ @dorothy-zbornak
|
packages/contract-artifacts/ @dekz @mzhu25
|
||||||
|
packages/protocol-utils/ @dekz @mzhu25
|
||||||
|
|
||||||
# Protocol/smart contracts
|
# Protocol/smart contracts
|
||||||
contracts/ @abandeali1 @hysz @dorothy-zbornak @mzhu25
|
|
||||||
|
contracts/ @dekz @mzhu25 @dextracker
|
||||||
|
@@ -1,4 +1,27 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "16.63.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Remove JS router",
|
||||||
|
"pr": 480
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Removed Median price in favour of best gas adjusted price",
|
||||||
|
"pr": 480
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": 1656491792
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "16.62.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Offboard Smoothy and ComethSwap",
|
||||||
|
"pr": 509
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "16.62.1",
|
"version": "16.62.1",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -5,6 +5,15 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v16.63.0 - _June 29, 2022_
|
||||||
|
|
||||||
|
* Remove JS router (#480)
|
||||||
|
* Removed Median price in favour of best gas adjusted price (#480)
|
||||||
|
|
||||||
|
## v16.62.2 - _Invalid date_
|
||||||
|
|
||||||
|
* Offboard Smoothy and ComethSwap (#509)
|
||||||
|
|
||||||
## v16.62.1 - _June 15, 2022_
|
## v16.62.1 - _June 15, 2022_
|
||||||
|
|
||||||
* Remove nUSD from intermediate liquidity to save on sampler gas (#505)
|
* Remove nUSD from intermediate liquidity to save on sampler gas (#505)
|
||||||
|
@@ -39,7 +39,6 @@ import "./MooniswapSampler.sol";
|
|||||||
import "./NativeOrderSampler.sol";
|
import "./NativeOrderSampler.sol";
|
||||||
import "./PlatypusSampler.sol";
|
import "./PlatypusSampler.sol";
|
||||||
import "./ShellSampler.sol";
|
import "./ShellSampler.sol";
|
||||||
import "./SmoothySampler.sol";
|
|
||||||
import "./TwoHopSampler.sol";
|
import "./TwoHopSampler.sol";
|
||||||
import "./UniswapSampler.sol";
|
import "./UniswapSampler.sol";
|
||||||
import "./UniswapV2Sampler.sol";
|
import "./UniswapV2Sampler.sol";
|
||||||
@@ -68,7 +67,6 @@ contract ERC20BridgeSampler is
|
|||||||
NativeOrderSampler,
|
NativeOrderSampler,
|
||||||
PlatypusSampler,
|
PlatypusSampler,
|
||||||
ShellSampler,
|
ShellSampler,
|
||||||
SmoothySampler,
|
|
||||||
TwoHopSampler,
|
TwoHopSampler,
|
||||||
UniswapSampler,
|
UniswapSampler,
|
||||||
UniswapV2Sampler,
|
UniswapV2Sampler,
|
||||||
|
@@ -1,156 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
// import "./interfaces/ISmoothy.sol";
|
|
||||||
import "./ApproximateBuys.sol";
|
|
||||||
import "./SamplerUtils.sol";
|
|
||||||
import "./interfaces/ISmoothy.sol";
|
|
||||||
|
|
||||||
contract SmoothySampler is
|
|
||||||
SamplerUtils,
|
|
||||||
ApproximateBuys
|
|
||||||
{
|
|
||||||
/// @dev Information for sampling from smoothy sources.
|
|
||||||
struct SmoothyInfo {
|
|
||||||
address poolAddress;
|
|
||||||
bytes4 sellQuoteFunctionSelector;
|
|
||||||
bytes4 buyQuoteFunctionSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Base gas limit for Smoothy calls.
|
|
||||||
uint256 constant private SMOOTHY_CALL_GAS = 600e3;
|
|
||||||
|
|
||||||
/// @dev Sample sell quotes from Smoothy.
|
|
||||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
|
||||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
|
||||||
/// amount.
|
|
||||||
function sampleSellsFromSmoothy(
|
|
||||||
SmoothyInfo memory smoothyInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory takerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory makerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Basically a Curve fork
|
|
||||||
|
|
||||||
// Smoothy only keep a percentage of its tokens available in reserve
|
|
||||||
uint256 poolReserveMakerAmount = ISmoothy(smoothyInfo.poolAddress).getBalance(uint256(toTokenIdx)) -
|
|
||||||
ISmoothy(smoothyInfo.poolAddress)._yBalances(uint256(toTokenIdx));
|
|
||||||
(, , , uint256 decimals) = ISmoothy(smoothyInfo.poolAddress).getTokenStats(uint256(toTokenIdx));
|
|
||||||
poolReserveMakerAmount = poolReserveMakerAmount/(10**(18-decimals));
|
|
||||||
|
|
||||||
uint256 numSamples = takerTokenAmounts.length;
|
|
||||||
makerTokenAmounts = new uint256[](numSamples);
|
|
||||||
for (uint256 i = 0; i < numSamples; i++) {
|
|
||||||
(bool didSucceed, bytes memory resultData) =
|
|
||||||
smoothyInfo.poolAddress.staticcall.gas(SMOOTHY_CALL_GAS)(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
smoothyInfo.sellQuoteFunctionSelector,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
takerTokenAmounts[i]
|
|
||||||
));
|
|
||||||
uint256 buyAmount = 0;
|
|
||||||
if (didSucceed) {
|
|
||||||
buyAmount = abi.decode(resultData, (uint256));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the quoted buyAmount is available in the pool reserve
|
|
||||||
if (buyAmount >= poolReserveMakerAmount) {
|
|
||||||
// Assign pool reserve amount for all higher samples to break early
|
|
||||||
for (uint256 j = i; j < numSamples; j++) {
|
|
||||||
makerTokenAmounts[j] = poolReserveMakerAmount;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
makerTokenAmounts[i] = buyAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break early if there are 0 amounts
|
|
||||||
if (makerTokenAmounts[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sample buy quotes from Smoothy.
|
|
||||||
/// @param smoothyInfo Smoothy information specific to this token pair.
|
|
||||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
|
||||||
/// @param toTokenIdx Index of the maker token (what to buy).
|
|
||||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
|
||||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
|
||||||
/// amount.
|
|
||||||
function sampleBuysFromSmoothy(
|
|
||||||
SmoothyInfo memory smoothyInfo,
|
|
||||||
int128 fromTokenIdx,
|
|
||||||
int128 toTokenIdx,
|
|
||||||
uint256[] memory makerTokenAmounts
|
|
||||||
)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
returns (uint256[] memory takerTokenAmounts)
|
|
||||||
{
|
|
||||||
// Buys not supported so approximate it.
|
|
||||||
return _sampleApproximateBuys(
|
|
||||||
ApproximateBuyQuoteOpts({
|
|
||||||
makerTokenData: abi.encode(toTokenIdx, smoothyInfo),
|
|
||||||
takerTokenData: abi.encode(fromTokenIdx, smoothyInfo),
|
|
||||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromSmoothy
|
|
||||||
}),
|
|
||||||
makerTokenAmounts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _sampleSellForApproximateBuyFromSmoothy(
|
|
||||||
bytes memory takerTokenData,
|
|
||||||
bytes memory makerTokenData,
|
|
||||||
uint256 sellAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 buyAmount)
|
|
||||||
{
|
|
||||||
(int128 takerTokenIdx, SmoothyInfo memory smoothyInfo) =
|
|
||||||
abi.decode(takerTokenData, (int128, SmoothyInfo));
|
|
||||||
(int128 makerTokenIdx) =
|
|
||||||
abi.decode(makerTokenData, (int128));
|
|
||||||
(bool success, bytes memory resultData) =
|
|
||||||
address(this).staticcall(abi.encodeWithSelector(
|
|
||||||
this.sampleSellsFromSmoothy.selector,
|
|
||||||
smoothyInfo,
|
|
||||||
takerTokenIdx,
|
|
||||||
makerTokenIdx,
|
|
||||||
_toSingleValueArray(sellAmount)
|
|
||||||
));
|
|
||||||
if (!success) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// solhint-disable-next-line indent
|
|
||||||
return abi.decode(resultData, (uint256[]))[0];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,45 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 ZeroEx Intl.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma solidity ^0.6;
|
|
||||||
|
|
||||||
|
|
||||||
interface ISmoothy {
|
|
||||||
|
|
||||||
function getBalance (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 balance);
|
|
||||||
|
|
||||||
function _yBalances (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 balance);
|
|
||||||
|
|
||||||
function getTokenStats (
|
|
||||||
uint256 tid
|
|
||||||
)
|
|
||||||
external
|
|
||||||
view
|
|
||||||
returns (uint256 softWeight, uint256 hardWeight, uint256 balance, uint256 decimals);
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/asset-swapper",
|
"name": "@0x/asset-swapper",
|
||||||
"version": "16.62.1",
|
"version": "16.63.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|SmoothySampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json",
|
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2BatchSampler|BalancerV2Common|BalancerV2Sampler|BancorSampler|BancorV3Sampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|ERC20BridgeSampler|FakeTaker|GMXSampler|IBalancer|IBalancerV2Vault|IBancor|IBancorV3|ICurve|IGMX|IMStable|IMooniswap|IMultiBridge|IPlatypus|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|NativeOrderSampler|PlatypusSampler|SamplerUtils|ShellSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler|VelodromeSampler).json",
|
||||||
"postpublish": {
|
"postpublish": {
|
||||||
"assets": []
|
"assets": []
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ export {
|
|||||||
ContractTxFunctionObj,
|
ContractTxFunctionObj,
|
||||||
SendTransactionOpts,
|
SendTransactionOpts,
|
||||||
} from '@0x/base-contract';
|
} from '@0x/base-contract';
|
||||||
export { ContractAddresses } from '@0x/contract-addresses';
|
export { ContractAddresses, ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
export {
|
export {
|
||||||
V4RFQFirmQuote,
|
V4RFQFirmQuote,
|
||||||
V4RFQIndicativeQuote,
|
V4RFQIndicativeQuote,
|
||||||
@@ -132,6 +132,7 @@ export {
|
|||||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
NATIVE_FEE_TOKEN_BY_CHAIN_ID,
|
||||||
|
ZERO_AMOUNT,
|
||||||
} from './utils/market_operation_utils/constants';
|
} from './utils/market_operation_utils/constants';
|
||||||
export {
|
export {
|
||||||
Parameters,
|
Parameters,
|
||||||
@@ -141,7 +142,6 @@ export {
|
|||||||
export {
|
export {
|
||||||
BalancerFillData,
|
BalancerFillData,
|
||||||
BancorFillData,
|
BancorFillData,
|
||||||
CollapsedFill,
|
|
||||||
CurveFillData,
|
CurveFillData,
|
||||||
CurveFunctionSelectors,
|
CurveFunctionSelectors,
|
||||||
CurveInfo,
|
CurveInfo,
|
||||||
@@ -150,7 +150,9 @@ export {
|
|||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
ExchangeProxyOverhead,
|
ExchangeProxyOverhead,
|
||||||
FeeSchedule,
|
FeeSchedule,
|
||||||
|
GasSchedule,
|
||||||
Fill,
|
Fill,
|
||||||
|
FillAdjustor,
|
||||||
FillData,
|
FillData,
|
||||||
GetMarketOrdersRfqOpts,
|
GetMarketOrdersRfqOpts,
|
||||||
LiquidityProviderFillData,
|
LiquidityProviderFillData,
|
||||||
@@ -159,7 +161,6 @@ export {
|
|||||||
MarketDepthSide,
|
MarketDepthSide,
|
||||||
MooniswapFillData,
|
MooniswapFillData,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeCollapsedFill,
|
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
NativeFillData,
|
NativeFillData,
|
||||||
@@ -168,6 +169,7 @@ export {
|
|||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
UniswapV2FillData,
|
UniswapV2FillData,
|
||||||
} from './utils/market_operation_utils/types';
|
} from './utils/market_operation_utils/types';
|
||||||
|
export { IdentityFillAdjustor } from './utils/market_operation_utils/identity_fill_adjustor';
|
||||||
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
export {
|
export {
|
||||||
BridgeQuoteReportEntry,
|
BridgeQuoteReportEntry,
|
||||||
@@ -191,3 +193,5 @@ export type Native = ERC20BridgeSource.Native;
|
|||||||
export type MultiHop = ERC20BridgeSource.MultiHop;
|
export type MultiHop = ERC20BridgeSource.MultiHop;
|
||||||
|
|
||||||
export { rfqtMocker, RfqtQuoteEndpoint } from './utils/rfqt_mocker';
|
export { rfqtMocker, RfqtQuoteEndpoint } from './utils/rfqt_mocker';
|
||||||
|
|
||||||
|
export { adjustOutput } from './utils/market_operation_utils/fills';
|
||||||
|
@@ -282,7 +282,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
// ETH buy/sell is supported
|
// ETH buy/sell is supported
|
||||||
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
![sellToken, buyToken].includes(NATIVE_FEE_TOKEN_BY_CHAIN_ID[ChainId.Mainnet])
|
||||||
) {
|
) {
|
||||||
const fillData = slippedOrders[0].fills[0].fillData as CurveFillData;
|
const fillData = slippedOrders[0].fillData as CurveFillData;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToLiquidityProvider(
|
.sellToLiquidityProvider(
|
||||||
@@ -311,7 +311,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
this.chainId === ChainId.Mainnet &&
|
this.chainId === ChainId.Mainnet &&
|
||||||
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.Mooniswap])
|
||||||
) {
|
) {
|
||||||
const fillData = slippedOrders[0].fills[0].fillData as MooniswapFillData;
|
const fillData = slippedOrders[0].fillData as MooniswapFillData;
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._exchangeProxy
|
calldataHexString: this._exchangeProxy
|
||||||
.sellToLiquidityProvider(
|
.sellToLiquidityProvider(
|
||||||
|
@@ -33,8 +33,8 @@ import { DexOrderSampler } from './utils/market_operation_utils/sampler';
|
|||||||
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
import { SourceFilters } from './utils/market_operation_utils/source_filters';
|
||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
FeeSchedule,
|
|
||||||
FillData,
|
FillData,
|
||||||
|
GasSchedule,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
MarketDepth,
|
MarketDepth,
|
||||||
MarketDepthSide,
|
MarketDepthSide,
|
||||||
@@ -366,9 +366,11 @@ export class SwapQuoter {
|
|||||||
const calcOpts: GetMarketOrdersOpts = {
|
const calcOpts: GetMarketOrdersOpts = {
|
||||||
...cloneOpts,
|
...cloneOpts,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
feeSchedule: _.mapValues(opts.feeSchedule, gasCost => (fillData: FillData) =>
|
feeSchedule: _.mapValues(opts.gasSchedule, gasCost => (fillData: FillData) => {
|
||||||
gasCost === undefined ? 0 : gasPrice.times(gasCost(fillData)),
|
const gas = gasCost ? gasCost(fillData) : 0;
|
||||||
),
|
const fee = gasPrice.times(gas);
|
||||||
|
return { gas, fee };
|
||||||
|
}),
|
||||||
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
|
||||||
};
|
};
|
||||||
// pass the QuoteRequestor on if rfqt enabled
|
// pass the QuoteRequestor on if rfqt enabled
|
||||||
@@ -502,7 +504,7 @@ function createSwapQuote(
|
|||||||
operation: MarketOperation,
|
operation: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
gasPrice: BigNumber,
|
gasPrice: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
gasSchedule: GasSchedule,
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): SwapQuote {
|
): SwapQuote {
|
||||||
const {
|
const {
|
||||||
@@ -562,7 +564,7 @@ function calculateQuoteInfo(
|
|||||||
operation: MarketOperation,
|
operation: MarketOperation,
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
gasPrice: BigNumber,
|
gasPrice: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
gasSchedule: GasSchedule,
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||||
const bestCaseFillResult = simulateBestCaseFill({
|
const bestCaseFillResult = simulateBestCaseFill({
|
||||||
@@ -591,25 +593,23 @@ function calculateQuoteInfo(
|
|||||||
function calculateTwoHopQuoteInfo(
|
function calculateTwoHopQuoteInfo(
|
||||||
optimizedOrders: OptimizedMarketOrder[],
|
optimizedOrders: OptimizedMarketOrder[],
|
||||||
operation: MarketOperation,
|
operation: MarketOperation,
|
||||||
gasSchedule: FeeSchedule,
|
gasSchedule: GasSchedule,
|
||||||
slippage: number,
|
slippage: number,
|
||||||
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
): { bestCaseQuoteInfo: SwapQuoteInfo; worstCaseQuoteInfo: SwapQuoteInfo; sourceBreakdown: SwapQuoteOrdersBreakdown } {
|
||||||
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
const [firstHopOrder, secondHopOrder] = optimizedOrders;
|
||||||
const [firstHopFill] = firstHopOrder.fills;
|
|
||||||
const [secondHopFill] = secondHopOrder.fills;
|
|
||||||
const gas = new BigNumber(
|
const gas = new BigNumber(
|
||||||
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
gasSchedule[ERC20BridgeSource.MultiHop]!({
|
||||||
firstHopSource: _.pick(firstHopFill, 'source', 'fillData'),
|
firstHopSource: _.pick(firstHopOrder, 'source', 'fillData'),
|
||||||
secondHopSource: _.pick(secondHopFill, 'source', 'fillData'),
|
secondHopSource: _.pick(secondHopOrder, 'source', 'fillData'),
|
||||||
}),
|
}),
|
||||||
).toNumber();
|
).toNumber();
|
||||||
const isSell = operation === MarketOperation.Sell;
|
const isSell = operation === MarketOperation.Sell;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bestCaseQuoteInfo: {
|
bestCaseQuoteInfo: {
|
||||||
makerAmount: isSell ? secondHopFill.output : secondHopFill.input,
|
makerAmount: isSell ? secondHopOrder.fill.output : secondHopOrder.fill.input,
|
||||||
takerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
takerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||||
totalTakerAmount: isSell ? firstHopFill.input : firstHopFill.output,
|
totalTakerAmount: isSell ? firstHopOrder.fill.input : firstHopOrder.fill.output,
|
||||||
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
feeTakerTokenAmount: constants.ZERO_AMOUNT,
|
||||||
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
protocolFeeInWeiAmount: constants.ZERO_AMOUNT,
|
||||||
gas,
|
gas,
|
||||||
@@ -635,7 +635,7 @@ function calculateTwoHopQuoteInfo(
|
|||||||
[ERC20BridgeSource.MultiHop]: {
|
[ERC20BridgeSource.MultiHop]: {
|
||||||
proportion: new BigNumber(1),
|
proportion: new BigNumber(1),
|
||||||
intermediateToken: secondHopOrder.takerToken,
|
intermediateToken: secondHopOrder.takerToken,
|
||||||
hops: [firstHopFill.source, secondHopFill.source],
|
hops: [firstHopOrder.source, secondHopOrder.source],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -8,7 +8,6 @@ import {
|
|||||||
BELT_BSC_INFOS,
|
BELT_BSC_INFOS,
|
||||||
BISWAP_ROUTER_BY_CHAIN_ID,
|
BISWAP_ROUTER_BY_CHAIN_ID,
|
||||||
CHEESESWAP_ROUTER_BY_CHAIN_ID,
|
CHEESESWAP_ROUTER_BY_CHAIN_ID,
|
||||||
COMETHSWAP_ROUTER_BY_CHAIN_ID,
|
|
||||||
COMPONENT_POOLS_BY_CHAIN_ID,
|
COMPONENT_POOLS_BY_CHAIN_ID,
|
||||||
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
CRYPTO_COM_ROUTER_BY_CHAIN_ID,
|
||||||
CURVE_AVALANCHE_INFOS,
|
CURVE_AVALANCHE_INFOS,
|
||||||
@@ -42,8 +41,6 @@ import {
|
|||||||
SADDLE_MAINNET_INFOS,
|
SADDLE_MAINNET_INFOS,
|
||||||
SHELL_POOLS_BY_CHAIN_ID,
|
SHELL_POOLS_BY_CHAIN_ID,
|
||||||
SHIBASWAP_ROUTER_BY_CHAIN_ID,
|
SHIBASWAP_ROUTER_BY_CHAIN_ID,
|
||||||
SMOOTHY_BSC_INFOS,
|
|
||||||
SMOOTHY_MAINNET_INFOS,
|
|
||||||
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
SPIRITSWAP_ROUTER_BY_CHAIN_ID,
|
||||||
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
SPOOKYSWAP_ROUTER_BY_CHAIN_ID,
|
||||||
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
SUSHISWAP_ROUTER_BY_CHAIN_ID,
|
||||||
@@ -325,30 +322,6 @@ export function getEllipsisInfosForPair(chainId: ChainId, takerToken: string, ma
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSmoothyInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
|
||||||
if (chainId === ChainId.BSC) {
|
|
||||||
return Object.values(SMOOTHY_BSC_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (chainId === ChainId.Mainnet) {
|
|
||||||
return Object.values(SMOOTHY_MAINNET_INFOS).filter(c =>
|
|
||||||
[makerToken, takerToken].every(
|
|
||||||
t =>
|
|
||||||
(c.tokens.includes(t) && c.metaTokens === undefined) ||
|
|
||||||
(c.tokens.includes(t) &&
|
|
||||||
[makerToken, takerToken].filter(v => c.metaTokens?.includes(v)).length > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
export function getSaddleInfosForPair(chainId: ChainId, takerToken: string, makerToken: string): CurveInfo[] {
|
||||||
if (chainId !== ChainId.Mainnet) {
|
if (chainId !== ChainId.Mainnet) {
|
||||||
return [];
|
return [];
|
||||||
@@ -456,7 +429,6 @@ export function getCurveLikeInfosForPair(
|
|||||||
| ERC20BridgeSource.Synapse
|
| ERC20BridgeSource.Synapse
|
||||||
| ERC20BridgeSource.Belt
|
| ERC20BridgeSource.Belt
|
||||||
| ERC20BridgeSource.Ellipsis
|
| ERC20BridgeSource.Ellipsis
|
||||||
| ERC20BridgeSource.Smoothy
|
|
||||||
| ERC20BridgeSource.Saddle
|
| ERC20BridgeSource.Saddle
|
||||||
| ERC20BridgeSource.IronSwap
|
| ERC20BridgeSource.IronSwap
|
||||||
| ERC20BridgeSource.XSigma
|
| ERC20BridgeSource.XSigma
|
||||||
@@ -484,9 +456,6 @@ export function getCurveLikeInfosForPair(
|
|||||||
case ERC20BridgeSource.Ellipsis:
|
case ERC20BridgeSource.Ellipsis:
|
||||||
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
|
pools = getEllipsisInfosForPair(chainId, takerToken, makerToken);
|
||||||
break;
|
break;
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
pools = getSmoothyInfosForPair(chainId, takerToken, makerToken);
|
|
||||||
break;
|
|
||||||
case ERC20BridgeSource.Saddle:
|
case ERC20BridgeSource.Saddle:
|
||||||
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
|
pools = getSaddleInfosForPair(chainId, takerToken, makerToken);
|
||||||
break;
|
break;
|
||||||
@@ -527,7 +496,6 @@ export function uniswapV2LikeRouterAddress(
|
|||||||
| ERC20BridgeSource.ApeSwap
|
| ERC20BridgeSource.ApeSwap
|
||||||
| ERC20BridgeSource.CheeseSwap
|
| ERC20BridgeSource.CheeseSwap
|
||||||
| ERC20BridgeSource.QuickSwap
|
| ERC20BridgeSource.QuickSwap
|
||||||
| ERC20BridgeSource.ComethSwap
|
|
||||||
| ERC20BridgeSource.Dfyn
|
| ERC20BridgeSource.Dfyn
|
||||||
| ERC20BridgeSource.WaultSwap
|
| ERC20BridgeSource.WaultSwap
|
||||||
| ERC20BridgeSource.ShibaSwap
|
| ERC20BridgeSource.ShibaSwap
|
||||||
@@ -562,8 +530,6 @@ export function uniswapV2LikeRouterAddress(
|
|||||||
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
return CHEESESWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||||
case ERC20BridgeSource.QuickSwap:
|
case ERC20BridgeSource.QuickSwap:
|
||||||
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
return QUICKSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
return COMETHSWAP_ROUTER_BY_CHAIN_ID[chainId];
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
case ERC20BridgeSource.Dfyn:
|
||||||
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
|
return DFYN_ROUTER_BY_CHAIN_ID[chainId];
|
||||||
case ERC20BridgeSource.WaultSwap:
|
case ERC20BridgeSource.WaultSwap:
|
||||||
|
@@ -48,7 +48,7 @@ export function getComparisonPrices(
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const fillFeeInEth = new BigNumber(
|
const fillFeeInEth = new BigNumber(
|
||||||
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }),
|
(feeSchedule[ERC20BridgeSource.Native] as FeeEstimate)({ type: FillQuoteTransformerOrderType.Rfq }).fee,
|
||||||
);
|
);
|
||||||
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
const exchangeProxyOverheadInEth = new BigNumber(exchangeProxyOverhead(SOURCE_FLAGS.RfqOrder));
|
||||||
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
feeInEth = fillFeeInEth.plus(exchangeProxyOverheadInEth);
|
||||||
|
@@ -5,6 +5,7 @@ import { formatBytes32String } from '@ethersproject/strings';
|
|||||||
|
|
||||||
import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
|
import { TokenAdjacencyGraphBuilder } from '../token_adjacency_graph_builder';
|
||||||
|
|
||||||
|
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||||
import { SourceFilters } from './source_filters';
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
AaveV2FillData,
|
AaveV2FillData,
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
FeeSchedule,
|
FeeSchedule,
|
||||||
FillData,
|
FillData,
|
||||||
FinalUniswapV3FillData,
|
FinalUniswapV3FillData,
|
||||||
|
GasSchedule,
|
||||||
GeistFillData,
|
GeistFillData,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
isFinalUniswapV3FillData,
|
isFinalUniswapV3FillData,
|
||||||
@@ -98,7 +100,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
ERC20BridgeSource.Lido,
|
ERC20BridgeSource.Lido,
|
||||||
ERC20BridgeSource.MakerPsm,
|
ERC20BridgeSource.MakerPsm,
|
||||||
ERC20BridgeSource.KyberDmm,
|
ERC20BridgeSource.KyberDmm,
|
||||||
ERC20BridgeSource.Smoothy,
|
|
||||||
ERC20BridgeSource.Component,
|
ERC20BridgeSource.Component,
|
||||||
ERC20BridgeSource.Saddle,
|
ERC20BridgeSource.Saddle,
|
||||||
ERC20BridgeSource.XSigma,
|
ERC20BridgeSource.XSigma,
|
||||||
@@ -135,7 +136,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
ERC20BridgeSource.PancakeSwap,
|
ERC20BridgeSource.PancakeSwap,
|
||||||
ERC20BridgeSource.PancakeSwapV2,
|
ERC20BridgeSource.PancakeSwapV2,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.Smoothy,
|
|
||||||
ERC20BridgeSource.ApeSwap,
|
ERC20BridgeSource.ApeSwap,
|
||||||
ERC20BridgeSource.CheeseSwap,
|
ERC20BridgeSource.CheeseSwap,
|
||||||
ERC20BridgeSource.LiquidityProvider,
|
ERC20BridgeSource.LiquidityProvider,
|
||||||
@@ -150,7 +150,6 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
[ChainId.Polygon]: new SourceFilters([
|
[ChainId.Polygon]: new SourceFilters([
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.QuickSwap,
|
ERC20BridgeSource.QuickSwap,
|
||||||
ERC20BridgeSource.ComethSwap,
|
|
||||||
ERC20BridgeSource.Dfyn,
|
ERC20BridgeSource.Dfyn,
|
||||||
ERC20BridgeSource.MStable,
|
ERC20BridgeSource.MStable,
|
||||||
ERC20BridgeSource.Curve,
|
ERC20BridgeSource.Curve,
|
||||||
@@ -241,7 +240,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
ERC20BridgeSource.CryptoCom,
|
ERC20BridgeSource.CryptoCom,
|
||||||
ERC20BridgeSource.MakerPsm,
|
ERC20BridgeSource.MakerPsm,
|
||||||
ERC20BridgeSource.KyberDmm,
|
ERC20BridgeSource.KyberDmm,
|
||||||
ERC20BridgeSource.Smoothy,
|
|
||||||
ERC20BridgeSource.Component,
|
ERC20BridgeSource.Component,
|
||||||
ERC20BridgeSource.Saddle,
|
ERC20BridgeSource.Saddle,
|
||||||
ERC20BridgeSource.XSigma,
|
ERC20BridgeSource.XSigma,
|
||||||
@@ -278,7 +276,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
ERC20BridgeSource.PancakeSwap,
|
ERC20BridgeSource.PancakeSwap,
|
||||||
ERC20BridgeSource.PancakeSwapV2,
|
ERC20BridgeSource.PancakeSwapV2,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.Smoothy,
|
|
||||||
ERC20BridgeSource.ApeSwap,
|
ERC20BridgeSource.ApeSwap,
|
||||||
ERC20BridgeSource.CheeseSwap,
|
ERC20BridgeSource.CheeseSwap,
|
||||||
ERC20BridgeSource.LiquidityProvider,
|
ERC20BridgeSource.LiquidityProvider,
|
||||||
@@ -293,7 +290,6 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
|||||||
[ChainId.Polygon]: new SourceFilters([
|
[ChainId.Polygon]: new SourceFilters([
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.QuickSwap,
|
ERC20BridgeSource.QuickSwap,
|
||||||
ERC20BridgeSource.ComethSwap,
|
|
||||||
ERC20BridgeSource.Dfyn,
|
ERC20BridgeSource.Dfyn,
|
||||||
ERC20BridgeSource.MStable,
|
ERC20BridgeSource.MStable,
|
||||||
ERC20BridgeSource.Curve,
|
ERC20BridgeSource.Curve,
|
||||||
@@ -744,10 +740,6 @@ export const CURVE_OPTIMISM_POOLS = {
|
|||||||
tri: '0x1337bedc9d22ecbe766df105c9623922a27963ec',
|
tri: '0x1337bedc9d22ecbe766df105c9623922a27963ec',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SMOOTHY_POOLS = {
|
|
||||||
syUSD: '0xe5859f4efc09027a9b718781dcb2c6910cac6e91',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SADDLE_POOLS = {
|
export const SADDLE_POOLS = {
|
||||||
stablesV2: '0xaCb83E0633d6605c5001e2Ab59EF3C745547C8C7',
|
stablesV2: '0xaCb83E0633d6605c5001e2Ab59EF3C745547C8C7',
|
||||||
bitcoinsV2: '0xdf3309771d2BF82cb2B6C56F9f5365C8bD97c4f2',
|
bitcoinsV2: '0xdf3309771d2BF82cb2B6C56F9f5365C8bD97c4f2',
|
||||||
@@ -1589,39 +1581,6 @@ export const IRONSWAP_POLYGON_INFOS: { [name: string]: CurveInfo } = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SMOOTHY_MAINNET_INFOS: { [name: string]: CurveInfo } = {
|
|
||||||
[SMOOTHY_POOLS.syUSD]: {
|
|
||||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
|
||||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
|
||||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
|
||||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
|
||||||
tokens: [
|
|
||||||
MAINNET_TOKENS.USDT,
|
|
||||||
MAINNET_TOKENS.USDC,
|
|
||||||
MAINNET_TOKENS.DAI,
|
|
||||||
MAINNET_TOKENS.TUSD,
|
|
||||||
MAINNET_TOKENS.sUSD,
|
|
||||||
MAINNET_TOKENS.BUSD,
|
|
||||||
MAINNET_TOKENS.PAX,
|
|
||||||
MAINNET_TOKENS.GUSD,
|
|
||||||
],
|
|
||||||
metaTokens: undefined,
|
|
||||||
gasSchedule: 190e3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SMOOTHY_BSC_INFOS: { [name: string]: CurveInfo } = {
|
|
||||||
[SMOOTHY_POOLS.syUSD]: {
|
|
||||||
exchangeFunctionSelector: CurveFunctionSelectors.swap_uint256,
|
|
||||||
sellQuoteFunctionSelector: CurveFunctionSelectors.get_swap_amount,
|
|
||||||
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
|
|
||||||
poolAddress: SMOOTHY_POOLS.syUSD,
|
|
||||||
tokens: [BSC_TOKENS.BUSD, BSC_TOKENS.USDT, BSC_TOKENS.USDC, BSC_TOKENS.DAI, BSC_TOKENS.PAX, BSC_TOKENS.UST],
|
|
||||||
metaTokens: undefined,
|
|
||||||
gasSchedule: 90e3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
|
export const NERVE_BSC_INFOS: { [name: string]: CurveInfo } = {
|
||||||
[NERVE_POOLS.threePool]: {
|
[NERVE_POOLS.threePool]: {
|
||||||
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
exchangeFunctionSelector: CurveFunctionSelectors.swap,
|
||||||
@@ -2276,13 +2235,6 @@ export const QUICKSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
|||||||
NULL_ADDRESS,
|
NULL_ADDRESS,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const COMETHSWAP_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
|
||||||
{
|
|
||||||
[ChainId.Polygon]: '0x93bcdc45f7e62f89a8e901dc4a0e2c6c427d9f25',
|
|
||||||
},
|
|
||||||
NULL_ADDRESS,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DFYN_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
export const DFYN_ROUTER_BY_CHAIN_ID = valueByChainId<string>(
|
||||||
{
|
{
|
||||||
[ChainId.Polygon]: '0xa102072a4c07f06ec3b4900fdc4c7b80b6c57429',
|
[ChainId.Polygon]: '0xa102072a4c07f06ec3b4900fdc4c7b80b6c57429',
|
||||||
@@ -2431,7 +2383,7 @@ const uniswapV2CloneGasSchedule = (fillData?: FillData) => {
|
|||||||
* the ethereum transaction cost (21k)
|
* the ethereum transaction cost (21k)
|
||||||
*/
|
*/
|
||||||
// tslint:disable:custom-no-magic-numbers
|
// tslint:disable:custom-no-magic-numbers
|
||||||
export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
export const DEFAULT_GAS_SCHEDULE: Required<GasSchedule> = {
|
||||||
[ERC20BridgeSource.Native]: fillData => {
|
[ERC20BridgeSource.Native]: fillData => {
|
||||||
// TODO jacob re-order imports so there is no circular rependency with SignedNativeOrder
|
// TODO jacob re-order imports so there is no circular rependency with SignedNativeOrder
|
||||||
const nativeFillData = fillData as { type: FillQuoteTransformerOrderType };
|
const nativeFillData = fillData as { type: FillQuoteTransformerOrderType };
|
||||||
@@ -2450,7 +2402,6 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
|||||||
[ERC20BridgeSource.Synapse]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.Synapse]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
[ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.Belt]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
[ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.Ellipsis]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
[ERC20BridgeSource.Smoothy]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
|
||||||
[ERC20BridgeSource.Saddle]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.Saddle]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
[ERC20BridgeSource.IronSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.IronSwap]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
[ERC20BridgeSource.XSigma]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
[ERC20BridgeSource.XSigma]: fillData => (fillData as CurveFillData).pool.gasSchedule,
|
||||||
@@ -2589,7 +2540,6 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
|||||||
// Polygon
|
// Polygon
|
||||||
//
|
//
|
||||||
[ERC20BridgeSource.QuickSwap]: uniswapV2CloneGasSchedule,
|
[ERC20BridgeSource.QuickSwap]: uniswapV2CloneGasSchedule,
|
||||||
[ERC20BridgeSource.ComethSwap]: uniswapV2CloneGasSchedule,
|
|
||||||
[ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule,
|
[ERC20BridgeSource.Dfyn]: uniswapV2CloneGasSchedule,
|
||||||
[ERC20BridgeSource.MeshSwap]: uniswapV2CloneGasSchedule,
|
[ERC20BridgeSource.MeshSwap]: uniswapV2CloneGasSchedule,
|
||||||
|
|
||||||
@@ -2621,10 +2571,21 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
|
|||||||
[ERC20BridgeSource.Velodrome]: () => 160e3,
|
[ERC20BridgeSource.Velodrome]: () => 160e3,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = { ...DEFAULT_GAS_SCHEDULE };
|
export const DEFAULT_FEE_SCHEDULE: Required<FeeSchedule> = Object.keys(DEFAULT_GAS_SCHEDULE).reduce((acc, key) => {
|
||||||
|
acc[key as ERC20BridgeSource] = (fillData: FillData) => {
|
||||||
|
return {
|
||||||
|
gas: DEFAULT_GAS_SCHEDULE[key as ERC20BridgeSource](fillData),
|
||||||
|
fee: ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||||
|
}, {} as Required<FeeSchedule>);
|
||||||
|
|
||||||
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(20000);
|
||||||
|
|
||||||
|
export const DEFAULT_FEE_ESTIMATE = { gas: 0, fee: ZERO_AMOUNT };
|
||||||
|
|
||||||
// tslint:enable:custom-no-magic-numbers
|
// tslint:enable:custom-no-magic-numbers
|
||||||
|
|
||||||
export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice'> = {
|
export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice'> = {
|
||||||
@@ -2645,4 +2606,5 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: Omit<GetMarketOrdersOpts, 'gasPrice
|
|||||||
shouldIncludePriceComparisonsReport: false,
|
shouldIncludePriceComparisonsReport: false,
|
||||||
tokenAdjacencyGraph: { default: [] },
|
tokenAdjacencyGraph: { default: [] },
|
||||||
neonRouterNumSamples: 14,
|
neonRouterNumSamples: 14,
|
||||||
|
fillAdjustor: new IdentityFillAdjustor(),
|
||||||
};
|
};
|
||||||
|
@@ -3,74 +3,17 @@ import { BigNumber, hexUtils } from '@0x/utils';
|
|||||||
|
|
||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||||
|
|
||||||
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
import { DEFAULT_FEE_ESTIMATE, POSITIVE_INF, SOURCE_FLAGS } from './constants';
|
||||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill } from './types';
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create `Fill` objects from orders and dex quotes.
|
* Converts the ETH value to an amount in output tokens.
|
||||||
|
*
|
||||||
|
* By default this prefers the outputAmountPerEth, but if this value
|
||||||
|
* is zero it will utilize the inputAmountPerEth and input.
|
||||||
*/
|
*/
|
||||||
export function createFills(opts: {
|
|
||||||
side: MarketOperation;
|
|
||||||
orders?: NativeOrderWithFillableAmounts[];
|
|
||||||
dexQuotes?: DexSample[][];
|
|
||||||
targetInput?: BigNumber;
|
|
||||||
outputAmountPerEth?: BigNumber;
|
|
||||||
inputAmountPerEth?: BigNumber;
|
|
||||||
excludedSources?: ERC20BridgeSource[];
|
|
||||||
feeSchedule?: FeeSchedule;
|
|
||||||
}): Fill[][] {
|
|
||||||
const { side } = opts;
|
|
||||||
const excludedSources = opts.excludedSources || [];
|
|
||||||
const feeSchedule = opts.feeSchedule || {};
|
|
||||||
const orders = opts.orders || [];
|
|
||||||
const dexQuotes = opts.dexQuotes || [];
|
|
||||||
const outputAmountPerEth = opts.outputAmountPerEth || ZERO_AMOUNT;
|
|
||||||
const inputAmountPerEth = opts.inputAmountPerEth || ZERO_AMOUNT;
|
|
||||||
// Create native fills.
|
|
||||||
const nativeFills = nativeOrdersToFills(
|
|
||||||
side,
|
|
||||||
orders.filter(o => o.fillableTakerAmount.isGreaterThan(0)),
|
|
||||||
opts.targetInput,
|
|
||||||
outputAmountPerEth,
|
|
||||||
inputAmountPerEth,
|
|
||||||
feeSchedule,
|
|
||||||
);
|
|
||||||
// Create DEX fills.
|
|
||||||
const dexFills = dexQuotes.map(singleSourceSamples =>
|
|
||||||
dexSamplesToFills(side, singleSourceSamples, outputAmountPerEth, inputAmountPerEth, feeSchedule),
|
|
||||||
);
|
|
||||||
return [...dexFills, nativeFills]
|
|
||||||
.map(p => clipFillsToInput(p, opts.targetInput))
|
|
||||||
.filter(fills => hasLiquidity(fills) && !excludedSources.includes(fills[0].source));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clipFillsToInput(fills: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {
|
|
||||||
const clipped: Fill[] = [];
|
|
||||||
let input = ZERO_AMOUNT;
|
|
||||||
for (const fill of fills) {
|
|
||||||
if (input.gte(targetInput)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input = input.plus(fill.input);
|
|
||||||
clipped.push(fill);
|
|
||||||
}
|
|
||||||
return clipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasLiquidity(fills: Fill[]): boolean {
|
|
||||||
if (fills.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const totalInput = BigNumber.sum(...fills.map(fill => fill.input));
|
|
||||||
const totalOutput = BigNumber.sum(...fills.map(fill => fill.output));
|
|
||||||
if (totalInput.isZero() || totalOutput.isZero()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ethToOutputAmount({
|
export function ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -85,29 +28,28 @@ export function ethToOutputAmount({
|
|||||||
ethAmount: BigNumber | number;
|
ethAmount: BigNumber | number;
|
||||||
}): BigNumber {
|
}): BigNumber {
|
||||||
return !outputAmountPerEth.isZero()
|
return !outputAmountPerEth.isZero()
|
||||||
? outputAmountPerEth.times(ethAmount)
|
? outputAmountPerEth.times(ethAmount).integerValue()
|
||||||
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
|
: inputAmountPerEth.times(ethAmount).times(output.dividedToIntegerBy(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nativeOrdersToFills(
|
export function nativeOrderToFill(
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
orders: NativeOrderWithFillableAmounts[],
|
order: NativeOrderWithFillableAmounts,
|
||||||
targetInput: BigNumber = POSITIVE_INF,
|
targetInput: BigNumber = POSITIVE_INF,
|
||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
inputAmountPerEth: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
filterNegativeAdjustedRateOrders: boolean = true,
|
filterNegativeAdjustedRateOrders: boolean = true,
|
||||||
): Fill[] {
|
): Fill | undefined {
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
// Create a single path from all orders.
|
// Create a single path from all orders.
|
||||||
let fills: Array<Fill & { adjustedRate: BigNumber }> = [];
|
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = order;
|
||||||
for (const o of orders) {
|
|
||||||
const { fillableTakerAmount, fillableTakerFeeAmount, fillableMakerAmount, type } = o;
|
|
||||||
const makerAmount = fillableMakerAmount;
|
const makerAmount = fillableMakerAmount;
|
||||||
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
const takerAmount = fillableTakerAmount.plus(fillableTakerFeeAmount);
|
||||||
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
const input = side === MarketOperation.Sell ? takerAmount : makerAmount;
|
||||||
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
const output = side === MarketOperation.Sell ? makerAmount : takerAmount;
|
||||||
const fee = fees[ERC20BridgeSource.Native] === undefined ? 0 : fees[ERC20BridgeSource.Native]!(o);
|
const { fee, gas } =
|
||||||
|
fees[ERC20BridgeSource.Native] === undefined ? DEFAULT_FEE_ESTIMATE : fees[ERC20BridgeSource.Native]!(order);
|
||||||
const outputPenalty = ethToOutputAmount({
|
const outputPenalty = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -129,78 +71,63 @@ export function nativeOrdersToFills(
|
|||||||
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
side === MarketOperation.Sell ? adjustedOutput.div(clippedInput) : clippedInput.div(adjustedOutput);
|
||||||
// Optionally skip orders with rates that are <= 0.
|
// Optionally skip orders with rates that are <= 0.
|
||||||
if (filterNegativeAdjustedRateOrders && adjustedRate.lte(0)) {
|
if (filterNegativeAdjustedRateOrders && adjustedRate.lte(0)) {
|
||||||
continue;
|
return undefined;
|
||||||
}
|
}
|
||||||
fills.push({
|
|
||||||
|
return {
|
||||||
sourcePathId,
|
sourcePathId,
|
||||||
adjustedRate,
|
|
||||||
adjustedOutput,
|
adjustedOutput,
|
||||||
input: clippedInput,
|
input: clippedInput,
|
||||||
output: clippedOutput,
|
output: clippedOutput,
|
||||||
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
flags: SOURCE_FLAGS[type === FillQuoteTransformerOrderType.Rfq ? 'RfqOrder' : 'LimitOrder'],
|
||||||
index: 0, // TBD
|
|
||||||
parent: undefined, // TBD
|
|
||||||
source: ERC20BridgeSource.Native,
|
source: ERC20BridgeSource.Native,
|
||||||
type,
|
type,
|
||||||
fillData: { ...o },
|
fillData: { ...order },
|
||||||
});
|
gas,
|
||||||
}
|
};
|
||||||
// Sort by descending adjusted rate.
|
|
||||||
fills = fills.sort((a, b) => b.adjustedRate.comparedTo(a.adjustedRate));
|
|
||||||
// Re-index fills.
|
|
||||||
for (let i = 0; i < fills.length; ++i) {
|
|
||||||
fills[i].parent = i === 0 ? undefined : fills[i - 1];
|
|
||||||
fills[i].index = i;
|
|
||||||
}
|
|
||||||
return fills;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dexSamplesToFills(
|
export function dexSampleToFill(
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
samples: DexSample[],
|
sample: DexSample,
|
||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
inputAmountPerEth: BigNumber,
|
inputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
): Fill[] {
|
): Fill {
|
||||||
const sourcePathId = hexUtils.random();
|
const sourcePathId = hexUtils.random();
|
||||||
const fills: Fill[] = [];
|
|
||||||
// Drop any non-zero entries. This can occur if the any fills on Kyber were UniswapReserves
|
|
||||||
// We need not worry about Kyber fills going to UniswapReserve as the input amount
|
|
||||||
// we fill is the same as we sampled. I.e we received [0,20,30] output from [1,2,3] input
|
|
||||||
// and we only fill [2,3] on Kyber (as 1 returns 0 output)
|
|
||||||
const nonzeroSamples = samples.filter(q => !q.output.isZero());
|
|
||||||
for (let i = 0; i < nonzeroSamples.length; i++) {
|
|
||||||
const sample = nonzeroSamples[i];
|
|
||||||
const prevSample = i === 0 ? undefined : nonzeroSamples[i - 1];
|
|
||||||
const { source, fillData } = sample;
|
const { source, fillData } = sample;
|
||||||
const input = sample.input.minus(prevSample ? prevSample.input : 0);
|
const input = sample.input;
|
||||||
const output = sample.output.minus(prevSample ? prevSample.output : 0);
|
const output = sample.output;
|
||||||
let penalty = ZERO_AMOUNT;
|
const { fee, gas } =
|
||||||
if (i === 0) {
|
fees[source] === undefined ? DEFAULT_FEE_ESTIMATE : fees[source]!(sample.fillData) || DEFAULT_FEE_ESTIMATE;
|
||||||
const fee = fees[source] === undefined ? 0 : fees[source]!(sample.fillData) || 0;
|
|
||||||
// Only the first fill in a DEX path incurs a penalty.
|
const penalty = ethToOutputAmount({
|
||||||
penalty = ethToOutputAmount({
|
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
inputAmountPerEth,
|
inputAmountPerEth,
|
||||||
outputAmountPerEth,
|
outputAmountPerEth,
|
||||||
ethAmount: fee,
|
ethAmount: fee,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
|
||||||
|
|
||||||
fills.push({
|
return {
|
||||||
sourcePathId,
|
sourcePathId,
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
adjustedOutput,
|
adjustedOutput: adjustOutput(side, output, penalty),
|
||||||
source,
|
source,
|
||||||
fillData,
|
fillData,
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
index: i,
|
|
||||||
parent: i !== 0 ? fills[fills.length - 1] : undefined,
|
|
||||||
flags: SOURCE_FLAGS[source],
|
flags: SOURCE_FLAGS[source],
|
||||||
});
|
gas,
|
||||||
}
|
};
|
||||||
return fills;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the output depending on whether this is a buy or a sell.
|
||||||
|
*
|
||||||
|
* If it is a sell, than output is lowered by the adjustment.
|
||||||
|
* If it is a buy, than output is increased by adjustment.
|
||||||
|
*/
|
||||||
|
export function adjustOutput(side: MarketOperation, output: BigNumber, penalty: BigNumber): BigNumber {
|
||||||
|
return side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
|
import { Fill, FillAdjustor } from './types';
|
||||||
|
|
||||||
|
// tslint:disable:prefer-function-over-method
|
||||||
|
|
||||||
|
export class IdentityFillAdjustor implements FillAdjustor {
|
||||||
|
public adjustFills(side: MarketOperation, fills: Fill[], amount: BigNumber): Fill[] {
|
||||||
|
return fills;
|
||||||
|
}
|
||||||
|
}
|
@@ -41,19 +41,17 @@ import {
|
|||||||
SOURCE_FLAGS,
|
SOURCE_FLAGS,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { createFills } from './fills';
|
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||||
import { getBestTwoHopQuote } from './multihop_utils';
|
import { getBestTwoHopQuote } from './multihop_utils';
|
||||||
import { createOrdersFromTwoHopSample } from './orders';
|
import { createOrdersFromTwoHopSample } from './orders';
|
||||||
import { Path, PathPenaltyOpts } from './path';
|
import { Path, PathPenaltyOpts } from './path';
|
||||||
import { findOptimalPathJSAsync, findOptimalRustPathFromSamples } from './path_optimizer';
|
import { findOptimalPathFromSamples } from './path_optimizer';
|
||||||
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
import { DexOrderSampler, getSampleAmounts } from './sampler';
|
||||||
import { SourceFilters } from './source_filters';
|
import { SourceFilters } from './source_filters';
|
||||||
import {
|
import {
|
||||||
AggregationError,
|
AggregationError,
|
||||||
CollapsedFill,
|
|
||||||
DexSample,
|
DexSample,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
Fill,
|
|
||||||
GenerateOptimizedOrdersOpts,
|
GenerateOptimizedOrdersOpts,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
MarketSideLiquidity,
|
MarketSideLiquidity,
|
||||||
@@ -62,8 +60,6 @@ import {
|
|||||||
OrderDomain,
|
OrderDomain,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const SHOULD_USE_RUST_ROUTER = process.env.RUST_ROUTER === 'true';
|
|
||||||
|
|
||||||
// tslint:disable:boolean-naming
|
// tslint:disable:boolean-naming
|
||||||
|
|
||||||
export class MarketOperationUtils {
|
export class MarketOperationUtils {
|
||||||
@@ -167,18 +163,20 @@ export class MarketOperationUtils {
|
|||||||
// Get native order fillable amounts.
|
// Get native order fillable amounts.
|
||||||
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||||
// Get ETH -> maker token price.
|
// Get ETH -> maker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getBestNativeTokenSellRate(
|
||||||
feeSourceFilters.sources,
|
feeSourceFilters.sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
this._nativeFeeToken,
|
this._nativeFeeToken,
|
||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
|
_opts.feeSchedule,
|
||||||
),
|
),
|
||||||
// Get ETH -> taker token price.
|
// Get ETH -> taker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getBestNativeTokenSellRate(
|
||||||
feeSourceFilters.sources,
|
feeSourceFilters.sources,
|
||||||
takerToken,
|
takerToken,
|
||||||
this._nativeFeeToken,
|
this._nativeFeeToken,
|
||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
|
_opts.feeSchedule,
|
||||||
),
|
),
|
||||||
// Get sell quotes for taker -> maker.
|
// Get sell quotes for taker -> maker.
|
||||||
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||||
@@ -278,18 +276,20 @@ export class MarketOperationUtils {
|
|||||||
// Get native order fillable amounts.
|
// Get native order fillable amounts.
|
||||||
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
this._sampler.getLimitOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
|
||||||
// Get ETH -> makerToken token price.
|
// Get ETH -> makerToken token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getBestNativeTokenSellRate(
|
||||||
feeSourceFilters.sources,
|
feeSourceFilters.sources,
|
||||||
makerToken,
|
makerToken,
|
||||||
this._nativeFeeToken,
|
this._nativeFeeToken,
|
||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
|
_opts.feeSchedule,
|
||||||
),
|
),
|
||||||
// Get ETH -> taker token price.
|
// Get ETH -> taker token price.
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getBestNativeTokenSellRate(
|
||||||
feeSourceFilters.sources,
|
feeSourceFilters.sources,
|
||||||
takerToken,
|
takerToken,
|
||||||
this._nativeFeeToken,
|
this._nativeFeeToken,
|
||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
|
_opts.feeSchedule,
|
||||||
),
|
),
|
||||||
// Get buy quotes for taker -> maker.
|
// Get buy quotes for taker -> maker.
|
||||||
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
this._sampler.getBuyQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
|
||||||
@@ -384,11 +384,12 @@ export class MarketOperationUtils {
|
|||||||
this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy),
|
this._sampler.getLimitOrderFillableMakerAmounts(orders, this.contractAddresses.exchangeProxy),
|
||||||
),
|
),
|
||||||
...batchNativeOrders.map(orders =>
|
...batchNativeOrders.map(orders =>
|
||||||
this._sampler.getMedianSellRate(
|
this._sampler.getBestNativeTokenSellRate(
|
||||||
feeSourceFilters.sources,
|
feeSourceFilters.sources,
|
||||||
orders[0].order.takerToken,
|
orders[0].order.takerToken,
|
||||||
this._nativeFeeToken,
|
this._nativeFeeToken,
|
||||||
this._nativeFeeTokenAmount,
|
this._nativeFeeTokenAmount,
|
||||||
|
_opts.feeSchedule,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...batchNativeOrders.map((orders, i) =>
|
...batchNativeOrders.map((orders, i) =>
|
||||||
@@ -455,6 +456,7 @@ export class MarketOperationUtils {
|
|||||||
allowFallback: _opts.allowFallback,
|
allowFallback: _opts.allowFallback,
|
||||||
gasPrice: _opts.gasPrice,
|
gasPrice: _opts.gasPrice,
|
||||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||||
|
fillAdjustor: _opts.fillAdjustor,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return optimizerResult;
|
return optimizerResult;
|
||||||
@@ -516,12 +518,9 @@ export class MarketOperationUtils {
|
|||||||
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
const takerAmountPerEth = side === MarketOperation.Sell ? inputAmountPerEth : outputAmountPerEth;
|
||||||
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
|
const makerAmountPerEth = side === MarketOperation.Sell ? outputAmountPerEth : inputAmountPerEth;
|
||||||
|
|
||||||
let fills: Fill[][];
|
|
||||||
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
|
// Find the optimal path using Rust router if enabled, otherwise fallback to JS Router
|
||||||
let optimalPath: Path | undefined;
|
let optimalPath: Path | undefined;
|
||||||
if (SHOULD_USE_RUST_ROUTER) {
|
optimalPath = findOptimalPathFromSamples(
|
||||||
fills = [[]];
|
|
||||||
optimalPath = findOptimalRustPathFromSamples(
|
|
||||||
side,
|
side,
|
||||||
dexQuotes,
|
dexQuotes,
|
||||||
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
[...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
||||||
@@ -530,46 +529,27 @@ export class MarketOperationUtils {
|
|||||||
opts.feeSchedule,
|
opts.feeSchedule,
|
||||||
this._sampler.chainId,
|
this._sampler.chainId,
|
||||||
opts.neonRouterNumSamples,
|
opts.neonRouterNumSamples,
|
||||||
|
opts.fillAdjustor,
|
||||||
opts.samplerMetrics,
|
opts.samplerMetrics,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Convert native orders and dex quotes into `Fill` objects.
|
|
||||||
fills = createFills({
|
|
||||||
side,
|
|
||||||
orders: [...nativeOrders, ...augmentedRfqtIndicativeQuotes],
|
|
||||||
dexQuotes,
|
|
||||||
targetInput: inputAmount,
|
|
||||||
outputAmountPerEth,
|
|
||||||
inputAmountPerEth,
|
|
||||||
excludedSources: opts.excludedSources,
|
|
||||||
feeSchedule: opts.feeSchedule,
|
|
||||||
});
|
|
||||||
|
|
||||||
optimalPath = await findOptimalPathJSAsync(
|
const optimalPathAdjustedRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
||||||
side,
|
|
||||||
fills,
|
|
||||||
inputAmount,
|
|
||||||
opts.runLimit,
|
|
||||||
opts.samplerMetrics,
|
|
||||||
penaltyOpts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
const { adjustedRate: bestTwoHopAdjustedRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
||||||
|
|
||||||
const { adjustedRate: bestTwoHopRate, quote: bestTwoHopQuote } = getBestTwoHopQuote(
|
|
||||||
marketSideLiquidity,
|
marketSideLiquidity,
|
||||||
opts.feeSchedule,
|
opts.feeSchedule,
|
||||||
opts.exchangeProxyOverhead,
|
opts.exchangeProxyOverhead,
|
||||||
|
opts.fillAdjustor,
|
||||||
);
|
);
|
||||||
if (bestTwoHopQuote && bestTwoHopRate.isGreaterThan(optimalPathRate)) {
|
|
||||||
|
if (bestTwoHopQuote && bestTwoHopAdjustedRate.isGreaterThan(optimalPathAdjustedRate)) {
|
||||||
const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts);
|
const twoHopOrders = createOrdersFromTwoHopSample(bestTwoHopQuote, orderOpts);
|
||||||
return {
|
return {
|
||||||
optimizedOrders: twoHopOrders,
|
optimizedOrders: twoHopOrders,
|
||||||
liquidityDelivered: bestTwoHopQuote,
|
liquidityDelivered: bestTwoHopQuote,
|
||||||
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
|
||||||
marketSideLiquidity,
|
marketSideLiquidity,
|
||||||
adjustedRate: bestTwoHopRate,
|
adjustedRate: bestTwoHopAdjustedRate,
|
||||||
takerAmountPerEth,
|
takerAmountPerEth,
|
||||||
makerAmountPerEth,
|
makerAmountPerEth,
|
||||||
};
|
};
|
||||||
@@ -580,19 +560,14 @@ export class MarketOperationUtils {
|
|||||||
throw new Error(AggregationError.NoOptimalPath);
|
throw new Error(AggregationError.NoOptimalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a fallback path if required
|
const finalizedPath = optimalPath.finalize(orderOpts);
|
||||||
// TODO(kimpers): Will experiment with disabling this and see how it affects revert rate
|
|
||||||
// to avoid yet another router roundtrip
|
|
||||||
// TODO: clean this up if we don't need it
|
|
||||||
// await this._addOptionalFallbackAsync(side, inputAmount, optimalPath, dexQuotes, fills, opts, penaltyOpts);
|
|
||||||
const collapsedPath = optimalPath.collapse(orderOpts);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
optimizedOrders: collapsedPath.orders,
|
optimizedOrders: finalizedPath.orders,
|
||||||
liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
|
liquidityDelivered: finalizedPath.fills,
|
||||||
sourceFlags: collapsedPath.sourceFlags,
|
sourceFlags: finalizedPath.sourceFlags,
|
||||||
marketSideLiquidity,
|
marketSideLiquidity,
|
||||||
adjustedRate: optimalPathRate,
|
adjustedRate: optimalPathAdjustedRate,
|
||||||
takerAmountPerEth,
|
takerAmountPerEth,
|
||||||
makerAmountPerEth,
|
makerAmountPerEth,
|
||||||
};
|
};
|
||||||
@@ -618,6 +593,7 @@ export class MarketOperationUtils {
|
|||||||
gasPrice: _opts.gasPrice,
|
gasPrice: _opts.gasPrice,
|
||||||
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
neonRouterNumSamples: _opts.neonRouterNumSamples,
|
||||||
samplerMetrics: _opts.samplerMetrics,
|
samplerMetrics: _opts.samplerMetrics,
|
||||||
|
fillAdjustor: _opts.fillAdjustor,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (nativeOrders.length === 0) {
|
if (nativeOrders.length === 0) {
|
||||||
@@ -630,9 +606,15 @@ export class MarketOperationUtils {
|
|||||||
? this.getMarketSellLiquidityAsync.bind(this)
|
? this.getMarketSellLiquidityAsync.bind(this)
|
||||||
: this.getMarketBuyLiquidityAsync.bind(this);
|
: this.getMarketBuyLiquidityAsync.bind(this);
|
||||||
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
|
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
|
||||||
|
|
||||||
|
// Phase 1 Routing
|
||||||
|
// We find an optimized path for ALL the DEX and open-orderbook liquidity
|
||||||
let optimizerResult: OptimizerResult | undefined;
|
let optimizerResult: OptimizerResult | undefined;
|
||||||
try {
|
try {
|
||||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, {
|
||||||
|
...optimizerOpts,
|
||||||
|
fillAdjustor: new IdentityFillAdjustor(),
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If no on-chain or off-chain Open Orderbook orders are present, a `NoOptimalPath` will be thrown.
|
// If no on-chain or off-chain Open Orderbook orders are present, a `NoOptimalPath` will be thrown.
|
||||||
// If this happens at this stage, there is still a chance that an RFQ order is fillable, therefore
|
// If this happens at this stage, there is still a chance that an RFQ order is fillable, therefore
|
||||||
@@ -656,6 +638,17 @@ export class MarketOperationUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result
|
// If RFQ liquidity is enabled, make a request to check RFQ liquidity against the first optimizer result
|
||||||
|
|
||||||
|
// Phase 2 Routing
|
||||||
|
// Mix in any off-chain RFQ quotes
|
||||||
|
// Apply any fill adjustments i
|
||||||
|
const phaseTwoOptimizerOpts = {
|
||||||
|
...optimizerOpts,
|
||||||
|
// Pass in the FillAdjustor for Phase 2 adjustment, in the future we may perform this adjustment
|
||||||
|
// in Phase 1.
|
||||||
|
fillAdjustor: _opts.fillAdjustor,
|
||||||
|
};
|
||||||
|
|
||||||
const { rfqt } = _opts;
|
const { rfqt } = _opts;
|
||||||
if (
|
if (
|
||||||
marketSideLiquidity.isRfqSupported &&
|
marketSideLiquidity.isRfqSupported &&
|
||||||
@@ -716,8 +709,28 @@ export class MarketOperationUtils {
|
|||||||
});
|
});
|
||||||
// Re-run optimizer with the new indicative quote
|
// Re-run optimizer with the new indicative quote
|
||||||
if (indicativeQuotes.length > 0) {
|
if (indicativeQuotes.length > 0) {
|
||||||
|
// Attach the indicative quotes to the market side liquidity
|
||||||
marketSideLiquidity.quotes.rfqtIndicativeQuotes = indicativeQuotes;
|
marketSideLiquidity.quotes.rfqtIndicativeQuotes = indicativeQuotes;
|
||||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
|
||||||
|
// Phase 2 Routing
|
||||||
|
const phase1OptimalSources = optimizerResult
|
||||||
|
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||||
|
: [];
|
||||||
|
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||||
|
...marketSideLiquidity,
|
||||||
|
quotes: {
|
||||||
|
...marketSideLiquidity.quotes,
|
||||||
|
// Select only the quotes that were chosen in Phase 1
|
||||||
|
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||||
|
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||||
|
phase2MarketSideLiquidity,
|
||||||
|
phaseTwoOptimizerOpts,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// A firm quote is being requested, and firm quotes price-aware enabled.
|
// A firm quote is being requested, and firm quotes price-aware enabled.
|
||||||
@@ -775,6 +788,8 @@ export class MarketOperationUtils {
|
|||||||
fillableTakerFeeAmount: ZERO_AMOUNT,
|
fillableTakerFeeAmount: ZERO_AMOUNT,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Attach the firm RFQt quotes to the market side liquidity
|
||||||
marketSideLiquidity.quotes.nativeOrders = [
|
marketSideLiquidity.quotes.nativeOrders = [
|
||||||
...quotesWithOrderFillableAmounts,
|
...quotesWithOrderFillableAmounts,
|
||||||
...marketSideLiquidity.quotes.nativeOrders,
|
...marketSideLiquidity.quotes.nativeOrders,
|
||||||
@@ -783,7 +798,27 @@ export class MarketOperationUtils {
|
|||||||
// Re-run optimizer with the new firm quote. This is the second and last time
|
// Re-run optimizer with the new firm quote. This is the second and last time
|
||||||
// we run the optimized in a block of code. In this case, we don't catch a potential `NoOptimalPath` exception
|
// we run the optimized in a block of code. In this case, we don't catch a potential `NoOptimalPath` exception
|
||||||
// and we let it bubble up if it happens.
|
// and we let it bubble up if it happens.
|
||||||
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);
|
|
||||||
|
// Phase 2 Routing
|
||||||
|
// Optimization: Filter by what is already currently in the Phase1 output as it doesn't
|
||||||
|
// seem possible that inclusion of RFQT could impact the sources chosen from Phase 1.
|
||||||
|
const phase1OptimalSources = optimizerResult
|
||||||
|
? optimizerResult.optimizedOrders.map(o => o.source)
|
||||||
|
: [];
|
||||||
|
const phase2MarketSideLiquidity: MarketSideLiquidity = {
|
||||||
|
...marketSideLiquidity,
|
||||||
|
quotes: {
|
||||||
|
...marketSideLiquidity.quotes,
|
||||||
|
// Select only the quotes that were chosen in Phase 1
|
||||||
|
dexQuotes: marketSideLiquidity.quotes.dexQuotes.filter(
|
||||||
|
q => q.length > 0 && phase1OptimalSources.includes(q[0].source),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
optimizerResult = await this._generateOptimizedOrdersAsync(
|
||||||
|
phase2MarketSideLiquidity,
|
||||||
|
phaseTwoOptimizerOpts,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -836,75 +871,6 @@ export class MarketOperationUtils {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO(kimpers): Remove this when we know that it's safe to drop the fallbacks on native orders
|
|
||||||
// tslint:disable-next-line: prefer-function-over-method
|
|
||||||
private async _addOptionalFallbackAsync(
|
|
||||||
side: MarketOperation,
|
|
||||||
inputAmount: BigNumber,
|
|
||||||
optimalPath: Path,
|
|
||||||
dexQuotes: DexSample[][],
|
|
||||||
fills: Fill[][],
|
|
||||||
opts: GenerateOptimizedOrdersOpts,
|
|
||||||
penaltyOpts: PathPenaltyOpts,
|
|
||||||
): Promise<void> {
|
|
||||||
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
|
|
||||||
const optimalPathRate = optimalPath ? optimalPath.adjustedRate() : ZERO_AMOUNT;
|
|
||||||
// Generate a fallback path if sources requiring a fallback (fragile) are in the optimal path.
|
|
||||||
// Native is relatively fragile (limit order collision, expiry, or lack of available maker balance)
|
|
||||||
// LiquidityProvider is relatively fragile (collision)
|
|
||||||
const fragileSources = [ERC20BridgeSource.Native, ERC20BridgeSource.LiquidityProvider];
|
|
||||||
const fragileFills = optimalPath.fills.filter(f => fragileSources.includes(f.source));
|
|
||||||
if (opts.allowFallback && fragileFills.length !== 0) {
|
|
||||||
// We create a fallback path that is exclusive of Native liquidity
|
|
||||||
// This is the optimal on-chain path for the entire input amount
|
|
||||||
const sturdyPenaltyOpts = {
|
|
||||||
...penaltyOpts,
|
|
||||||
exchangeProxyOverhead: (sourceFlags: bigint) =>
|
|
||||||
// tslint:disable-next-line: no-bitwise
|
|
||||||
penaltyOpts.exchangeProxyOverhead(sourceFlags | optimalPath.sourceFlags),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sturdyOptimalPath: Path | undefined;
|
|
||||||
if (SHOULD_USE_RUST_ROUTER) {
|
|
||||||
const sturdySamples = dexQuotes.filter(
|
|
||||||
samples => samples.length > 0 && !fragileSources.includes(samples[0].source),
|
|
||||||
);
|
|
||||||
sturdyOptimalPath = findOptimalRustPathFromSamples(
|
|
||||||
side,
|
|
||||||
sturdySamples,
|
|
||||||
[],
|
|
||||||
inputAmount,
|
|
||||||
sturdyPenaltyOpts,
|
|
||||||
opts.feeSchedule,
|
|
||||||
this._sampler.chainId,
|
|
||||||
opts.neonRouterNumSamples,
|
|
||||||
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const sturdyFills = fills.filter(p => p.length > 0 && !fragileSources.includes(p[0].source));
|
|
||||||
sturdyOptimalPath = await findOptimalPathJSAsync(
|
|
||||||
side,
|
|
||||||
sturdyFills,
|
|
||||||
inputAmount,
|
|
||||||
opts.runLimit,
|
|
||||||
undefined, // hack: set sampler metrics to undefined to avoid fallback timings
|
|
||||||
sturdyPenaltyOpts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Calculate the slippage of on-chain sources compared to the most optimal path
|
|
||||||
// if within an acceptable threshold we enable a fallback to prevent reverts
|
|
||||||
if (
|
|
||||||
sturdyOptimalPath !== undefined &&
|
|
||||||
(fragileFills.length === optimalPath.fills.length ||
|
|
||||||
sturdyOptimalPath.adjustedSlippage(optimalPathRate) <= maxFallbackSlippage)
|
|
||||||
) {
|
|
||||||
optimalPath.addFallback(sturdyOptimalPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable: max-file-line-count
|
// tslint:disable: max-file-line-count
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
DexSample,
|
DexSample,
|
||||||
ExchangeProxyOverhead,
|
ExchangeProxyOverhead,
|
||||||
FeeSchedule,
|
FeeSchedule,
|
||||||
|
FillAdjustor,
|
||||||
MarketSideLiquidity,
|
MarketSideLiquidity,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
@@ -38,6 +39,7 @@ export function getBestTwoHopQuote(
|
|||||||
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
|
||||||
feeSchedule?: FeeSchedule,
|
feeSchedule?: FeeSchedule,
|
||||||
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
exchangeProxyOverhead?: ExchangeProxyOverhead,
|
||||||
|
fillAdjustor?: FillAdjustor,
|
||||||
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
|
||||||
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
const { side, inputAmount, outputAmountPerEth, quotes } = marketSideLiquidity;
|
||||||
const { twoHopQuotes } = quotes;
|
const { twoHopQuotes } = quotes;
|
||||||
@@ -57,7 +59,15 @@ export function getBestTwoHopQuote(
|
|||||||
}
|
}
|
||||||
const best = filteredQuotes
|
const best = filteredQuotes
|
||||||
.map(quote =>
|
.map(quote =>
|
||||||
getTwoHopAdjustedRate(side, quote, inputAmount, outputAmountPerEth, feeSchedule, exchangeProxyOverhead),
|
getTwoHopAdjustedRate(
|
||||||
|
side,
|
||||||
|
quote,
|
||||||
|
inputAmount,
|
||||||
|
outputAmountPerEth,
|
||||||
|
feeSchedule,
|
||||||
|
exchangeProxyOverhead,
|
||||||
|
fillAdjustor,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.reduce(
|
.reduce(
|
||||||
(prev, curr, i) =>
|
(prev, curr, i) =>
|
||||||
@@ -70,6 +80,7 @@ export function getBestTwoHopQuote(
|
|||||||
outputAmountPerEth,
|
outputAmountPerEth,
|
||||||
feeSchedule,
|
feeSchedule,
|
||||||
exchangeProxyOverhead,
|
exchangeProxyOverhead,
|
||||||
|
fillAdjustor,
|
||||||
),
|
),
|
||||||
quote: filteredQuotes[0],
|
quote: filteredQuotes[0],
|
||||||
},
|
},
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
import { BridgeProtocol, encodeBridgeSourceId, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||||
|
import _ = require('lodash');
|
||||||
|
|
||||||
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
|
||||||
|
|
||||||
@@ -11,12 +12,12 @@ import {
|
|||||||
BalancerV2BatchSwapFillData,
|
BalancerV2BatchSwapFillData,
|
||||||
BalancerV2FillData,
|
BalancerV2FillData,
|
||||||
BancorFillData,
|
BancorFillData,
|
||||||
CollapsedFill,
|
|
||||||
CompoundFillData,
|
CompoundFillData,
|
||||||
CurveFillData,
|
CurveFillData,
|
||||||
DexSample,
|
DexSample,
|
||||||
DODOFillData,
|
DODOFillData,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
Fill,
|
||||||
FillData,
|
FillData,
|
||||||
FinalUniswapV3FillData,
|
FinalUniswapV3FillData,
|
||||||
GeistFillData,
|
GeistFillData,
|
||||||
@@ -28,7 +29,7 @@ import {
|
|||||||
MakerPsmFillData,
|
MakerPsmFillData,
|
||||||
MooniswapFillData,
|
MooniswapFillData,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeCollapsedFill,
|
NativeFillData,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
OptimizedMarketBridgeOrder,
|
OptimizedMarketBridgeOrder,
|
||||||
@@ -60,23 +61,27 @@ export function createOrdersFromTwoHopSample(
|
|||||||
): OptimizedMarketOrder[] {
|
): OptimizedMarketOrder[] {
|
||||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||||
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
|
const { firstHopSource, secondHopSource, intermediateToken } = sample.fillData;
|
||||||
const firstHopFill: CollapsedFill = {
|
const firstHopFill: Fill = {
|
||||||
sourcePathId: '',
|
sourcePathId: '',
|
||||||
source: firstHopSource.source,
|
source: firstHopSource.source,
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
input: opts.side === MarketOperation.Sell ? sample.input : ZERO_AMOUNT,
|
||||||
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
output: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||||
subFills: [],
|
adjustedOutput: opts.side === MarketOperation.Sell ? ZERO_AMOUNT : sample.output,
|
||||||
fillData: firstHopSource.fillData,
|
fillData: firstHopSource.fillData,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
const secondHopFill: CollapsedFill = {
|
const secondHopFill: Fill = {
|
||||||
sourcePathId: '',
|
sourcePathId: '',
|
||||||
source: secondHopSource.source,
|
source: secondHopSource.source,
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
input: opts.side === MarketOperation.Sell ? MAX_UINT256 : sample.input,
|
||||||
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
output: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||||
subFills: [],
|
adjustedOutput: opts.side === MarketOperation.Sell ? sample.output : MAX_UINT256,
|
||||||
fillData: secondHopSource.fillData,
|
fillData: secondHopSource.fillData,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
return [
|
return [
|
||||||
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
createBridgeOrder(firstHopFill, intermediateToken, takerToken, opts.side),
|
||||||
@@ -135,8 +140,6 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
|||||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Ellipsis');
|
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Ellipsis');
|
||||||
case ERC20BridgeSource.Component:
|
case ERC20BridgeSource.Component:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.Shell, 'Component');
|
return encodeBridgeSourceId(BridgeProtocol.Shell, 'Component');
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
return encodeBridgeSourceId(BridgeProtocol.Curve, 'Smoothy');
|
|
||||||
case ERC20BridgeSource.Saddle:
|
case ERC20BridgeSource.Saddle:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Saddle');
|
return encodeBridgeSourceId(BridgeProtocol.Nerve, 'Saddle');
|
||||||
case ERC20BridgeSource.XSigma:
|
case ERC20BridgeSource.XSigma:
|
||||||
@@ -151,8 +154,6 @@ export function getErc20BridgeSourceToBridgeSource(source: ERC20BridgeSource): s
|
|||||||
return encodeBridgeSourceId(BridgeProtocol.KyberDmm, 'KyberDmm');
|
return encodeBridgeSourceId(BridgeProtocol.KyberDmm, 'KyberDmm');
|
||||||
case ERC20BridgeSource.QuickSwap:
|
case ERC20BridgeSource.QuickSwap:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'QuickSwap');
|
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'QuickSwap');
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'ComethSwap');
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
case ERC20BridgeSource.Dfyn:
|
||||||
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Dfyn');
|
return encodeBridgeSourceId(BridgeProtocol.UniswapV2, 'Dfyn');
|
||||||
case ERC20BridgeSource.CurveV2:
|
case ERC20BridgeSource.CurveV2:
|
||||||
@@ -236,7 +237,6 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
|||||||
case ERC20BridgeSource.Synapse:
|
case ERC20BridgeSource.Synapse:
|
||||||
case ERC20BridgeSource.Belt:
|
case ERC20BridgeSource.Belt:
|
||||||
case ERC20BridgeSource.Ellipsis:
|
case ERC20BridgeSource.Ellipsis:
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
case ERC20BridgeSource.Saddle:
|
case ERC20BridgeSource.Saddle:
|
||||||
case ERC20BridgeSource.XSigma:
|
case ERC20BridgeSource.XSigma:
|
||||||
case ERC20BridgeSource.FirebirdOneSwap:
|
case ERC20BridgeSource.FirebirdOneSwap:
|
||||||
@@ -284,7 +284,6 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
|||||||
case ERC20BridgeSource.ApeSwap:
|
case ERC20BridgeSource.ApeSwap:
|
||||||
case ERC20BridgeSource.CheeseSwap:
|
case ERC20BridgeSource.CheeseSwap:
|
||||||
case ERC20BridgeSource.QuickSwap:
|
case ERC20BridgeSource.QuickSwap:
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
case ERC20BridgeSource.Dfyn:
|
||||||
case ERC20BridgeSource.WaultSwap:
|
case ERC20BridgeSource.WaultSwap:
|
||||||
case ERC20BridgeSource.ShibaSwap:
|
case ERC20BridgeSource.ShibaSwap:
|
||||||
@@ -398,68 +397,6 @@ export function createBridgeDataForBridgeOrder(order: OptimizedMarketBridgeOrder
|
|||||||
return bridgeData;
|
return bridgeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBridgeOrder(
|
|
||||||
fill: CollapsedFill,
|
|
||||||
makerToken: string,
|
|
||||||
takerToken: string,
|
|
||||||
side: MarketOperation,
|
|
||||||
): OptimizedMarketBridgeOrder {
|
|
||||||
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
|
||||||
return {
|
|
||||||
makerToken,
|
|
||||||
takerToken,
|
|
||||||
makerAmount,
|
|
||||||
takerAmount,
|
|
||||||
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
|
||||||
source: fill.source,
|
|
||||||
sourcePathId: fill.sourcePathId,
|
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
|
||||||
fills: [fill],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: CollapsedFill): FillData {
|
|
||||||
switch (fill.source) {
|
|
||||||
case ERC20BridgeSource.UniswapV3: {
|
|
||||||
const fd = fill.fillData as UniswapV3FillData;
|
|
||||||
const { uniswapPath, gasUsed } = getBestUniswapV3PathAmountForInputAmount(fd, fill.input);
|
|
||||||
const finalFillData: FinalUniswapV3FillData = {
|
|
||||||
router: fd.router,
|
|
||||||
tokenAddressPath: fd.tokenAddressPath,
|
|
||||||
uniswapPath,
|
|
||||||
gasUsed,
|
|
||||||
};
|
|
||||||
return finalFillData;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return fill.fillData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBestUniswapV3PathAmountForInputAmount(
|
|
||||||
fillData: UniswapV3FillData,
|
|
||||||
inputAmount: BigNumber,
|
|
||||||
): UniswapV3PathAmount {
|
|
||||||
if (fillData.pathAmounts.length === 0) {
|
|
||||||
throw new Error(`No Uniswap V3 paths`);
|
|
||||||
}
|
|
||||||
// Find the best path that can satisfy `inputAmount`.
|
|
||||||
// Assumes `fillData.pathAmounts` is sorted ascending.
|
|
||||||
for (const pathAmount of fillData.pathAmounts) {
|
|
||||||
if (pathAmount.inputAmount.gte(inputAmount)) {
|
|
||||||
return pathAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fillData.pathAmounts[fillData.pathAmounts.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
|
||||||
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
|
||||||
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
|
||||||
return [makerToken, takerToken];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
export const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
||||||
const curveEncoder = AbiEncoder.create([
|
const curveEncoder = AbiEncoder.create([
|
||||||
{ name: 'curveAddress', type: 'address' },
|
{ name: 'curveAddress', type: 'address' },
|
||||||
@@ -506,7 +443,6 @@ export const BRIDGE_ENCODERS: {
|
|||||||
[ERC20BridgeSource.Synapse]: curveEncoder,
|
[ERC20BridgeSource.Synapse]: curveEncoder,
|
||||||
[ERC20BridgeSource.Belt]: curveEncoder,
|
[ERC20BridgeSource.Belt]: curveEncoder,
|
||||||
[ERC20BridgeSource.Ellipsis]: curveEncoder,
|
[ERC20BridgeSource.Ellipsis]: curveEncoder,
|
||||||
[ERC20BridgeSource.Smoothy]: curveEncoder,
|
|
||||||
[ERC20BridgeSource.Saddle]: curveEncoder,
|
[ERC20BridgeSource.Saddle]: curveEncoder,
|
||||||
[ERC20BridgeSource.XSigma]: curveEncoder,
|
[ERC20BridgeSource.XSigma]: curveEncoder,
|
||||||
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
|
[ERC20BridgeSource.FirebirdOneSwap]: curveEncoder,
|
||||||
@@ -544,7 +480,6 @@ export const BRIDGE_ENCODERS: {
|
|||||||
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
|
[ERC20BridgeSource.WaultSwap]: routerAddressPathEncoder,
|
||||||
// Polygon
|
// Polygon
|
||||||
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
|
[ERC20BridgeSource.QuickSwap]: routerAddressPathEncoder,
|
||||||
[ERC20BridgeSource.ComethSwap]: routerAddressPathEncoder,
|
|
||||||
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
|
[ERC20BridgeSource.Dfyn]: routerAddressPathEncoder,
|
||||||
// Generic pools
|
// Generic pools
|
||||||
[ERC20BridgeSource.Shell]: poolEncoder,
|
[ERC20BridgeSource.Shell]: poolEncoder,
|
||||||
@@ -584,7 +519,7 @@ export const BRIDGE_ENCODERS: {
|
|||||||
[ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'),
|
[ERC20BridgeSource.Velodrome]: AbiEncoder.create('(address,bool)'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNumber, BigNumber] {
|
function getFillTokenAmounts(fill: Fill, side: MarketOperation): [BigNumber, BigNumber] {
|
||||||
return [
|
return [
|
||||||
// Maker asset amount.
|
// Maker asset amount.
|
||||||
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
|
side === MarketOperation.Sell ? fill.output.integerValue(BigNumber.ROUND_DOWN) : fill.input,
|
||||||
@@ -594,7 +529,7 @@ function getFillTokenAmounts(fill: CollapsedFill, side: MarketOperation): [BigNu
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createNativeOptimizedOrder(
|
export function createNativeOptimizedOrder(
|
||||||
fill: NativeCollapsedFill,
|
fill: Fill<NativeFillData>,
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
): OptimizedMarketOrderBase<NativeLimitOrderFillData> | OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||||
const fillData = fill.fillData;
|
const fillData = fill.fillData;
|
||||||
@@ -606,10 +541,76 @@ export function createNativeOptimizedOrder(
|
|||||||
takerToken: fillData.order.takerToken,
|
takerToken: fillData.order.takerToken,
|
||||||
makerAmount,
|
makerAmount,
|
||||||
takerAmount,
|
takerAmount,
|
||||||
fills: [fill],
|
|
||||||
fillData,
|
fillData,
|
||||||
|
fill: cleanFillForExport(fill),
|
||||||
};
|
};
|
||||||
return fill.type === FillQuoteTransformerOrderType.Rfq
|
return fill.type === FillQuoteTransformerOrderType.Rfq
|
||||||
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
? { ...base, type: FillQuoteTransformerOrderType.Rfq, fillData: fillData as NativeRfqOrderFillData }
|
||||||
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
: { ...base, type: FillQuoteTransformerOrderType.Limit, fillData: fillData as NativeLimitOrderFillData };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createBridgeOrder(
|
||||||
|
fill: Fill,
|
||||||
|
makerToken: string,
|
||||||
|
takerToken: string,
|
||||||
|
side: MarketOperation,
|
||||||
|
): OptimizedMarketBridgeOrder {
|
||||||
|
const [makerAmount, takerAmount] = getFillTokenAmounts(fill, side);
|
||||||
|
return {
|
||||||
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
source: fill.source,
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
makerAmount,
|
||||||
|
takerAmount,
|
||||||
|
fillData: createFinalBridgeOrderFillDataFromCollapsedFill(fill),
|
||||||
|
fill: cleanFillForExport(fill),
|
||||||
|
sourcePathId: fill.sourcePathId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanFillForExport(fill: Fill): Fill {
|
||||||
|
return _.omit(fill, ['flags', 'fillData', 'sourcePathId', 'source', 'type']) as Fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFinalBridgeOrderFillDataFromCollapsedFill(fill: Fill): FillData {
|
||||||
|
switch (fill.source) {
|
||||||
|
case ERC20BridgeSource.UniswapV3: {
|
||||||
|
const fd = fill.fillData as UniswapV3FillData;
|
||||||
|
const { uniswapPath, gasUsed } = getBestUniswapV3PathAmountForInputAmount(fd, fill.input);
|
||||||
|
const finalFillData: FinalUniswapV3FillData = {
|
||||||
|
router: fd.router,
|
||||||
|
tokenAddressPath: fd.tokenAddressPath,
|
||||||
|
uniswapPath,
|
||||||
|
gasUsed,
|
||||||
|
};
|
||||||
|
return finalFillData;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fill.fillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBestUniswapV3PathAmountForInputAmount(
|
||||||
|
fillData: UniswapV3FillData,
|
||||||
|
inputAmount: BigNumber,
|
||||||
|
): UniswapV3PathAmount {
|
||||||
|
if (fillData.pathAmounts.length === 0) {
|
||||||
|
throw new Error(`No Uniswap V3 paths`);
|
||||||
|
}
|
||||||
|
// Find the best path that can satisfy `inputAmount`.
|
||||||
|
// Assumes `fillData.pathAmounts` is sorted ascending.
|
||||||
|
for (const pathAmount of fillData.pathAmounts) {
|
||||||
|
if (pathAmount.inputAmount.gte(inputAmount)) {
|
||||||
|
return pathAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fillData.pathAmounts[fillData.pathAmounts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMakerTakerTokens(opts: CreateOrderFromPathOpts): [string, string] {
|
||||||
|
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
|
||||||
|
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
|
||||||
|
return [makerToken, takerToken];
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import _ = require('lodash');
|
||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
@@ -6,14 +7,7 @@ import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
|
|||||||
import { ethToOutputAmount } from './fills';
|
import { ethToOutputAmount } from './fills';
|
||||||
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
|
import { createBridgeOrder, createNativeOptimizedOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
|
||||||
import { getCompleteRate, getRate } from './rate_utils';
|
import { getCompleteRate, getRate } from './rate_utils';
|
||||||
import {
|
import { ERC20BridgeSource, ExchangeProxyOverhead, Fill, NativeFillData, OptimizedMarketOrder } from './types';
|
||||||
CollapsedFill,
|
|
||||||
ERC20BridgeSource,
|
|
||||||
ExchangeProxyOverhead,
|
|
||||||
Fill,
|
|
||||||
NativeCollapsedFill,
|
|
||||||
OptimizedMarketOrder,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
// tslint:disable: prefer-for-of no-bitwise completed-docs
|
||||||
|
|
||||||
@@ -37,7 +31,6 @@ export const DEFAULT_PATH_PENALTY_OPTS: PathPenaltyOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Path {
|
export class Path {
|
||||||
public collapsedFills?: ReadonlyArray<CollapsedFill>;
|
|
||||||
public orders?: OptimizedMarketOrder[];
|
public orders?: OptimizedMarketOrder[];
|
||||||
public sourceFlags: bigint = BigInt(0);
|
public sourceFlags: bigint = BigInt(0);
|
||||||
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
protected _size: PathSize = { input: ZERO_AMOUNT, output: ZERO_AMOUNT };
|
||||||
@@ -57,16 +50,6 @@ export class Path {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static clone(base: Path): Path {
|
|
||||||
const clonedPath = new Path(base.side, base.fills.slice(), base.targetInput, base.pathPenaltyOpts);
|
|
||||||
clonedPath.sourceFlags = base.sourceFlags;
|
|
||||||
clonedPath._size = { ...base._size };
|
|
||||||
clonedPath._adjustedSize = { ...base._adjustedSize };
|
|
||||||
clonedPath.collapsedFills = base.collapsedFills === undefined ? undefined : base.collapsedFills.slice();
|
|
||||||
clonedPath.orders = base.orders === undefined ? undefined : base.orders.slice();
|
|
||||||
return clonedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
protected readonly side: MarketOperation,
|
protected readonly side: MarketOperation,
|
||||||
public fills: ReadonlyArray<Fill>,
|
public fills: ReadonlyArray<Fill>,
|
||||||
@@ -74,68 +57,33 @@ export class Path {
|
|||||||
public readonly pathPenaltyOpts: PathPenaltyOpts,
|
public readonly pathPenaltyOpts: PathPenaltyOpts,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public append(fill: Fill): this {
|
|
||||||
(this.fills as Fill[]).push(fill);
|
|
||||||
this.sourceFlags |= fill.flags;
|
|
||||||
this._addFillSize(fill);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a fallback path to the current path
|
* Finalizes this path, creating fillable orders with the information required
|
||||||
* Fallback must contain exclusive fills that are
|
* for settlement
|
||||||
* not present in this path
|
|
||||||
*/
|
*/
|
||||||
public addFallback(fallback: Path): this {
|
public finalize(opts: CreateOrderFromPathOpts): FinalizedPath {
|
||||||
// We pre-pend the sources which have a higher probability of failure
|
|
||||||
// This allows us to continue on to the remaining fills
|
|
||||||
// If the "flakey" sources like Native were at the end, we may have a failure
|
|
||||||
// as the last fill and then either revert, or go back to a source we previously
|
|
||||||
// filled against
|
|
||||||
const nativeFills = this.fills.filter(f => f.source === ERC20BridgeSource.Native);
|
|
||||||
const otherFills = this.fills.filter(f => f.source !== ERC20BridgeSource.Native);
|
|
||||||
|
|
||||||
// Map to the unique source id and the index to represent a unique fill
|
|
||||||
const fillToFillId = (fill: Fill) => `${fill.sourcePathId}${fill.index}`;
|
|
||||||
const otherFillIds = otherFills.map(f => fillToFillId(f));
|
|
||||||
|
|
||||||
this.fills = [
|
|
||||||
// Append all of the native fills first
|
|
||||||
...nativeFills,
|
|
||||||
// Add the other fills that are not native in the optimal path
|
|
||||||
...otherFills,
|
|
||||||
// Add the fills to the end that aren't already included
|
|
||||||
...fallback.fills.filter(f => !otherFillIds.includes(fillToFillId(f))),
|
|
||||||
];
|
|
||||||
// Recompute the source flags
|
|
||||||
this.sourceFlags = this.fills.reduce((flags, fill) => flags | fill.flags, BigInt(0));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public collapse(opts: CreateOrderFromPathOpts): CollapsedPath {
|
|
||||||
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
const [makerToken, takerToken] = getMakerTakerTokens(opts);
|
||||||
const collapsedFills = this.collapsedFills === undefined ? this._collapseFills() : this.collapsedFills;
|
|
||||||
this.orders = [];
|
this.orders = [];
|
||||||
for (let i = 0; i < collapsedFills.length; ) {
|
for (const fill of this.fills) {
|
||||||
if (collapsedFills[i].source === ERC20BridgeSource.Native) {
|
// internal BigInt flag field is not supported JSON and is tricky
|
||||||
this.orders.push(createNativeOptimizedOrder(collapsedFills[i] as NativeCollapsedFill, opts.side));
|
// to remove upstream. Since it's not needed in a FinalizedPath we just drop it.
|
||||||
++i;
|
const normalizedFill = _.omit(fill, 'flags') as Fill;
|
||||||
continue;
|
if (fill.source === ERC20BridgeSource.Native) {
|
||||||
|
this.orders.push(createNativeOptimizedOrder(normalizedFill as Fill<NativeFillData>, opts.side));
|
||||||
|
} else {
|
||||||
|
this.orders.push(createBridgeOrder(normalizedFill, makerToken, takerToken, opts.side));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.orders.push(createBridgeOrder(collapsedFills[i], makerToken, takerToken, opts.side));
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
return this as CollapsedPath;
|
return this as FinalizedPath;
|
||||||
}
|
|
||||||
|
|
||||||
public size(): PathSize {
|
|
||||||
return this._size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public adjustedSize(): PathSize {
|
public adjustedSize(): PathSize {
|
||||||
|
// Adjusted input/output has been adjusted by the cost of the DEX, but not by any
|
||||||
|
// overhead added by the exchange proxy.
|
||||||
const { input, output } = this._adjustedSize;
|
const { input, output } = this._adjustedSize;
|
||||||
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
const { exchangeProxyOverhead, outputAmountPerEth, inputAmountPerEth } = this.pathPenaltyOpts;
|
||||||
|
// Calculate the additional penalty from the ways this path can be filled
|
||||||
|
// by the exchange proxy, e.g VIPs (small) or FillQuoteTransformer (large)
|
||||||
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
const gasOverhead = exchangeProxyOverhead(this.sourceFlags);
|
||||||
const pathPenalty = ethToOutputAmount({
|
const pathPenalty = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
@@ -155,6 +103,10 @@ export class Path {
|
|||||||
return getCompleteRate(this.side, input, output, this.targetInput);
|
return getCompleteRate(this.side, input, output, this.targetInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the rate of this path, where the output has been
|
||||||
|
* adjusted for penalties (e.g cost)
|
||||||
|
*/
|
||||||
public adjustedRate(): BigNumber {
|
public adjustedRate(): BigNumber {
|
||||||
const { input, output } = this.adjustedSize();
|
const { input, output } = this.adjustedSize();
|
||||||
return getRate(this.side, input, output);
|
return getRate(this.side, input, output);
|
||||||
@@ -171,16 +123,11 @@ export class Path {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
public adjustedSlippage(maxRate: BigNumber): number {
|
/**
|
||||||
if (maxRate.eq(0)) {
|
* Compares two paths returning if this adjusted path
|
||||||
return 0;
|
* is better than the other adjusted path
|
||||||
}
|
*/
|
||||||
const totalRate = this.adjustedRate();
|
public isAdjustedBetterThan(other: Path): boolean {
|
||||||
const rateChange = maxRate.minus(totalRate);
|
|
||||||
return rateChange.div(maxRate).toNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
public isBetterThan(other: Path): boolean {
|
|
||||||
if (!this.targetInput.isEqualTo(other.targetInput)) {
|
if (!this.targetInput.isEqualTo(other.targetInput)) {
|
||||||
throw new Error(`Target input mismatch: ${this.targetInput} !== ${other.targetInput}`);
|
throw new Error(`Target input mismatch: ${this.targetInput} !== ${other.targetInput}`);
|
||||||
}
|
}
|
||||||
@@ -192,78 +139,6 @@ export class Path {
|
|||||||
} else {
|
} else {
|
||||||
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
||||||
}
|
}
|
||||||
// if (otherInput.isLessThan(targetInput)) {
|
|
||||||
// return input.isGreaterThan(otherInput);
|
|
||||||
// } else if (input.isGreaterThanOrEqualTo(targetInput)) {
|
|
||||||
// return this.adjustedCompleteRate().isGreaterThan(other.adjustedCompleteRate());
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isComplete(): boolean {
|
|
||||||
const { input } = this._size;
|
|
||||||
return input.gte(this.targetInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isValid(skipDuplicateCheck: boolean = false): boolean {
|
|
||||||
for (let i = 0; i < this.fills.length; ++i) {
|
|
||||||
// Fill must immediately follow its parent.
|
|
||||||
if (this.fills[i].parent) {
|
|
||||||
if (i === 0 || this.fills[i - 1] !== this.fills[i].parent) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!skipDuplicateCheck) {
|
|
||||||
// Fill must not be duplicated.
|
|
||||||
for (let j = 0; j < i; ++j) {
|
|
||||||
if (this.fills[i] === this.fills[j]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isValidNextFill(fill: Fill): boolean {
|
|
||||||
if (this.fills.length === 0) {
|
|
||||||
return !fill.parent;
|
|
||||||
}
|
|
||||||
if (this.fills[this.fills.length - 1] === fill.parent) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (fill.parent) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _collapseFills(): ReadonlyArray<CollapsedFill> {
|
|
||||||
this.collapsedFills = [];
|
|
||||||
for (const fill of this.fills) {
|
|
||||||
const source = fill.source;
|
|
||||||
if (this.collapsedFills.length !== 0 && source !== ERC20BridgeSource.Native) {
|
|
||||||
const prevFill = this.collapsedFills[this.collapsedFills.length - 1];
|
|
||||||
// If the last fill is from the same source, merge them.
|
|
||||||
if (prevFill.sourcePathId === fill.sourcePathId) {
|
|
||||||
prevFill.input = prevFill.input.plus(fill.input);
|
|
||||||
prevFill.output = prevFill.output.plus(fill.output);
|
|
||||||
prevFill.fillData = fill.fillData;
|
|
||||||
prevFill.subFills.push(fill);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(this.collapsedFills as CollapsedFill[]).push({
|
|
||||||
sourcePathId: fill.sourcePathId,
|
|
||||||
source: fill.source,
|
|
||||||
type: fill.type,
|
|
||||||
fillData: fill.fillData,
|
|
||||||
input: fill.input,
|
|
||||||
output: fill.output,
|
|
||||||
subFills: [fill],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.collapsedFills;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addFillSize(fill: Fill): void {
|
private _addFillSize(fill: Fill): void {
|
||||||
@@ -285,7 +160,6 @@ export class Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollapsedPath extends Path {
|
export interface FinalizedPath extends Path {
|
||||||
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
|
|
||||||
readonly orders: OptimizedMarketOrder[];
|
readonly orders: OptimizedMarketOrder[];
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { assert } from '@0x/assert';
|
import { assert } from '@0x/assert';
|
||||||
import { ChainId } from '@0x/contract-addresses';
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
import { OptimizerCapture, route, SerializedPath } from '@0x/neon-router';
|
||||||
|
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils } from '@0x/utils';
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
@@ -9,13 +10,12 @@ import { DEFAULT_WARNING_LOGGER } from '../../constants';
|
|||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
import { MarketOperation, NativeOrderWithFillableAmounts } from '../../types';
|
||||||
|
|
||||||
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
import { VIP_ERC20_BRIDGE_SOURCES_BY_CHAIN_ID, ZERO_AMOUNT } from './constants';
|
||||||
import { dexSamplesToFills, ethToOutputAmount, nativeOrdersToFills } from './fills';
|
import { dexSampleToFill, ethToOutputAmount, nativeOrderToFill } from './fills';
|
||||||
import { DEFAULT_PATH_PENALTY_OPTS, Path, PathPenaltyOpts } from './path';
|
import { Path, PathPenaltyOpts } from './path';
|
||||||
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillData, SamplerMetrics } from './types';
|
import { DexSample, ERC20BridgeSource, FeeSchedule, Fill, FillAdjustor, FillData, SamplerMetrics } from './types';
|
||||||
|
|
||||||
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs no-bitwise
|
||||||
|
|
||||||
const RUN_LIMIT_DECAY_FACTOR = 0.5;
|
|
||||||
// NOTE: The Rust router will panic with less than 3 samples
|
// NOTE: The Rust router will panic with less than 3 samples
|
||||||
const MIN_NUM_SAMPLE_INPUTS = 3;
|
const MIN_NUM_SAMPLE_INPUTS = 3;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ function calculateOuputFee(
|
|||||||
): BigNumber {
|
): BigNumber {
|
||||||
if (isDexSample(sampleOrNativeOrder)) {
|
if (isDexSample(sampleOrNativeOrder)) {
|
||||||
const { input, output, source, fillData } = sampleOrNativeOrder;
|
const { input, output, source, fillData } = sampleOrNativeOrder;
|
||||||
const fee = fees[source]?.(fillData) || 0;
|
const fee = fees[source]?.(fillData).fee || ZERO_AMOUNT;
|
||||||
const outputFee = ethToOutputAmount({
|
const outputFee = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -56,7 +56,7 @@ function calculateOuputFee(
|
|||||||
return outputFee;
|
return outputFee;
|
||||||
} else {
|
} else {
|
||||||
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
const { input, output } = nativeOrderToNormalizedAmounts(side, sampleOrNativeOrder);
|
||||||
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder) || 0;
|
const fee = fees[ERC20BridgeSource.Native]?.(sampleOrNativeOrder).fee || ZERO_AMOUNT;
|
||||||
const outputFee = ethToOutputAmount({
|
const outputFee = ethToOutputAmount({
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -77,6 +77,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
neonRouterNumSamples: number,
|
neonRouterNumSamples: number,
|
||||||
vipSourcesSet: Set<ERC20BridgeSource>,
|
vipSourcesSet: Set<ERC20BridgeSource>,
|
||||||
|
fillAdjustor: FillAdjustor,
|
||||||
): { allSourcesPath: Path | undefined; vipSourcesPath: Path | undefined } | undefined {
|
): { allSourcesPath: Path | undefined; vipSourcesPath: Path | undefined } | undefined {
|
||||||
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
// Currently the rust router is unable to handle 1 base unit sized quotes and will error out
|
||||||
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
// To avoid flooding the logs with these errors we just return an insufficient liquidity error
|
||||||
@@ -85,31 +86,44 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createFill = (sample: DexSample): Fill | undefined => {
|
// Create a `Fill` from a dex sample and adjust it with any passed in
|
||||||
const fills = dexSamplesToFills(side, [sample], opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
// adjustor
|
||||||
// NOTE: If the sample has 0 output dexSamplesToFills will return [] because no fill can be created
|
const createFillFromDexSample = (sample: DexSample): Fill => {
|
||||||
if (fills.length === 0) {
|
const fill = dexSampleToFill(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees);
|
||||||
return undefined;
|
const adjustedFills = fillAdjustor.adjustFills(side, [fill], input);
|
||||||
}
|
return adjustedFills[0];
|
||||||
|
|
||||||
return fills[0];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPathFromStrategy = (sourcesRustRoute: Float64Array, sourcesOutputAmounts: Float64Array) => {
|
const createPathFromStrategy = (optimalRouteInputs: Float64Array, optimalRouteOutputs: Float64Array) => {
|
||||||
|
/**
|
||||||
|
* inputs are the amounts to fill at each source index
|
||||||
|
* e.g fill 2076 at index 4
|
||||||
|
* [ 0, 0, 0, 0, 2076, 464, 230,
|
||||||
|
* 230, 0, 0, 0 ]
|
||||||
|
* the sum represents the total input amount
|
||||||
|
*
|
||||||
|
* outputs are the amounts we expect out at each source index
|
||||||
|
* [ 0, 0, 0, 0, 42216, 9359, 4677,
|
||||||
|
* 4674, 0, 0, 0 ]
|
||||||
|
* the sum represents the total expected output amount
|
||||||
|
*/
|
||||||
|
|
||||||
const routesAndSamplesAndOutputs = _.zip(
|
const routesAndSamplesAndOutputs = _.zip(
|
||||||
sourcesRustRoute,
|
optimalRouteInputs,
|
||||||
|
optimalRouteOutputs,
|
||||||
samplesAndNativeOrdersWithResults,
|
samplesAndNativeOrdersWithResults,
|
||||||
sourcesOutputAmounts,
|
|
||||||
sampleSourcePathIds,
|
sampleSourcePathIds,
|
||||||
);
|
);
|
||||||
const adjustedFills: Fill[] = [];
|
const adjustedFills: Fill[] = [];
|
||||||
const totalRoutedAmount = BigNumber.sum(...sourcesRustRoute);
|
const totalRoutedAmount = BigNumber.sum(...optimalRouteInputs);
|
||||||
|
|
||||||
|
// Due to precision errors we can end up with a totalRoutedAmount that is not exactly equal to the input
|
||||||
|
const precisionErrorScalar = input.dividedBy(totalRoutedAmount);
|
||||||
|
|
||||||
const scale = input.dividedBy(totalRoutedAmount);
|
|
||||||
for (const [
|
for (const [
|
||||||
routeInput,
|
routeInput,
|
||||||
routeSamplesAndNativeOrders,
|
|
||||||
outputAmount,
|
outputAmount,
|
||||||
|
routeSamplesAndNativeOrders,
|
||||||
sourcePathId,
|
sourcePathId,
|
||||||
] of routesAndSamplesAndOutputs) {
|
] of routesAndSamplesAndOutputs) {
|
||||||
if (!Number.isFinite(outputAmount)) {
|
if (!Number.isFinite(outputAmount)) {
|
||||||
@@ -119,26 +133,27 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount) {
|
if (!routeInput || !routeSamplesAndNativeOrders || !outputAmount) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// TODO(kimpers): [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
|
// TODO: [TKR-241] amounts are sometimes clipped in the router due to precision loss for number/f64
|
||||||
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
|
// we can work around it by scaling it and rounding up. However now we end up with a total amount of a couple base units too much
|
||||||
const rustInputAdjusted = BigNumber.min(
|
const routeInputCorrected = BigNumber.min(
|
||||||
new BigNumber(routeInput).multipliedBy(scale).integerValue(BigNumber.ROUND_CEIL),
|
precisionErrorScalar.multipliedBy(routeInput).integerValue(BigNumber.ROUND_CEIL),
|
||||||
input,
|
input,
|
||||||
);
|
);
|
||||||
|
|
||||||
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
|
const current = routeSamplesAndNativeOrders[routeSamplesAndNativeOrders.length - 1];
|
||||||
|
// If it is a native single order we only have one Input/output
|
||||||
|
// we want to convert this to an array of samples
|
||||||
if (!isDexSample(current)) {
|
if (!isDexSample(current)) {
|
||||||
const nativeFill = nativeOrdersToFills(
|
const nativeFill = nativeOrderToFill(
|
||||||
side,
|
side,
|
||||||
[current],
|
current,
|
||||||
rustInputAdjusted,
|
routeInputCorrected,
|
||||||
opts.outputAmountPerEth,
|
opts.outputAmountPerEth,
|
||||||
opts.inputAmountPerEth,
|
opts.inputAmountPerEth,
|
||||||
fees,
|
fees,
|
||||||
false,
|
false,
|
||||||
)[0] as Fill | undefined;
|
);
|
||||||
// Note: If the order has an adjusted rate of less than or equal to 0 it will be skipped
|
// Note: If the order has an adjusted rate of less than or equal to 0 it will be undefined
|
||||||
// and nativeFill will be `undefined`
|
|
||||||
if (nativeFill) {
|
if (nativeFill) {
|
||||||
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
// NOTE: For Limit/RFQ orders we are done here. No need to scale output
|
||||||
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
|
adjustedFills.push({ ...nativeFill, sourcePathId: sourcePathId ?? hexUtils.random() });
|
||||||
@@ -147,62 +162,54 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: For DexSamples only
|
// NOTE: For DexSamples only
|
||||||
let fill = createFill(current);
|
let fill = createFillFromDexSample(current);
|
||||||
if (!fill) {
|
if (!fill) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
|
const routeSamples = routeSamplesAndNativeOrders as Array<DexSample<FillData>>;
|
||||||
// Descend to approach a closer fill for fillData which may not be consistent
|
|
||||||
// throughout the path (UniswapV3) and for a closer guesstimate at
|
|
||||||
// gas used
|
|
||||||
|
|
||||||
|
// From the output of the router, find the closest Sample in terms of input.
|
||||||
|
// The Router may have chosen an amount to fill that we do not have a measured sample of
|
||||||
|
// Choosing this accurately is required in some sources where the `FillData` may change depending
|
||||||
|
// on the size of the trade. For example, UniswapV3 has variable gas cost
|
||||||
|
// which increases with input.
|
||||||
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
assert.assert(routeSamples.length >= 1, 'Found no sample to use for source');
|
||||||
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
for (let k = routeSamples.length - 1; k >= 0; k--) {
|
||||||
|
// If we're at the last remaining sample that's all we have left to use
|
||||||
if (k === 0) {
|
if (k === 0) {
|
||||||
fill = createFill(routeSamples[0]) ?? fill;
|
fill = createFillFromDexSample(routeSamples[0]) ?? fill;
|
||||||
}
|
}
|
||||||
if (rustInputAdjusted.isGreaterThan(routeSamples[k].input)) {
|
if (routeInputCorrected.isGreaterThan(routeSamples[k].input)) {
|
||||||
const left = routeSamples[k];
|
const left = routeSamples[k];
|
||||||
const right = routeSamples[k + 1];
|
const right = routeSamples[k + 1];
|
||||||
if (left && right) {
|
if (left && right) {
|
||||||
fill =
|
fill =
|
||||||
createFill({
|
createFillFromDexSample({
|
||||||
...right, // default to the greater (for gas used)
|
...right, // default to the greater (for gas used)
|
||||||
input: rustInputAdjusted,
|
input: routeInputCorrected,
|
||||||
output: new BigNumber(outputAmount),
|
output: new BigNumber(outputAmount).integerValue(),
|
||||||
}) ?? fill;
|
}) ?? fill;
|
||||||
} else {
|
} else {
|
||||||
assert.assert(Boolean(left || right), 'No valid sample to use');
|
assert.assert(Boolean(left || right), 'No valid sample to use');
|
||||||
fill = createFill(left || right) ?? fill;
|
fill = createFillFromDexSample(left || right) ?? fill;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kimpers): remove once we have solved the rounding/precision loss issues in the Rust router
|
// TODO: remove once we have solved the rounding/precision loss issues in the Rust router
|
||||||
const maxSampledOutput = BigNumber.max(...routeSamples.map(s => s.output));
|
const maxSampledOutput = BigNumber.max(...routeSamples.map(s => s.output)).integerValue();
|
||||||
// Scale output by scale factor but never go above the largest sample in sell quotes (unknown liquidity) or below 1 base unit (unfillable)
|
// Scale output by scale factor but never go above the largest sample in sell quotes (unknown liquidity) or below 1 base unit (unfillable)
|
||||||
const scaleOutput = (output: BigNumber) => {
|
const scaleOutput = (output: BigNumber) => {
|
||||||
// Don't try to scale 0 output as it will be clamped to 1
|
const capped = BigNumber.min(output.integerValue(), maxSampledOutput);
|
||||||
if (output.eq(ZERO_AMOUNT)) {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scaled = output
|
|
||||||
.times(scale)
|
|
||||||
.decimalPlaces(0, side === MarketOperation.Sell ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
|
||||||
const capped = MarketOperation.Sell ? BigNumber.min(scaled, maxSampledOutput) : scaled;
|
|
||||||
|
|
||||||
return BigNumber.max(capped, 1);
|
return BigNumber.max(capped, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
adjustedFills.push({
|
adjustedFills.push({
|
||||||
...fill,
|
...fill,
|
||||||
input: rustInputAdjusted,
|
input: routeInputCorrected,
|
||||||
output: scaleOutput(fill.output),
|
output: scaleOutput(fill.output),
|
||||||
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
adjustedOutput: scaleOutput(fill.adjustedOutput),
|
||||||
index: 0,
|
|
||||||
parent: undefined,
|
|
||||||
sourcePathId: sourcePathId ?? hexUtils.random(),
|
sourcePathId: sourcePathId ?? hexUtils.random(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -224,7 +231,6 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourcePathId = hexUtils.random();
|
|
||||||
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
const singleSourceSamplesWithOutput = [...singleSourceSamples];
|
||||||
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
for (let i = singleSourceSamples.length - 1; i >= 0; i--) {
|
||||||
const currentOutput = singleSourceSamples[i].output;
|
const currentOutput = singleSourceSamples[i].output;
|
||||||
@@ -240,17 +246,23 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kimpers): Do we need to handle 0 entries, from eg Kyber?
|
// TODO: Do we need to handle 0 entries, from eg Kyber?
|
||||||
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
|
const serializedPath = singleSourceSamplesWithOutput.reduce<SerializedPath>(
|
||||||
(memo, sample, sampleIdx) => {
|
(memo, sample, sampleIdx) => {
|
||||||
memo.ids.push(`${sample.source}-${serializedPaths.length}-${sampleIdx}`);
|
// Use the fill from createFillFromDexSample to apply
|
||||||
memo.inputs.push(sample.input.integerValue().toNumber());
|
// any user supplied adjustments
|
||||||
memo.outputs.push(sample.output.integerValue().toNumber());
|
const f = createFillFromDexSample(sample);
|
||||||
memo.outputFees.push(
|
memo.ids.push(`${f.source}-${serializedPaths.length}-${sampleIdx}`);
|
||||||
calculateOuputFee(side, sample, opts.outputAmountPerEth, opts.inputAmountPerEth, fees)
|
memo.inputs.push(f.input.integerValue().toNumber());
|
||||||
|
memo.outputs.push(f.output.integerValue().toNumber());
|
||||||
|
// Calculate the penalty of this sample as the diff between the
|
||||||
|
// output and the adjusted output
|
||||||
|
const outputFee = f.output
|
||||||
|
.minus(f.adjustedOutput)
|
||||||
|
.absoluteValue()
|
||||||
.integerValue()
|
.integerValue()
|
||||||
.toNumber(),
|
.toNumber();
|
||||||
);
|
memo.outputFees.push(outputFee);
|
||||||
|
|
||||||
return memo;
|
return memo;
|
||||||
},
|
},
|
||||||
@@ -265,6 +277,8 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
|
|
||||||
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
samplesAndNativeOrdersWithResults.push(singleSourceSamplesWithOutput);
|
||||||
serializedPaths.push(serializedPath);
|
serializedPaths.push(serializedPath);
|
||||||
|
|
||||||
|
const sourcePathId = hexUtils.random();
|
||||||
sampleSourcePathIds.push(sourcePathId);
|
sampleSourcePathIds.push(sourcePathId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,19 +320,22 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
normalizedOrderOutput.times(scaleToInput).times(fraction),
|
normalizedOrderOutput.times(scaleToInput).times(fraction),
|
||||||
normalizedOrderOutput,
|
normalizedOrderOutput,
|
||||||
);
|
);
|
||||||
const id = `${ERC20BridgeSource.Native}-${serializedPaths.length}-${idx}-${i}`;
|
const id = `${ERC20BridgeSource.Native}-${nativeOrder.type}-${serializedPaths.length}-${idx}-${i}`;
|
||||||
inputs.push(currentInput.integerValue().toNumber());
|
inputs.push(currentInput.integerValue().toNumber());
|
||||||
outputs.push(currentOutput.integerValue().toNumber());
|
outputs.push(currentOutput.integerValue().toNumber());
|
||||||
outputFees.push(fee);
|
outputFees.push(fee);
|
||||||
ids.push(id);
|
ids.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have a VIP for the Rfq order type, Limit order currently goes through FQT
|
||||||
|
const isVip = nativeOrder.type !== FillQuoteTransformerOrderType.Limit;
|
||||||
|
|
||||||
const serializedPath: SerializedPath = {
|
const serializedPath: SerializedPath = {
|
||||||
ids,
|
ids,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
outputFees,
|
outputFees,
|
||||||
isVip: true,
|
isVip,
|
||||||
};
|
};
|
||||||
|
|
||||||
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
samplesAndNativeOrdersWithResults.push([nativeOrder]);
|
||||||
@@ -375,7 +392,7 @@ function findRoutesAndCreateOptimalPath(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findOptimalRustPathFromSamples(
|
export function findOptimalPathFromSamples(
|
||||||
side: MarketOperation,
|
side: MarketOperation,
|
||||||
samples: DexSample[][],
|
samples: DexSample[][],
|
||||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||||
@@ -384,6 +401,7 @@ export function findOptimalRustPathFromSamples(
|
|||||||
fees: FeeSchedule,
|
fees: FeeSchedule,
|
||||||
chainId: ChainId,
|
chainId: ChainId,
|
||||||
neonRouterNumSamples: number,
|
neonRouterNumSamples: number,
|
||||||
|
fillAdjustor: FillAdjustor,
|
||||||
samplerMetrics?: SamplerMetrics,
|
samplerMetrics?: SamplerMetrics,
|
||||||
): Path | undefined {
|
): Path | undefined {
|
||||||
const beforeTimeMs = performance.now();
|
const beforeTimeMs = performance.now();
|
||||||
@@ -406,6 +424,7 @@ export function findOptimalRustPathFromSamples(
|
|||||||
fees,
|
fees,
|
||||||
neonRouterNumSamples,
|
neonRouterNumSamples,
|
||||||
vipSourcesSet,
|
vipSourcesSet,
|
||||||
|
fillAdjustor,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!paths) {
|
if (!paths) {
|
||||||
@@ -415,7 +434,7 @@ export function findOptimalRustPathFromSamples(
|
|||||||
|
|
||||||
const { allSourcesPath, vipSourcesPath } = paths;
|
const { allSourcesPath, vipSourcesPath } = paths;
|
||||||
|
|
||||||
if (!allSourcesPath || vipSourcesPath?.isBetterThan(allSourcesPath)) {
|
if (!allSourcesPath || vipSourcesPath?.isAdjustedBetterThan(allSourcesPath)) {
|
||||||
sendMetrics();
|
sendMetrics();
|
||||||
return vipSourcesPath;
|
return vipSourcesPath;
|
||||||
}
|
}
|
||||||
@@ -423,143 +442,3 @@ export function findOptimalRustPathFromSamples(
|
|||||||
sendMetrics();
|
sendMetrics();
|
||||||
return allSourcesPath;
|
return allSourcesPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the optimal mixture of fills that maximizes (for sells) or minimizes
|
|
||||||
* (for buys) output, while meeting the input requirement.
|
|
||||||
*/
|
|
||||||
export async function findOptimalPathJSAsync(
|
|
||||||
side: MarketOperation,
|
|
||||||
fills: Fill[][],
|
|
||||||
targetInput: BigNumber,
|
|
||||||
runLimit: number = 2 ** 8,
|
|
||||||
samplerMetrics?: SamplerMetrics,
|
|
||||||
opts: PathPenaltyOpts = DEFAULT_PATH_PENALTY_OPTS,
|
|
||||||
): Promise<Path | undefined> {
|
|
||||||
const beforeTimeMs = performance.now();
|
|
||||||
// Sort fill arrays by descending adjusted completed rate.
|
|
||||||
// Remove any paths which cannot impact the optimal path
|
|
||||||
const sortedPaths = reducePaths(fillsToSortedPaths(fills, side, targetInput, opts), side);
|
|
||||||
if (sortedPaths.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const rates = rateBySourcePathId(sortedPaths);
|
|
||||||
let optimalPath = sortedPaths[0];
|
|
||||||
for (const [i, path] of sortedPaths.slice(1).entries()) {
|
|
||||||
optimalPath = mixPaths(side, optimalPath, path, targetInput, runLimit * RUN_LIMIT_DECAY_FACTOR ** i, rates);
|
|
||||||
// Yield to event loop.
|
|
||||||
await Promise.resolve();
|
|
||||||
}
|
|
||||||
const finalPath = optimalPath.isComplete() ? optimalPath : undefined;
|
|
||||||
// tslint:disable-next-line: no-unused-expression
|
|
||||||
samplerMetrics &&
|
|
||||||
samplerMetrics.logRouterDetails({
|
|
||||||
router: 'js',
|
|
||||||
type: 'total',
|
|
||||||
timingMs: performance.now() - beforeTimeMs,
|
|
||||||
});
|
|
||||||
return finalPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort fill arrays by descending adjusted completed rate.
|
|
||||||
export function fillsToSortedPaths(
|
|
||||||
fills: Fill[][],
|
|
||||||
side: MarketOperation,
|
|
||||||
targetInput: BigNumber,
|
|
||||||
opts: PathPenaltyOpts,
|
|
||||||
): Path[] {
|
|
||||||
const paths = fills.map(singleSourceFills => Path.create(side, singleSourceFills, targetInput, opts));
|
|
||||||
const sortedPaths = paths.sort((a, b) => {
|
|
||||||
const aRate = a.adjustedCompleteRate();
|
|
||||||
const bRate = b.adjustedCompleteRate();
|
|
||||||
// There is a case where the adjusted completed rate isn't sufficient for the desired amount
|
|
||||||
// resulting in a NaN div by 0 (output)
|
|
||||||
if (bRate.isNaN()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (aRate.isNaN()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return bRate.comparedTo(aRate);
|
|
||||||
});
|
|
||||||
return sortedPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove paths which have no impact on the optimal path
|
|
||||||
export function reducePaths(sortedPaths: Path[], side: MarketOperation): Path[] {
|
|
||||||
// Any path which has a min rate that is less than the best adjusted completed rate has no chance of improving
|
|
||||||
// the overall route.
|
|
||||||
const bestNonNativeCompletePath = sortedPaths.filter(
|
|
||||||
p => p.isComplete() && p.fills[0].source !== ERC20BridgeSource.Native,
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
// If there is no complete path then just go ahead with the sorted paths
|
|
||||||
// I.e if the token only exists on sources which cannot sell to infinity
|
|
||||||
// or buys where X is greater than all the tokens available in the pools
|
|
||||||
if (!bestNonNativeCompletePath) {
|
|
||||||
return sortedPaths;
|
|
||||||
}
|
|
||||||
const bestNonNativeCompletePathAdjustedRate = bestNonNativeCompletePath.adjustedCompleteRate();
|
|
||||||
if (!bestNonNativeCompletePathAdjustedRate.isGreaterThan(0)) {
|
|
||||||
return sortedPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredPaths = sortedPaths.filter(p =>
|
|
||||||
p.bestRate().isGreaterThanOrEqualTo(bestNonNativeCompletePathAdjustedRate),
|
|
||||||
);
|
|
||||||
return filteredPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mixPaths(
|
|
||||||
side: MarketOperation,
|
|
||||||
pathA: Path,
|
|
||||||
pathB: Path,
|
|
||||||
targetInput: BigNumber,
|
|
||||||
maxSteps: number,
|
|
||||||
rates: { [id: string]: BigNumber },
|
|
||||||
): Path {
|
|
||||||
const _maxSteps = Math.max(maxSteps, 32);
|
|
||||||
let steps = 0;
|
|
||||||
// We assume pathA is the better of the two initially.
|
|
||||||
let bestPath: Path = pathA;
|
|
||||||
|
|
||||||
const _walk = (path: Path, remainingFills: Fill[]) => {
|
|
||||||
steps += 1;
|
|
||||||
if (path.isBetterThan(bestPath)) {
|
|
||||||
bestPath = path;
|
|
||||||
}
|
|
||||||
const remainingInput = targetInput.minus(path.size().input);
|
|
||||||
if (remainingInput.isGreaterThan(0)) {
|
|
||||||
for (let i = 0; i < remainingFills.length && steps < _maxSteps; ++i) {
|
|
||||||
const fill = remainingFills[i];
|
|
||||||
// Only walk valid paths.
|
|
||||||
if (!path.isValidNextFill(fill)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Remove this fill from the next list of candidate fills.
|
|
||||||
const nextRemainingFills = remainingFills.slice();
|
|
||||||
nextRemainingFills.splice(i, 1);
|
|
||||||
// Recurse.
|
|
||||||
_walk(Path.clone(path).append(fill), nextRemainingFills);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const allFills = [...pathA.fills, ...pathB.fills];
|
|
||||||
// Sort subpaths by rate and keep fills contiguous to improve our
|
|
||||||
// chances of walking ideal, valid paths first.
|
|
||||||
const sortedFills = allFills.sort((a, b) => {
|
|
||||||
if (a.sourcePathId !== b.sourcePathId) {
|
|
||||||
return rates[b.sourcePathId].comparedTo(rates[a.sourcePathId]);
|
|
||||||
}
|
|
||||||
return a.index - b.index;
|
|
||||||
});
|
|
||||||
_walk(Path.create(side, [], targetInput, pathA.pathPenaltyOpts), sortedFills);
|
|
||||||
if (!bestPath.isValid()) {
|
|
||||||
throw new Error('nooope');
|
|
||||||
}
|
|
||||||
return bestPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rateBySourcePathId(paths: Path[]): { [id: string]: BigNumber } {
|
|
||||||
return _.fromPairs(paths.map(p => [p.fills[0].sourcePathId, p.adjustedRate()]));
|
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,20 @@
|
|||||||
|
import { FillQuoteTransformerOrderType } from '@0x/protocol-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { MarketOperation } from '../../types';
|
import { MarketOperation } from '../../types';
|
||||||
|
|
||||||
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
import { SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
|
||||||
import { DexSample, ERC20BridgeSource, ExchangeProxyOverhead, FeeSchedule, MultiHopFillData } from './types';
|
import { adjustOutput } from './fills';
|
||||||
|
import { IdentityFillAdjustor } from './identity_fill_adjustor';
|
||||||
|
import {
|
||||||
|
DexSample,
|
||||||
|
ERC20BridgeSource,
|
||||||
|
ExchangeProxyOverhead,
|
||||||
|
FeeSchedule,
|
||||||
|
Fill,
|
||||||
|
FillAdjustor,
|
||||||
|
MultiHopFillData,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
// tslint:disable:no-bitwise
|
// tslint:disable:no-bitwise
|
||||||
|
|
||||||
@@ -18,20 +29,55 @@ export function getTwoHopAdjustedRate(
|
|||||||
outputAmountPerEth: BigNumber,
|
outputAmountPerEth: BigNumber,
|
||||||
fees: FeeSchedule = {},
|
fees: FeeSchedule = {},
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
exchangeProxyOverhead: ExchangeProxyOverhead = () => ZERO_AMOUNT,
|
||||||
|
fillAdjustor: FillAdjustor = new IdentityFillAdjustor(),
|
||||||
): BigNumber {
|
): BigNumber {
|
||||||
const { output, input, fillData } = twoHopQuote;
|
const { output, input, fillData } = twoHopQuote;
|
||||||
if (input.isLessThan(targetInput) || output.isZero()) {
|
if (input.isLessThan(targetInput) || output.isZero()) {
|
||||||
return ZERO_AMOUNT;
|
return ZERO_AMOUNT;
|
||||||
}
|
}
|
||||||
const penalty = outputAmountPerEth.times(
|
|
||||||
exchangeProxyOverhead(
|
// Flags to indicate which sources are used
|
||||||
|
const flags =
|
||||||
SOURCE_FLAGS.MultiHop |
|
SOURCE_FLAGS.MultiHop |
|
||||||
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
SOURCE_FLAGS[fillData.firstHopSource.source] |
|
||||||
SOURCE_FLAGS[fillData.secondHopSource.source],
|
SOURCE_FLAGS[fillData.secondHopSource.source];
|
||||||
).plus(fees[ERC20BridgeSource.MultiHop]!(fillData)),
|
|
||||||
);
|
// Penalty of going to those sources in terms of output
|
||||||
const adjustedOutput = side === MarketOperation.Sell ? output.minus(penalty) : output.plus(penalty);
|
const sourcePenalty = outputAmountPerEth.times(fees[ERC20BridgeSource.MultiHop]!(fillData).fee).integerValue();
|
||||||
return side === MarketOperation.Sell ? adjustedOutput.div(input) : input.div(adjustedOutput);
|
|
||||||
|
// Create a Fill so it can be adjusted by the `FillAdjustor`
|
||||||
|
const fill: Fill = {
|
||||||
|
...twoHopQuote,
|
||||||
|
flags,
|
||||||
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
adjustedOutput: adjustOutput(side, twoHopQuote.output, sourcePenalty),
|
||||||
|
sourcePathId: `${ERC20BridgeSource.MultiHop}-${fillData.firstHopSource.source}-${fillData.secondHopSource.source}`,
|
||||||
|
// We don't have this information at this stage
|
||||||
|
gas: 0,
|
||||||
|
};
|
||||||
|
// Adjust the individual Fill
|
||||||
|
// HACK: Chose the worst of slippage between the two sources in multihop
|
||||||
|
const adjustedOutputLeft = fillAdjustor.adjustFills(
|
||||||
|
side,
|
||||||
|
[{ ...fill, source: fillData.firstHopSource.source }],
|
||||||
|
targetInput,
|
||||||
|
)[0].adjustedOutput;
|
||||||
|
const adjustedOutputRight = fillAdjustor.adjustFills(
|
||||||
|
side,
|
||||||
|
[{ ...fill, source: fillData.secondHopSource.source }],
|
||||||
|
targetInput,
|
||||||
|
)[0].adjustedOutput;
|
||||||
|
// In Sells, output smaller is worse (you're getting less out)
|
||||||
|
// In Buys, output larger is worse (it's costing you more)
|
||||||
|
const fillAdjustedOutput =
|
||||||
|
side === MarketOperation.Sell
|
||||||
|
? BigNumber.min(adjustedOutputLeft, adjustedOutputRight)
|
||||||
|
: BigNumber.max(adjustedOutputLeft, adjustedOutputRight);
|
||||||
|
|
||||||
|
const pathPenalty = outputAmountPerEth.times(exchangeProxyOverhead(flags)).integerValue();
|
||||||
|
const pathAdjustedOutput = adjustOutput(side, fillAdjustedOutput, pathPenalty);
|
||||||
|
|
||||||
|
return getRate(side, input, pathAdjustedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +105,8 @@ export function getCompleteRate(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the rate given the input/output of a path.
|
* Computes the rate given the input/output of a path.
|
||||||
|
*
|
||||||
|
* If it is a sell, output/input. If it is a buy, input/output.
|
||||||
*/
|
*/
|
||||||
export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
|
export function getRate(side: MarketOperation, input: BigNumber, output: BigNumber): BigNumber {
|
||||||
if (input.eq(0) || output.eq(0)) {
|
if (input.eq(0) || output.eq(0)) {
|
||||||
|
@@ -75,6 +75,7 @@ import {
|
|||||||
DexSample,
|
DexSample,
|
||||||
DODOFillData,
|
DODOFillData,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
FeeSchedule,
|
||||||
GeistFillData,
|
GeistFillData,
|
||||||
GeistInfo,
|
GeistInfo,
|
||||||
GenericRouterFillData,
|
GenericRouterFillData,
|
||||||
@@ -475,62 +476,6 @@ export class SamplerOperations {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSmoothySellQuotes(
|
|
||||||
pool: CurveInfo,
|
|
||||||
fromTokenIdx: number,
|
|
||||||
toTokenIdx: number,
|
|
||||||
takerFillAmounts: BigNumber[],
|
|
||||||
): SourceQuoteOperation<CurveFillData> {
|
|
||||||
return new SamplerContractOperation({
|
|
||||||
source: ERC20BridgeSource.Smoothy,
|
|
||||||
fillData: {
|
|
||||||
pool,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
},
|
|
||||||
contract: this._samplerContract,
|
|
||||||
function: this._samplerContract.sampleSellsFromSmoothy,
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
poolAddress: pool.poolAddress,
|
|
||||||
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
|
|
||||||
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
|
|
||||||
},
|
|
||||||
new BigNumber(fromTokenIdx),
|
|
||||||
new BigNumber(toTokenIdx),
|
|
||||||
takerFillAmounts,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSmoothyBuyQuotes(
|
|
||||||
pool: CurveInfo,
|
|
||||||
fromTokenIdx: number,
|
|
||||||
toTokenIdx: number,
|
|
||||||
makerFillAmounts: BigNumber[],
|
|
||||||
): SourceQuoteOperation<CurveFillData> {
|
|
||||||
return new SamplerContractOperation({
|
|
||||||
source: ERC20BridgeSource.Smoothy,
|
|
||||||
fillData: {
|
|
||||||
pool,
|
|
||||||
fromTokenIdx,
|
|
||||||
toTokenIdx,
|
|
||||||
},
|
|
||||||
contract: this._samplerContract,
|
|
||||||
function: this._samplerContract.sampleBuysFromSmoothy,
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
poolAddress: pool.poolAddress,
|
|
||||||
sellQuoteFunctionSelector: pool.sellQuoteFunctionSelector,
|
|
||||||
buyQuoteFunctionSelector: pool.buyQuoteFunctionSelector,
|
|
||||||
},
|
|
||||||
new BigNumber(fromTokenIdx),
|
|
||||||
new BigNumber(toTokenIdx),
|
|
||||||
makerFillAmounts,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getBalancerV2MultihopSellQuotes(
|
public getBalancerV2MultihopSellQuotes(
|
||||||
vault: string,
|
vault: string,
|
||||||
quoteSwaps: BalancerSwapInfo, // Should always be sell swap steps.
|
quoteSwaps: BalancerSwapInfo, // Should always be sell swap steps.
|
||||||
@@ -1365,16 +1310,22 @@ export class SamplerOperations {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMedianSellRate(
|
/**
|
||||||
|
* Returns the best price for the native token
|
||||||
|
* Best is calculated according to the fee schedule, so the price of the
|
||||||
|
* best source, fee adjusted, will be returned.
|
||||||
|
*/
|
||||||
|
public getBestNativeTokenSellRate(
|
||||||
sources: ERC20BridgeSource[],
|
sources: ERC20BridgeSource[],
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
nativeToken: string,
|
||||||
takerFillAmount: BigNumber,
|
nativeFillAmount: BigNumber,
|
||||||
|
feeSchedule: FeeSchedule,
|
||||||
): BatchedOperation<BigNumber> {
|
): BatchedOperation<BigNumber> {
|
||||||
if (makerToken.toLowerCase() === takerToken.toLowerCase()) {
|
if (makerToken.toLowerCase() === nativeToken.toLowerCase()) {
|
||||||
return SamplerOperations.constant(new BigNumber(1));
|
return SamplerOperations.constant(new BigNumber(1));
|
||||||
}
|
}
|
||||||
const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, [takerFillAmount], {
|
const subOps = this._getSellQuoteOperations(sources, makerToken, nativeToken, [nativeFillAmount], {
|
||||||
default: [],
|
default: [],
|
||||||
});
|
});
|
||||||
return this._createBatch(
|
return this._createBatch(
|
||||||
@@ -1383,15 +1334,35 @@ export class SamplerOperations {
|
|||||||
if (samples.length === 0) {
|
if (samples.length === 0) {
|
||||||
return ZERO_AMOUNT;
|
return ZERO_AMOUNT;
|
||||||
}
|
}
|
||||||
const flatSortedSamples = samples
|
|
||||||
.reduce((acc, v) => acc.concat(...v))
|
const adjustedPrices = subOps.map((s, i) => {
|
||||||
.filter(v => !v.isZero())
|
// If the source gave us nothing, skip it and return a default
|
||||||
.sort((a, b) => a.comparedTo(b));
|
if (samples[i].length === 0 || samples[i][0].isZero()) {
|
||||||
if (flatSortedSamples.length === 0) {
|
return { adjustedPrice: ZERO_AMOUNT, source: s.source, price: ZERO_AMOUNT };
|
||||||
return ZERO_AMOUNT;
|
|
||||||
}
|
}
|
||||||
const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
|
const v = samples[i][0];
|
||||||
return medianSample.div(takerFillAmount);
|
const price = v.dividedBy(nativeFillAmount);
|
||||||
|
// Create an adjusted price to avoid selecting the following:
|
||||||
|
// * a source that is too expensive to arbitrage given the gas environment
|
||||||
|
// * when a number of sources are poorly priced or liquidity is low
|
||||||
|
|
||||||
|
// Fee is already gas * gasPrice
|
||||||
|
const fee = feeSchedule[subOps[i].source]
|
||||||
|
? feeSchedule[subOps[i].source]!(subOps[i].fillData).fee
|
||||||
|
: ZERO_AMOUNT;
|
||||||
|
const adjustedNativeAmount = nativeFillAmount.plus(fee);
|
||||||
|
const adjustedPrice = v.div(adjustedNativeAmount);
|
||||||
|
return {
|
||||||
|
adjustedPrice,
|
||||||
|
source: subOps[i].source,
|
||||||
|
price,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedPrices = adjustedPrices.sort((a, b) => a.adjustedPrice.comparedTo(b.adjustedPrice));
|
||||||
|
const selectedPrice = sortedPrices[sortedPrices.length - 1].price;
|
||||||
|
|
||||||
|
return selectedPrice;
|
||||||
},
|
},
|
||||||
() => ZERO_AMOUNT,
|
() => ZERO_AMOUNT,
|
||||||
);
|
);
|
||||||
@@ -1481,7 +1452,6 @@ export class SamplerOperations {
|
|||||||
case ERC20BridgeSource.ApeSwap:
|
case ERC20BridgeSource.ApeSwap:
|
||||||
case ERC20BridgeSource.CheeseSwap:
|
case ERC20BridgeSource.CheeseSwap:
|
||||||
case ERC20BridgeSource.QuickSwap:
|
case ERC20BridgeSource.QuickSwap:
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
case ERC20BridgeSource.Dfyn:
|
||||||
case ERC20BridgeSource.WaultSwap:
|
case ERC20BridgeSource.WaultSwap:
|
||||||
case ERC20BridgeSource.ShibaSwap:
|
case ERC20BridgeSource.ShibaSwap:
|
||||||
@@ -1531,15 +1501,6 @@ export class SamplerOperations {
|
|||||||
source,
|
source,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
|
||||||
this.getSmoothySellQuotes(
|
|
||||||
pool,
|
|
||||||
pool.tokens.indexOf(takerToken),
|
|
||||||
pool.tokens.indexOf(makerToken),
|
|
||||||
takerFillAmounts,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.Shell:
|
case ERC20BridgeSource.Shell:
|
||||||
case ERC20BridgeSource.Component:
|
case ERC20BridgeSource.Component:
|
||||||
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||||
@@ -1829,7 +1790,6 @@ export class SamplerOperations {
|
|||||||
case ERC20BridgeSource.ApeSwap:
|
case ERC20BridgeSource.ApeSwap:
|
||||||
case ERC20BridgeSource.CheeseSwap:
|
case ERC20BridgeSource.CheeseSwap:
|
||||||
case ERC20BridgeSource.QuickSwap:
|
case ERC20BridgeSource.QuickSwap:
|
||||||
case ERC20BridgeSource.ComethSwap:
|
|
||||||
case ERC20BridgeSource.Dfyn:
|
case ERC20BridgeSource.Dfyn:
|
||||||
case ERC20BridgeSource.WaultSwap:
|
case ERC20BridgeSource.WaultSwap:
|
||||||
case ERC20BridgeSource.ShibaSwap:
|
case ERC20BridgeSource.ShibaSwap:
|
||||||
@@ -1879,15 +1839,6 @@ export class SamplerOperations {
|
|||||||
source,
|
source,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case ERC20BridgeSource.Smoothy:
|
|
||||||
return getCurveLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
|
||||||
this.getSmoothyBuyQuotes(
|
|
||||||
pool,
|
|
||||||
pool.tokens.indexOf(takerToken),
|
|
||||||
pool.tokens.indexOf(makerToken),
|
|
||||||
makerFillAmounts,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.Shell:
|
case ERC20BridgeSource.Shell:
|
||||||
case ERC20BridgeSource.Component:
|
case ERC20BridgeSource.Component:
|
||||||
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
return getShellLikeInfosForPair(this.chainId, takerToken, makerToken, source).map(pool =>
|
||||||
|
@@ -55,7 +55,6 @@ export enum ERC20BridgeSource {
|
|||||||
DodoV2 = 'DODO_V2',
|
DodoV2 = 'DODO_V2',
|
||||||
CryptoCom = 'CryptoCom',
|
CryptoCom = 'CryptoCom',
|
||||||
KyberDmm = 'KyberDMM',
|
KyberDmm = 'KyberDMM',
|
||||||
Smoothy = 'Smoothy',
|
|
||||||
Component = 'Component',
|
Component = 'Component',
|
||||||
Saddle = 'Saddle',
|
Saddle = 'Saddle',
|
||||||
XSigma = 'xSigma',
|
XSigma = 'xSigma',
|
||||||
@@ -82,7 +81,6 @@ export enum ERC20BridgeSource {
|
|||||||
ACryptos = 'ACryptoS',
|
ACryptos = 'ACryptoS',
|
||||||
// Polygon only
|
// Polygon only
|
||||||
QuickSwap = 'QuickSwap',
|
QuickSwap = 'QuickSwap',
|
||||||
ComethSwap = 'ComethSwap',
|
|
||||||
Dfyn = 'Dfyn',
|
Dfyn = 'Dfyn',
|
||||||
WaultSwap = 'WaultSwap',
|
WaultSwap = 'WaultSwap',
|
||||||
FirebirdOneSwap = 'FirebirdOneSwap',
|
FirebirdOneSwap = 'FirebirdOneSwap',
|
||||||
@@ -132,7 +130,7 @@ export enum CurveFunctionSelectors {
|
|||||||
exchange_underlying_v2 = '0x65b2489b',
|
exchange_underlying_v2 = '0x65b2489b',
|
||||||
get_dy_v2 = '0x556d6e9f',
|
get_dy_v2 = '0x556d6e9f',
|
||||||
get_dy_underlying_v2 = '0x85f11d1e',
|
get_dy_underlying_v2 = '0x85f11d1e',
|
||||||
// Smoothy
|
// Smoothy(deprecated)
|
||||||
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
swap_uint256 = '0x5673b02d', // swap(uint256,uint256,uint256,uint256)
|
||||||
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
get_swap_amount = '0x45cf2ef6', // getSwapAmount(uint256,uint256,uint256)
|
||||||
// Nerve BSC, Saddle Mainnet, Synapse
|
// Nerve BSC, Saddle Mainnet, Synapse
|
||||||
@@ -403,45 +401,10 @@ export interface Fill<TFillData extends FillData = FillData> {
|
|||||||
output: BigNumber;
|
output: BigNumber;
|
||||||
// The output fill amount, adjusted by fees.
|
// The output fill amount, adjusted by fees.
|
||||||
adjustedOutput: BigNumber;
|
adjustedOutput: BigNumber;
|
||||||
// Fill that must precede this one. This enforces certain fills to be contiguous.
|
// The expected gas cost of this fill
|
||||||
parent?: Fill;
|
gas: number;
|
||||||
// The index of the fill in the original path.
|
|
||||||
index: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents continguous fills on a path that have been merged together.
|
|
||||||
*/
|
|
||||||
export interface CollapsedFill<TFillData extends FillData = FillData> {
|
|
||||||
source: ERC20BridgeSource;
|
|
||||||
type: FillQuoteTransformerOrderType; // should correspond with TFillData
|
|
||||||
fillData: 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;
|
|
||||||
/**
|
|
||||||
* Total input amount (sum of `subFill`s)
|
|
||||||
*/
|
|
||||||
input: BigNumber;
|
|
||||||
/**
|
|
||||||
* Total output amount (sum of `subFill`s)
|
|
||||||
*/
|
|
||||||
output: BigNumber;
|
|
||||||
/**
|
|
||||||
* Quantities of all the fills that were collapsed.
|
|
||||||
*/
|
|
||||||
subFills: Array<{
|
|
||||||
input: BigNumber;
|
|
||||||
output: BigNumber;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `CollapsedFill` wrapping a native order.
|
|
||||||
*/
|
|
||||||
export interface NativeCollapsedFill extends CollapsedFill<NativeFillData> {}
|
|
||||||
|
|
||||||
export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> {
|
export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData> {
|
||||||
source: ERC20BridgeSource;
|
source: ERC20BridgeSource;
|
||||||
fillData: TFillData;
|
fillData: TFillData;
|
||||||
@@ -450,24 +413,21 @@ export interface OptimizedMarketOrderBase<TFillData extends FillData = FillData>
|
|||||||
takerToken: string;
|
takerToken: string;
|
||||||
makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill
|
makerAmount: BigNumber; // The amount we wish to buy from this order, e.g inclusive of any previous partial fill
|
||||||
takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill
|
takerAmount: BigNumber; // The amount we wish to fill this for, e.g inclusive of any previous partial fill
|
||||||
fills: CollapsedFill[];
|
fill: Omit<Fill, 'flags' | 'fillData' | 'sourcePathId' | 'source' | 'type'>; // Remove duplicates which have been brought into the OrderBase interface
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
|
export interface OptimizedMarketBridgeOrder<TFillData extends FillData = FillData>
|
||||||
extends OptimizedMarketOrderBase<TFillData> {
|
extends OptimizedMarketOrderBase<TFillData> {
|
||||||
type: FillQuoteTransformerOrderType.Bridge;
|
type: FillQuoteTransformerOrderType.Bridge;
|
||||||
fillData: TFillData;
|
|
||||||
sourcePathId: string;
|
sourcePathId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
export interface OptimizedLimitOrder extends OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||||
type: FillQuoteTransformerOrderType.Limit;
|
type: FillQuoteTransformerOrderType.Limit;
|
||||||
fillData: NativeLimitOrderFillData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrderFillData> {
|
||||||
type: FillQuoteTransformerOrderType.Rfq;
|
type: FillQuoteTransformerOrderType.Rfq;
|
||||||
fillData: NativeRfqOrderFillData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -484,8 +444,12 @@ export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
|||||||
firmQuoteValidator?: RfqFirmQuoteValidator;
|
firmQuoteValidator?: RfqFirmQuoteValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeeEstimate = (fillData: FillData) => number | BigNumber;
|
export type FeeEstimate = (fillData: FillData) => { gas: number; fee: BigNumber };
|
||||||
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
|
export type FeeSchedule = Partial<{ [key in ERC20BridgeSource]: FeeEstimate }>;
|
||||||
|
|
||||||
|
export type GasEstimate = (fillData: FillData) => number;
|
||||||
|
export type GasSchedule = Partial<{ [key in ERC20BridgeSource]: GasEstimate }>;
|
||||||
|
|
||||||
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
export type ExchangeProxyOverhead = (sourceFlags: bigint) => BigNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -549,7 +513,7 @@ export interface GetMarketOrdersOpts {
|
|||||||
/**
|
/**
|
||||||
* Estimated gas consumed by each liquidity source.
|
* Estimated gas consumed by each liquidity source.
|
||||||
*/
|
*/
|
||||||
gasSchedule: FeeSchedule;
|
gasSchedule: GasSchedule;
|
||||||
exchangeProxyOverhead: ExchangeProxyOverhead;
|
exchangeProxyOverhead: ExchangeProxyOverhead;
|
||||||
/**
|
/**
|
||||||
* Whether to pad the quote with a redundant fallback quote using different
|
* Whether to pad the quote with a redundant fallback quote using different
|
||||||
@@ -584,6 +548,11 @@ export interface GetMarketOrdersOpts {
|
|||||||
* Sampler metrics for recording data on the sampler service and operations
|
* Sampler metrics for recording data on the sampler service and operations
|
||||||
*/
|
*/
|
||||||
samplerMetrics?: SamplerMetrics;
|
samplerMetrics?: SamplerMetrics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts fills individual fills based on caller supplied criteria
|
||||||
|
*/
|
||||||
|
fillAdjustor: FillAdjustor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SamplerMetrics {
|
export interface SamplerMetrics {
|
||||||
@@ -629,7 +598,7 @@ export interface SourceQuoteOperation<TFillData extends FillData = FillData> ext
|
|||||||
export interface OptimizerResult {
|
export interface OptimizerResult {
|
||||||
optimizedOrders: OptimizedMarketOrder[];
|
optimizedOrders: OptimizedMarketOrder[];
|
||||||
sourceFlags: bigint;
|
sourceFlags: bigint;
|
||||||
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
|
liquidityDelivered: Readonly<Fill[] | DexSample<MultiHopFillData>>;
|
||||||
marketSideLiquidity: MarketSideLiquidity;
|
marketSideLiquidity: MarketSideLiquidity;
|
||||||
adjustedRate: BigNumber;
|
adjustedRate: BigNumber;
|
||||||
takerAmountPerEth: BigNumber;
|
takerAmountPerEth: BigNumber;
|
||||||
@@ -697,8 +666,13 @@ export interface GenerateOptimizedOrdersOpts {
|
|||||||
gasPrice: BigNumber;
|
gasPrice: BigNumber;
|
||||||
neonRouterNumSamples: number;
|
neonRouterNumSamples: number;
|
||||||
samplerMetrics?: SamplerMetrics;
|
samplerMetrics?: SamplerMetrics;
|
||||||
|
fillAdjustor: FillAdjustor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComparisonPrice {
|
export interface ComparisonPrice {
|
||||||
wholeOrder: BigNumber | undefined;
|
wholeOrder: BigNumber | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FillAdjustor {
|
||||||
|
adjustFills: (side: MarketOperation, fills: Fill[], amount: BigNumber) => Fill[];
|
||||||
|
}
|
||||||
|
@@ -5,12 +5,11 @@ import _ = require('lodash');
|
|||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
|
import { MarketOperation, NativeOrderWithFillableAmounts } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CollapsedFill,
|
|
||||||
DexSample,
|
DexSample,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
Fill,
|
||||||
FillData,
|
FillData,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeCollapsedFill,
|
|
||||||
NativeFillData,
|
NativeFillData,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
@@ -123,7 +122,7 @@ export interface PriceComparisonsReport {
|
|||||||
export function generateQuoteReport(
|
export function generateQuoteReport(
|
||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
nativeOrders: NativeOrderWithFillableAmounts[],
|
nativeOrders: NativeOrderWithFillableAmounts[],
|
||||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
liquidityDelivered: ReadonlyArray<Fill> | DexSample<MultiHopFillData>,
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
): QuoteReport {
|
): QuoteReport {
|
||||||
@@ -174,7 +173,7 @@ export function generateQuoteReport(
|
|||||||
export function generateExtendedQuoteReportSources(
|
export function generateExtendedQuoteReportSources(
|
||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
quotes: RawQuotes,
|
quotes: RawQuotes,
|
||||||
liquidityDelivered: ReadonlyArray<CollapsedFill> | DexSample<MultiHopFillData>,
|
liquidityDelivered: ReadonlyArray<Fill> | DexSample<MultiHopFillData>,
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
@@ -343,7 +342,7 @@ export function multiHopSampleToReportSource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollapsedFill {
|
function _isNativeOrderFromCollapsedFill(cf: Fill): cf is Fill<NativeFillData> {
|
||||||
const { type } = cf;
|
const { type } = cf;
|
||||||
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
|
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import { BigNumber } from '@0x/utils';
|
|||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import { MarketOperation } from '../types';
|
import { MarketOperation } from '../types';
|
||||||
|
|
||||||
import { FeeSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
import { GasSchedule, NativeLimitOrderFillData, OptimizedMarketOrder } from './market_operation_utils/types';
|
||||||
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
import { getNativeAdjustedTakerFeeAmount } from './utils';
|
||||||
|
|
||||||
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
const { PROTOCOL_FEE_MULTIPLIER, ZERO_AMOUNT } = constants;
|
||||||
@@ -72,7 +72,7 @@ export interface QuoteFillInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QuoteFillInfoOpts {
|
export interface QuoteFillInfoOpts {
|
||||||
gasSchedule: FeeSchedule;
|
gasSchedule: GasSchedule;
|
||||||
protocolFeeMultiplier: BigNumber;
|
protocolFeeMultiplier: BigNumber;
|
||||||
slippage: number;
|
slippage: number;
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ export function fillQuoteOrders(
|
|||||||
fillOrders: QuoteFillOrderCall[],
|
fillOrders: QuoteFillOrderCall[],
|
||||||
inputAmount: BigNumber,
|
inputAmount: BigNumber,
|
||||||
protocolFeePerFillOrder: BigNumber,
|
protocolFeePerFillOrder: BigNumber,
|
||||||
gasSchedule: FeeSchedule,
|
gasSchedule: GasSchedule,
|
||||||
): IntermediateQuoteFillResult {
|
): IntermediateQuoteFillResult {
|
||||||
const result: IntermediateQuoteFillResult = {
|
const result: IntermediateQuoteFillResult = {
|
||||||
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
...EMPTY_QUOTE_INTERMEDIATE_FILL_RESULT,
|
||||||
@@ -151,28 +151,18 @@ export function fillQuoteOrders(
|
|||||||
if (remainingInput.lte(0)) {
|
if (remainingInput.lte(0)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (const fill of fo.order.fills) {
|
const { source, fillData } = fo.order;
|
||||||
if (remainingInput.lte(0)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const { source, fillData } = fill;
|
|
||||||
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
const gas = gasSchedule[source] === undefined ? 0 : gasSchedule[source]!(fillData);
|
||||||
result.gas += new BigNumber(gas).toNumber();
|
result.gas += new BigNumber(gas).toNumber();
|
||||||
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
result.inputBySource[source] = result.inputBySource[source] || ZERO_AMOUNT;
|
||||||
|
|
||||||
// Actual rates are rarely linear, so fill subfills individually to
|
|
||||||
// get a better approximation of fill size.
|
|
||||||
for (const subFill of fill.subFills) {
|
|
||||||
if (remainingInput.lte(0)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const filledInput = solveForInputFillAmount(
|
const filledInput = solveForInputFillAmount(
|
||||||
remainingInput,
|
remainingInput,
|
||||||
subFill.input,
|
fo.order.fill.input,
|
||||||
fo.totalOrderInput,
|
fo.totalOrderInput,
|
||||||
fo.totalOrderInputFee,
|
fo.totalOrderInputFee,
|
||||||
);
|
);
|
||||||
const filledOutput = subFill.output.times(filledInput.div(subFill.input));
|
const filledOutput = fo.order.fill.output.times(filledInput.div(fo.order.fill.input));
|
||||||
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
const filledInputFee = filledInput.div(fo.totalOrderInput).times(fo.totalOrderInputFee);
|
||||||
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
const filledOutputFee = filledOutput.div(fo.totalOrderOutput).times(fo.totalOrderOutputFee);
|
||||||
|
|
||||||
@@ -182,8 +172,6 @@ export function fillQuoteOrders(
|
|||||||
result.inputFee = result.inputFee.plus(filledInputFee);
|
result.inputFee = result.inputFee.plus(filledInputFee);
|
||||||
result.outputFee = result.outputFee.plus(filledOutputFee);
|
result.outputFee = result.outputFee.plus(filledOutputFee);
|
||||||
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
remainingInput = remainingInput.minus(filledInput.plus(filledInputFee));
|
||||||
}
|
|
||||||
}
|
|
||||||
// NOTE: V4 Limit orders have Protocol fees
|
// NOTE: V4 Limit orders have Protocol fees
|
||||||
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
|
const protocolFee = hasProtocolFee(fo.order) ? protocolFeePerFillOrder : ZERO_AMOUNT;
|
||||||
result.protocolFee = result.protocolFee.plus(protocolFee);
|
result.protocolFee = result.protocolFee.plus(protocolFee);
|
||||||
@@ -314,7 +302,7 @@ function fromIntermediateQuoteFillResult(ir: IntermediateQuoteFillResult, quoteI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: FeeSchedule): number {
|
function getTotalGasUsedByFills(fills: OptimizedMarketOrder[], gasSchedule: GasSchedule): number {
|
||||||
let gasUsed = 0;
|
let gasUsed = 0;
|
||||||
for (const f of fills) {
|
for (const f of fills) {
|
||||||
const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData);
|
const fee = gasSchedule[f.source] === undefined ? 0 : gasSchedule[f.source]!(f.fillData);
|
||||||
|
@@ -31,7 +31,6 @@ import * as IMStable from '../test/generated-artifacts/IMStable.json';
|
|||||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
||||||
import * as IPlatypus from '../test/generated-artifacts/IPlatypus.json';
|
import * as IPlatypus from '../test/generated-artifacts/IPlatypus.json';
|
||||||
import * as IShell from '../test/generated-artifacts/IShell.json';
|
import * as IShell from '../test/generated-artifacts/IShell.json';
|
||||||
import * as ISmoothy from '../test/generated-artifacts/ISmoothy.json';
|
|
||||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
||||||
import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json';
|
import * as KyberDmmSampler from '../test/generated-artifacts/KyberDmmSampler.json';
|
||||||
@@ -44,7 +43,6 @@ import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSamp
|
|||||||
import * as PlatypusSampler from '../test/generated-artifacts/PlatypusSampler.json';
|
import * as PlatypusSampler from '../test/generated-artifacts/PlatypusSampler.json';
|
||||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
||||||
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
|
import * as ShellSampler from '../test/generated-artifacts/ShellSampler.json';
|
||||||
import * as SmoothySampler from '../test/generated-artifacts/SmoothySampler.json';
|
|
||||||
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
import * as TestNativeOrderSampler from '../test/generated-artifacts/TestNativeOrderSampler.json';
|
||||||
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
import * as TwoHopSampler from '../test/generated-artifacts/TwoHopSampler.json';
|
||||||
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
||||||
@@ -78,7 +76,6 @@ export const artifacts = {
|
|||||||
PlatypusSampler: PlatypusSampler as ContractArtifact,
|
PlatypusSampler: PlatypusSampler as ContractArtifact,
|
||||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||||
ShellSampler: ShellSampler as ContractArtifact,
|
ShellSampler: ShellSampler as ContractArtifact,
|
||||||
SmoothySampler: SmoothySampler as ContractArtifact,
|
|
||||||
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
TwoHopSampler: TwoHopSampler as ContractArtifact,
|
||||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||||
@@ -96,7 +93,6 @@ export const artifacts = {
|
|||||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
IMultiBridge: IMultiBridge as ContractArtifact,
|
||||||
IPlatypus: IPlatypus as ContractArtifact,
|
IPlatypus: IPlatypus as ContractArtifact,
|
||||||
IShell: IShell as ContractArtifact,
|
IShell: IShell as ContractArtifact,
|
||||||
ISmoothy: ISmoothy as ContractArtifact,
|
|
||||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
||||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||||
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
|
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
|
||||||
|
@@ -18,7 +18,7 @@ const expect = chai.expect;
|
|||||||
const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||||
const ETH_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
const ETH_TOKEN = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||||
const GAS_PRICE = new BigNumber(50e9); // 50 gwei
|
const GAS_PRICE = new BigNumber(50e9); // 50 gwei
|
||||||
const NATIVE_ORDER_FEE = new BigNumber(220e3); // 220K gas
|
const NATIVE_ORDER_GAS = 220e3; // 220K gas
|
||||||
|
|
||||||
// DEX samples to fill in MarketSideLiquidity
|
// DEX samples to fill in MarketSideLiquidity
|
||||||
const curveSample: DexSample = {
|
const curveSample: DexSample = {
|
||||||
@@ -36,7 +36,10 @@ const uniswapSample1: DexSample = {
|
|||||||
const dexQuotes: DexSample[] = [curveSample, uniswapSample1];
|
const dexQuotes: DexSample[] = [curveSample, uniswapSample1];
|
||||||
|
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Native]: _.constant(GAS_PRICE.times(NATIVE_ORDER_FEE)),
|
[ERC20BridgeSource.Native]: _.constant({
|
||||||
|
gas: NATIVE_ORDER_GAS,
|
||||||
|
fee: GAS_PRICE.times(NATIVE_ORDER_GAS),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const exchangeProxyOverhead = (sourceFlags: bigint) => {
|
const exchangeProxyOverhead = (sourceFlags: bigint) => {
|
||||||
|
@@ -23,6 +23,8 @@ import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_
|
|||||||
import { AffiliateFeeType, MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
import { AffiliateFeeType, MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
Fill,
|
||||||
|
NativeFillData,
|
||||||
OptimizedLimitOrder,
|
OptimizedLimitOrder,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
} from '../src/utils/market_operation_utils/types';
|
} from '../src/utils/market_operation_utils/types';
|
||||||
@@ -100,7 +102,8 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
|
|||||||
takerToken: order.takerToken,
|
takerToken: order.takerToken,
|
||||||
makerAmount: order.makerAmount,
|
makerAmount: order.makerAmount,
|
||||||
takerAmount: order.takerAmount,
|
takerAmount: order.takerAmount,
|
||||||
fills: [],
|
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||||
|
fill: {} as Fill<NativeFillData>,
|
||||||
...optimizerFields,
|
...optimizerFields,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -16,16 +16,14 @@ import * as _ from 'lodash';
|
|||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
|
||||||
import { Integrator, NativeOrderWithFillableAmounts } from '../src/types';
|
import { Integrator } from '../src/types';
|
||||||
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
|
||||||
import {
|
import {
|
||||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
POSITIVE_INF,
|
|
||||||
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
SELL_SOURCE_FILTER_BY_CHAIN_ID,
|
||||||
SOURCE_FLAGS,
|
SOURCE_FLAGS,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
} from '../src/utils/market_operation_utils/constants';
|
} from '../src/utils/market_operation_utils/constants';
|
||||||
import { createFills } from '../src/utils/market_operation_utils/fills';
|
|
||||||
import { PoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
import { PoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
||||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||||
@@ -39,7 +37,6 @@ import {
|
|||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
LiquidityProviderFillData,
|
LiquidityProviderFillData,
|
||||||
MarketSideLiquidity,
|
MarketSideLiquidity,
|
||||||
NativeFillData,
|
|
||||||
OptimizedMarketBridgeOrder,
|
OptimizedMarketBridgeOrder,
|
||||||
OptimizerResultWithReport,
|
OptimizerResultWithReport,
|
||||||
TokenAdjacencyGraph,
|
TokenAdjacencyGraph,
|
||||||
@@ -272,7 +269,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetMedianRateOperation = (
|
type GetBestNativeTokenSellRateOperation = (
|
||||||
sources: ERC20BridgeSource[],
|
sources: ERC20BridgeSource[],
|
||||||
makerToken: string,
|
makerToken: string,
|
||||||
takerToken: string,
|
takerToken: string,
|
||||||
@@ -281,7 +278,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
liquidityProviderAddress?: string,
|
liquidityProviderAddress?: string,
|
||||||
) => BigNumber;
|
) => BigNumber;
|
||||||
|
|
||||||
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
|
function createGetBestNativeSellRate(rate: Numberish): GetBestNativeTokenSellRateOperation {
|
||||||
return (
|
return (
|
||||||
_sources: ERC20BridgeSource[],
|
_sources: ERC20BridgeSource[],
|
||||||
_makerToken: string,
|
_makerToken: string,
|
||||||
@@ -348,17 +345,6 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
fromTokenIdx: 0,
|
fromTokenIdx: 0,
|
||||||
toTokenIdx: 1,
|
toTokenIdx: 1,
|
||||||
},
|
},
|
||||||
[ERC20BridgeSource.Smoothy]: {
|
|
||||||
pool: {
|
|
||||||
poolAddress: randomAddress(),
|
|
||||||
tokens: [TAKER_TOKEN, MAKER_TOKEN],
|
|
||||||
exchangeFunctionSelector: hexUtils.random(4),
|
|
||||||
sellQuoteFunctionSelector: hexUtils.random(4),
|
|
||||||
buyQuoteFunctionSelector: hexUtils.random(4),
|
|
||||||
},
|
|
||||||
fromTokenIdx: 0,
|
|
||||||
toTokenIdx: 1,
|
|
||||||
},
|
|
||||||
[ERC20BridgeSource.Saddle]: {
|
[ERC20BridgeSource.Saddle]: {
|
||||||
pool: {
|
pool: {
|
||||||
poolAddress: randomAddress(),
|
poolAddress: randomAddress(),
|
||||||
@@ -399,7 +385,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
},
|
},
|
||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
|
||||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
|
||||||
getMedianSellRate: createGetMedianSellRate(1),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(1),
|
||||||
getTwoHopSellQuotes: (..._params: any[]) => [],
|
getTwoHopSellQuotes: (..._params: any[]) => [],
|
||||||
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
getTwoHopBuyQuotes: (..._params: any[]) => [],
|
||||||
isAddressContract: (..._params: any[]) => false,
|
isAddressContract: (..._params: any[]) => false,
|
||||||
@@ -632,7 +618,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
|
|
||||||
// to get a comparisonPrice, you need a feeschedule for a native order
|
// to get a comparisonPrice, you need a feeschedule for a native order
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Native]: _.constant(new BigNumber(1)),
|
[ERC20BridgeSource.Native]: _.constant({ gas: 1, fee: new BigNumber(1) }),
|
||||||
};
|
};
|
||||||
mockedQuoteRequestor
|
mockedQuoteRequestor
|
||||||
.setup(mqr => mqr.getMakerUriForSignature(TypeMoq.It.isValue(SIGNATURE)))
|
.setup(mqr => mqr.getMakerUriForSignature(TypeMoq.It.isValue(SIGNATURE)))
|
||||||
@@ -1022,7 +1008,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
const expectedMakerAmount = order.fills[0].output;
|
const expectedMakerAmount = order.fill.output;
|
||||||
const slippage = new BigNumber(1).minus(order.makerAmount.div(expectedMakerAmount.plus(1)));
|
const slippage = new BigNumber(1).minus(order.makerAmount.div(expectedMakerAmount.plus(1)));
|
||||||
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
||||||
}
|
}
|
||||||
@@ -1044,7 +1030,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
@@ -1067,15 +1053,16 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
[ERC20BridgeSource.SushiSwap]: [0.95, 0.1, 0.1, 0.1],
|
[ERC20BridgeSource.SushiSwap]: [0.95, 0.1, 0.1, 0.1],
|
||||||
};
|
};
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Native]: _.constant(
|
[ERC20BridgeSource.Native]: _.constant({
|
||||||
FILL_AMOUNT.div(4)
|
gas: 1,
|
||||||
|
fee: FILL_AMOUNT.div(4)
|
||||||
.times(nativeFeeRate)
|
.times(nativeFeeRate)
|
||||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||||
),
|
}),
|
||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||||
marketOperationUtils,
|
marketOperationUtils,
|
||||||
@@ -1084,7 +1071,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
@@ -1104,15 +1091,16 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||||
};
|
};
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
[ERC20BridgeSource.Uniswap]: _.constant({
|
||||||
FILL_AMOUNT.div(4)
|
gas: 1,
|
||||||
|
fee: FILL_AMOUNT.div(4)
|
||||||
.times(uniswapFeeRate)
|
.times(uniswapFeeRate)
|
||||||
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
|
||||||
),
|
}),
|
||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||||
marketOperationUtils,
|
marketOperationUtils,
|
||||||
@@ -1121,7 +1109,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
@@ -1139,7 +1127,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
const improvedOrdersResponse = await getMarketSellOrdersAsync(
|
||||||
marketOperationUtils,
|
marketOperationUtils,
|
||||||
@@ -1148,7 +1136,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
@@ -1175,7 +1163,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||||
const secondSources: ERC20BridgeSource[] = [];
|
const secondSources: ERC20BridgeSource[] = [];
|
||||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||||
@@ -1248,7 +1236,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_MAKER_RATE),
|
||||||
});
|
});
|
||||||
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||||
const gasPrice = 100e9; // 100 gwei
|
const gasPrice = 100e9; // 100 gwei
|
||||||
@@ -1273,7 +1261,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
||||||
expect(orderSources).to.deep.eq(expectedSources);
|
expect(orderSources).to.deep.eq(expectedSources);
|
||||||
});
|
});
|
||||||
@@ -1469,7 +1457,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
expect(improvedOrders).to.not.be.length(0);
|
expect(improvedOrders).to.not.be.length(0);
|
||||||
for (const order of improvedOrders) {
|
for (const order of improvedOrders) {
|
||||||
const expectedTakerAmount = order.fills[0].output;
|
const expectedTakerAmount = order.fill.output;
|
||||||
const slippage = order.takerAmount.div(expectedTakerAmount.plus(1)).minus(1);
|
const slippage = order.takerAmount.div(expectedTakerAmount.plus(1)).minus(1);
|
||||||
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
assertRoughlyEquals(slippage, bridgeSlippage, 1);
|
||||||
}
|
}
|
||||||
@@ -1491,7 +1479,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4 },
|
{ ...DEFAULT_OPTS, numSamples: 4 },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
@@ -1516,15 +1504,16 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
[ERC20BridgeSource.Curve]: [0.1, 0.1, 0.1, 0.1],
|
[ERC20BridgeSource.Curve]: [0.1, 0.1, 0.1, 0.1],
|
||||||
};
|
};
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Native]: _.constant(
|
[ERC20BridgeSource.Native]: _.constant({
|
||||||
FILL_AMOUNT.div(4)
|
gas: 1,
|
||||||
|
fee: FILL_AMOUNT.div(4)
|
||||||
.times(nativeFeeRate)
|
.times(nativeFeeRate)
|
||||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||||
),
|
}),
|
||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
||||||
marketOperationUtils,
|
marketOperationUtils,
|
||||||
@@ -1533,7 +1522,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Uniswap,
|
ERC20BridgeSource.Uniswap,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
@@ -1555,15 +1544,16 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
[ERC20BridgeSource.SushiSwap]: [0.92, 0.1, 0.1, 0.1],
|
[ERC20BridgeSource.SushiSwap]: [0.92, 0.1, 0.1, 0.1],
|
||||||
};
|
};
|
||||||
const feeSchedule = {
|
const feeSchedule = {
|
||||||
[ERC20BridgeSource.Uniswap]: _.constant(
|
[ERC20BridgeSource.Uniswap]: _.constant({
|
||||||
FILL_AMOUNT.div(4)
|
gas: 1,
|
||||||
|
fee: FILL_AMOUNT.div(4)
|
||||||
.times(uniswapFeeRate)
|
.times(uniswapFeeRate)
|
||||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||||
),
|
}),
|
||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||||
});
|
});
|
||||||
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
const improvedOrdersResponse = await getMarketBuyOrdersAsync(
|
||||||
marketOperationUtils,
|
marketOperationUtils,
|
||||||
@@ -1572,7 +1562,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
{ ...DEFAULT_OPTS, numSamples: 4, feeSchedule },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [
|
const expectedSources = [
|
||||||
ERC20BridgeSource.Native,
|
ERC20BridgeSource.Native,
|
||||||
ERC20BridgeSource.SushiSwap,
|
ERC20BridgeSource.SushiSwap,
|
||||||
@@ -1598,7 +1588,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
|
||||||
const secondSources: ERC20BridgeSource[] = [];
|
const secondSources: ERC20BridgeSource[] = [];
|
||||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||||
@@ -1619,7 +1609,7 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
};
|
};
|
||||||
replaceSamplerOps({
|
replaceSamplerOps({
|
||||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||||
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
|
getBestNativeTokenSellRate: createGetBestNativeSellRate(ETH_TO_TAKER_RATE),
|
||||||
});
|
});
|
||||||
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
|
||||||
const exchangeProxyOverhead = (sourceFlags: bigint) =>
|
const exchangeProxyOverhead = (sourceFlags: bigint) =>
|
||||||
@@ -1643,77 +1633,11 @@ describe('MarketOperationUtils tests', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
const improvedOrders = improvedOrdersResponse.optimizedOrders;
|
||||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
const orderSources = improvedOrders.map(o => o.source);
|
||||||
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
const expectedSources = [ERC20BridgeSource.LiquidityProvider];
|
||||||
expect(orderSources).to.deep.eq(expectedSources);
|
expect(orderSources).to.deep.eq(expectedSources);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createFills', () => {
|
|
||||||
const takerAmount = new BigNumber(5000000);
|
|
||||||
const outputAmountPerEth = new BigNumber(0.5);
|
|
||||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
|
||||||
const smallOrder: NativeOrderWithFillableAmounts = {
|
|
||||||
order: {
|
|
||||||
...new LimitOrder({
|
|
||||||
chainId: 1,
|
|
||||||
maker: 'SMALL_ORDER',
|
|
||||||
takerAmount,
|
|
||||||
makerAmount: takerAmount.times(2),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
fillableMakerAmount: takerAmount.times(2),
|
|
||||||
fillableTakerAmount: takerAmount,
|
|
||||||
fillableTakerFeeAmount: new BigNumber(0),
|
|
||||||
type: FillQuoteTransformerOrderType.Limit,
|
|
||||||
signature: SIGNATURE,
|
|
||||||
};
|
|
||||||
const largeOrder: NativeOrderWithFillableAmounts = {
|
|
||||||
order: {
|
|
||||||
...new LimitOrder({
|
|
||||||
chainId: 1,
|
|
||||||
maker: 'LARGE_ORDER',
|
|
||||||
takerAmount: smallOrder.order.takerAmount.times(2),
|
|
||||||
makerAmount: smallOrder.order.makerAmount.times(2),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
fillableTakerAmount: smallOrder.fillableTakerAmount.times(2),
|
|
||||||
fillableMakerAmount: smallOrder.fillableMakerAmount.times(2),
|
|
||||||
fillableTakerFeeAmount: new BigNumber(0),
|
|
||||||
type: FillQuoteTransformerOrderType.Limit,
|
|
||||||
signature: SIGNATURE,
|
|
||||||
};
|
|
||||||
const orders = [smallOrder, largeOrder];
|
|
||||||
const feeSchedule = {
|
|
||||||
[ERC20BridgeSource.Native]: _.constant(2e5),
|
|
||||||
};
|
|
||||||
|
|
||||||
it('penalizes native fill based on target amount when target is smaller', () => {
|
|
||||||
const path = createFills({
|
|
||||||
side: MarketOperation.Sell,
|
|
||||||
orders,
|
|
||||||
dexQuotes: [],
|
|
||||||
targetInput: takerAmount.minus(1),
|
|
||||||
outputAmountPerEth,
|
|
||||||
feeSchedule,
|
|
||||||
});
|
|
||||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
|
||||||
expect(path[0][0].input).to.be.bignumber.eq(takerAmount.minus(1));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('penalizes native fill based on available amount when target is larger', () => {
|
|
||||||
const path = createFills({
|
|
||||||
side: MarketOperation.Sell,
|
|
||||||
orders,
|
|
||||||
dexQuotes: [],
|
|
||||||
targetInput: POSITIVE_INF,
|
|
||||||
outputAmountPerEth,
|
|
||||||
feeSchedule,
|
|
||||||
});
|
|
||||||
expect((path[0][0].fillData as NativeFillData).order.maker).to.eq(largeOrder.order.maker);
|
|
||||||
expect((path[0][1].fillData as NativeFillData).order.maker).to.eq(smallOrder.order.maker);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// tslint:disable-next-line: max-file-line-count
|
// tslint:disable-next-line: max-file-line-count
|
||||||
|
@@ -1,90 +0,0 @@
|
|||||||
import { expect } from '@0x/contracts-test-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
|
|
||||||
import { MarketOperation } from '../src/types';
|
|
||||||
import { Path } from '../src/utils/market_operation_utils/path';
|
|
||||||
import { ERC20BridgeSource, Fill } from '../src/utils/market_operation_utils/types';
|
|
||||||
|
|
||||||
const createFill = (
|
|
||||||
source: ERC20BridgeSource,
|
|
||||||
index: number = 0,
|
|
||||||
input: BigNumber = new BigNumber(100),
|
|
||||||
output: BigNumber = new BigNumber(100),
|
|
||||||
): Fill =>
|
|
||||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
|
||||||
({
|
|
||||||
source,
|
|
||||||
input,
|
|
||||||
output,
|
|
||||||
adjustedOutput: output,
|
|
||||||
flags: BigInt(0),
|
|
||||||
sourcePathId: source,
|
|
||||||
index,
|
|
||||||
} as Fill);
|
|
||||||
|
|
||||||
describe('Path', () => {
|
|
||||||
it('Adds a fallback', () => {
|
|
||||||
const targetInput = new BigNumber(100);
|
|
||||||
const path = Path.create(
|
|
||||||
MarketOperation.Sell,
|
|
||||||
[createFill(ERC20BridgeSource.Native), createFill(ERC20BridgeSource.Native)],
|
|
||||||
targetInput,
|
|
||||||
);
|
|
||||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
|
||||||
path.addFallback(fallback);
|
|
||||||
const sources = path.fills.map(f => f.source);
|
|
||||||
expect(sources).to.deep.eq([ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds a fallback with LiquidityProvider', () => {
|
|
||||||
const targetInput = new BigNumber(100);
|
|
||||||
const path = Path.create(
|
|
||||||
MarketOperation.Sell,
|
|
||||||
[createFill(ERC20BridgeSource.Native), createFill(ERC20BridgeSource.LiquidityProvider)],
|
|
||||||
targetInput,
|
|
||||||
);
|
|
||||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
|
||||||
path.addFallback(fallback);
|
|
||||||
const sources = path.fills.map(f => f.source);
|
|
||||||
expect(sources).to.deep.eq([
|
|
||||||
ERC20BridgeSource.Native,
|
|
||||||
ERC20BridgeSource.LiquidityProvider,
|
|
||||||
ERC20BridgeSource.Uniswap,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Handles duplicates', () => {
|
|
||||||
const targetInput = new BigNumber(100);
|
|
||||||
const path = Path.create(
|
|
||||||
MarketOperation.Sell,
|
|
||||||
[createFill(ERC20BridgeSource.Uniswap), createFill(ERC20BridgeSource.LiquidityProvider)],
|
|
||||||
targetInput,
|
|
||||||
);
|
|
||||||
const fallback = Path.create(MarketOperation.Sell, [createFill(ERC20BridgeSource.Uniswap)], targetInput);
|
|
||||||
path.addFallback(fallback);
|
|
||||||
const sources = path.fills.map(f => f.source);
|
|
||||||
expect(sources).to.deep.eq([ERC20BridgeSource.Uniswap, ERC20BridgeSource.LiquidityProvider]);
|
|
||||||
});
|
|
||||||
it('Moves Native orders to the front and appends with unused fills', () => {
|
|
||||||
const targetInput = new BigNumber(100);
|
|
||||||
const path = Path.create(
|
|
||||||
MarketOperation.Sell,
|
|
||||||
[
|
|
||||||
createFill(ERC20BridgeSource.Uniswap, 0, new BigNumber(50)),
|
|
||||||
createFill(ERC20BridgeSource.Native, 0, new BigNumber(50)),
|
|
||||||
],
|
|
||||||
targetInput,
|
|
||||||
);
|
|
||||||
const fallback = Path.create(
|
|
||||||
MarketOperation.Sell,
|
|
||||||
[
|
|
||||||
createFill(ERC20BridgeSource.Uniswap, 0, new BigNumber(50)),
|
|
||||||
createFill(ERC20BridgeSource.Uniswap, 1, new BigNumber(50)),
|
|
||||||
],
|
|
||||||
targetInput,
|
|
||||||
);
|
|
||||||
path.addFallback(fallback);
|
|
||||||
const sources = path.fills.map(f => f.source);
|
|
||||||
expect(sources).to.deep.eq([ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap, ERC20BridgeSource.Uniswap]);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -9,11 +9,10 @@ import * as TypeMoq from 'typemoq';
|
|||||||
|
|
||||||
import { MarketOperation, NativeOrderWithFillableAmounts } from '../src/types';
|
import { MarketOperation, NativeOrderWithFillableAmounts } from '../src/types';
|
||||||
import {
|
import {
|
||||||
CollapsedFill,
|
|
||||||
DexSample,
|
DexSample,
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
Fill,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeCollapsedFill,
|
|
||||||
NativeFillData,
|
NativeFillData,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
@@ -34,7 +33,7 @@ import { getRandomAmount, getRandomSignature } from './utils/utils';
|
|||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): NativeCollapsedFill {
|
function fillFromNativeOrder(order: NativeOrderWithFillableAmounts): Fill<NativeFillData> {
|
||||||
const fillData = {
|
const fillData = {
|
||||||
order: order.order,
|
order: order.order,
|
||||||
signature: order.signature,
|
signature: order.signature,
|
||||||
@@ -50,7 +49,9 @@ function collapsedFillFromNativeOrder(order: NativeOrderWithFillableAmounts): Na
|
|||||||
order.type === FillQuoteTransformerOrderType.Limit
|
order.type === FillQuoteTransformerOrderType.Limit
|
||||||
? (fillData as NativeLimitOrderFillData)
|
? (fillData as NativeLimitOrderFillData)
|
||||||
: (fillData as NativeRfqOrderFillData),
|
: (fillData as NativeRfqOrderFillData),
|
||||||
subFills: [],
|
adjustedOutput: order.order.makerAmount,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,21 +112,25 @@ describe('generateQuoteReport', async () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// generate path
|
// generate path
|
||||||
const uniswap2Fill: CollapsedFill = {
|
const uniswap2Fill: Fill = {
|
||||||
...uniswapSample2,
|
...uniswapSample2,
|
||||||
subFills: [],
|
|
||||||
sourcePathId: hexUtils.random(),
|
sourcePathId: hexUtils.random(),
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
adjustedOutput: uniswapSample2.output,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
const balancer2Fill: CollapsedFill = {
|
const balancer2Fill: Fill = {
|
||||||
...balancerSample2,
|
...balancerSample2,
|
||||||
subFills: [],
|
|
||||||
sourcePathId: hexUtils.random(),
|
sourcePathId: hexUtils.random(),
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
adjustedOutput: balancerSample2.output,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
const orderbookOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder2);
|
const orderbookOrder2Fill: Fill = fillFromNativeOrder(orderbookOrder2);
|
||||||
const rfqtOrder2Fill: CollapsedFill = collapsedFillFromNativeOrder(rfqtOrder2);
|
const rfqtOrder2Fill: Fill = fillFromNativeOrder(rfqtOrder2);
|
||||||
const pathGenerated: CollapsedFill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, balancer2Fill];
|
const pathGenerated: Fill[] = [rfqtOrder2Fill, orderbookOrder2Fill, uniswap2Fill, balancer2Fill];
|
||||||
|
|
||||||
// quote generator mock
|
// quote generator mock
|
||||||
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
const quoteRequestor = TypeMoq.Mock.ofType<QuoteRequestor>();
|
||||||
@@ -241,20 +246,24 @@ describe('generateQuoteReport', async () => {
|
|||||||
const nativeOrders = [orderbookOrder1, orderbookOrder2];
|
const nativeOrders = [orderbookOrder1, orderbookOrder2];
|
||||||
|
|
||||||
// generate path
|
// generate path
|
||||||
const orderbookOrder1Fill: CollapsedFill = collapsedFillFromNativeOrder(orderbookOrder1);
|
const orderbookOrder1Fill: Fill = fillFromNativeOrder(orderbookOrder1);
|
||||||
const uniswap1Fill: CollapsedFill = {
|
const uniswap1Fill: Fill = {
|
||||||
...uniswapSample1,
|
...uniswapSample1,
|
||||||
subFills: [],
|
|
||||||
sourcePathId: hexUtils.random(),
|
sourcePathId: hexUtils.random(),
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
adjustedOutput: uniswapSample1.output,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
const balancer1Fill: CollapsedFill = {
|
const balancer1Fill: Fill = {
|
||||||
...balancerSample1,
|
...balancerSample1,
|
||||||
subFills: [],
|
|
||||||
sourcePathId: hexUtils.random(),
|
sourcePathId: hexUtils.random(),
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
|
adjustedOutput: balancerSample1.output,
|
||||||
|
flags: BigInt(0),
|
||||||
|
gas: 1,
|
||||||
};
|
};
|
||||||
const pathGenerated: CollapsedFill[] = [orderbookOrder1Fill, uniswap1Fill, balancer1Fill];
|
const pathGenerated: Fill[] = [orderbookOrder1Fill, uniswap1Fill, balancer1Fill];
|
||||||
|
|
||||||
const orderReport = generateQuoteReport(marketOperation, nativeOrders, pathGenerated);
|
const orderReport = generateQuoteReport(marketOperation, nativeOrders, pathGenerated);
|
||||||
|
|
||||||
|
@@ -5,8 +5,8 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import { MarketOperation } from '../src/types';
|
import { MarketOperation } from '../src/types';
|
||||||
import {
|
import {
|
||||||
CollapsedFill,
|
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
|
Fill,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
OptimizedMarketOrderBase,
|
OptimizedMarketOrderBase,
|
||||||
@@ -45,18 +45,16 @@ describe('quote_simulation tests', async () => {
|
|||||||
inputFeeRate: number;
|
inputFeeRate: number;
|
||||||
outputFeeRate: number;
|
outputFeeRate: number;
|
||||||
count: number;
|
count: number;
|
||||||
fillsCount: number;
|
|
||||||
side: MarketOperation;
|
side: MarketOperation;
|
||||||
type?: FillQuoteTransformerOrderType;
|
type?: FillQuoteTransformerOrderType;
|
||||||
}> = {},
|
}> = {},
|
||||||
): QuoteFillOrderCall[] {
|
): QuoteFillOrderCall[] {
|
||||||
const { fillableInput, fillableOutput, inputFeeRate, outputFeeRate, count, fillsCount, side, type } = {
|
const { fillableInput, fillableOutput, inputFeeRate, outputFeeRate, count, side, type } = {
|
||||||
fillableInput: getRandomOrderSize(),
|
fillableInput: getRandomOrderSize(),
|
||||||
fillableOutput: getRandomOrderSize(),
|
fillableOutput: getRandomOrderSize(),
|
||||||
inputFeeRate: 0,
|
inputFeeRate: 0,
|
||||||
outputFeeRate: 0,
|
outputFeeRate: 0,
|
||||||
count: 3,
|
count: 3,
|
||||||
fillsCount: 3,
|
|
||||||
side: MarketOperation.Sell,
|
side: MarketOperation.Sell,
|
||||||
...opts,
|
...opts,
|
||||||
};
|
};
|
||||||
@@ -83,7 +81,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
return {
|
return {
|
||||||
order: createQuoteFillOrderOrder(totalInputs[i], totalOutputs[i], {
|
order: createQuoteFillOrderOrder(totalInputs[i], totalOutputs[i], {
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
filledInput: filledInputs[i],
|
filledInput: filledInputs[i],
|
||||||
takerInputFee: inputFees[i].abs(),
|
takerInputFee: inputFees[i].abs(),
|
||||||
takerOutputFee: outputFees[i].abs(),
|
takerOutputFee: outputFees[i].abs(),
|
||||||
@@ -102,19 +99,17 @@ describe('quote_simulation tests', async () => {
|
|||||||
output: BigNumber,
|
output: BigNumber,
|
||||||
opts: Partial<{
|
opts: Partial<{
|
||||||
filledInput: BigNumber;
|
filledInput: BigNumber;
|
||||||
fillsCount: number;
|
|
||||||
side: MarketOperation;
|
side: MarketOperation;
|
||||||
takerInputFee: BigNumber;
|
takerInputFee: BigNumber;
|
||||||
takerOutputFee: BigNumber;
|
takerOutputFee: BigNumber;
|
||||||
type: FillQuoteTransformerOrderType;
|
type: FillQuoteTransformerOrderType;
|
||||||
}> = {},
|
}> = {},
|
||||||
): OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
): OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
||||||
const { filledInput, fillsCount, side, takerInputFee, takerOutputFee, type } = _.merge(
|
const { filledInput, side, takerInputFee, takerOutputFee, type } = _.merge(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
side: MarketOperation.Sell,
|
side: MarketOperation.Sell,
|
||||||
filledInput: ZERO,
|
filledInput: ZERO,
|
||||||
fillsCount: 3,
|
|
||||||
takerInputFee: ZERO,
|
takerInputFee: ZERO,
|
||||||
takerOutputFee: ZERO,
|
takerOutputFee: ZERO,
|
||||||
type: FillQuoteTransformerOrderType.Limit,
|
type: FillQuoteTransformerOrderType.Limit,
|
||||||
@@ -160,46 +155,23 @@ describe('quote_simulation tests', async () => {
|
|||||||
maxTakerTokenFillAmount: fillableTakerAmount,
|
maxTakerTokenFillAmount: fillableTakerAmount,
|
||||||
},
|
},
|
||||||
type,
|
type,
|
||||||
fills: createOrderCollapsedFills(fillableInput, fillableOutput, fillsCount),
|
fill: createOrderFill(fillableInput, fillableOutput),
|
||||||
};
|
};
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
const nativeSourcePathId = hexUtils.random();
|
const nativeSourcePathId = hexUtils.random();
|
||||||
function createOrderCollapsedFills(input: BigNumber, output: BigNumber, count: number): CollapsedFill[] {
|
function createOrderFill(input: BigNumber, output: BigNumber): Fill {
|
||||||
const inputs = subdivideAmount(input, count);
|
|
||||||
const outputs = subdivideAmount(output, count);
|
|
||||||
return _.times(count, i => {
|
|
||||||
const subFillInputs = subdivideAmount(inputs[i], count);
|
|
||||||
const subFillOutputs = subdivideAmount(outputs[i], count);
|
|
||||||
return {
|
return {
|
||||||
type: FillQuoteTransformerOrderType.Bridge,
|
type: FillQuoteTransformerOrderType.Bridge,
|
||||||
sourcePathId: nativeSourcePathId,
|
sourcePathId: nativeSourcePathId,
|
||||||
source: ERC20BridgeSource.Uniswap,
|
source: ERC20BridgeSource.Uniswap,
|
||||||
fillData: {},
|
fillData: {},
|
||||||
input: inputs[i],
|
input,
|
||||||
output: outputs[i],
|
output,
|
||||||
subFills: _.times(count, j => ({
|
flags: BigInt(0),
|
||||||
input: subFillInputs[j],
|
adjustedOutput: output,
|
||||||
output: subFillOutputs[j],
|
gas: 1,
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function countCollapsedFills(fillOrders: QuoteFillOrderCall[] | OptimizedMarketOrder[]): number {
|
|
||||||
let count = 0;
|
|
||||||
if ((fillOrders as any)[0].fills) {
|
|
||||||
const orders = (fillOrders as any) as OptimizedMarketOrder[];
|
|
||||||
for (const o of orders) {
|
|
||||||
count += o.fills.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const orders = (fillOrders as any) as QuoteFillOrderCall[];
|
|
||||||
for (const fo of orders) {
|
|
||||||
count += fo.order.fills.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomSide(): MarketOperation {
|
function randomSide(): MarketOperation {
|
||||||
@@ -237,14 +209,12 @@ describe('quote_simulation tests', async () => {
|
|||||||
describe('single order', () => {
|
describe('single order', () => {
|
||||||
it('can exactly fill one order', () => {
|
it('can exactly fill one order', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const fillOrders = createQuoteFillOrders({
|
const fillOrders = createQuoteFillOrders({
|
||||||
fillableInput,
|
fillableInput,
|
||||||
fillableOutput,
|
fillableOutput,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
||||||
@@ -253,19 +223,16 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partially fill one simple order', () => {
|
it('can partially fill one simple order', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = 1;
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const fillOrders = createQuoteFillOrders({
|
const fillOrders = createQuoteFillOrders({
|
||||||
fillableInput,
|
fillableInput,
|
||||||
fillableOutput,
|
fillableOutput,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||||
@@ -279,19 +246,16 @@ describe('quote_simulation tests', async () => {
|
|||||||
.integerValue();
|
.integerValue();
|
||||||
assertRoughlyEquals(totalFilledOutput, expectedOutputFilledAmount);
|
assertRoughlyEquals(totalFilledOutput, expectedOutputFilledAmount);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partially fill one batched order', () => {
|
it('can partially fill one batched order', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = 3;
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const fillOrders = createQuoteFillOrders({
|
const fillOrders = createQuoteFillOrders({
|
||||||
fillableInput,
|
fillableInput,
|
||||||
fillableOutput,
|
fillableOutput,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
||||||
@@ -301,20 +265,16 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
||||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.gte(1);
|
|
||||||
expect(result.gas).to.lte(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not over fill one order', () => {
|
it('does not over fill one order', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const fillOrders = createQuoteFillOrders({
|
const fillOrders = createQuoteFillOrders({
|
||||||
fillableInput,
|
fillableInput,
|
||||||
fillableOutput,
|
fillableOutput,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
||||||
@@ -324,12 +284,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can exactly fill one order with input fees', () => {
|
it('can exactly fill one order with input fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const inputFeeRate = getRandomFeeRate();
|
const inputFeeRate = getRandomFeeRate();
|
||||||
@@ -338,7 +296,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
inputFeeRate,
|
inputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||||
@@ -350,12 +307,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partially fill one order with input fees', () => {
|
it('can partially fill one order with input fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const inputFeeRate = getRandomFeeRate();
|
const inputFeeRate = getRandomFeeRate();
|
||||||
@@ -364,7 +319,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
inputFeeRate,
|
inputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||||
@@ -377,12 +331,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.lte(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not over fill one order with input fees', () => {
|
it('does not over fill one order with input fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const inputFeeRate = getRandomFeeRate();
|
const inputFeeRate = getRandomFeeRate();
|
||||||
@@ -391,7 +343,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
inputFeeRate,
|
inputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
||||||
@@ -404,12 +355,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can exactly fill one order with output fees', () => {
|
it('can exactly fill one order with output fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const outputFeeRate = getRandomFeeRate();
|
const outputFeeRate = getRandomFeeRate();
|
||||||
@@ -418,7 +367,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
outputFeeRate,
|
outputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||||
@@ -430,12 +378,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partial fill one order with output fees', () => {
|
it('can partial fill one order with output fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const outputFeeRate = getRandomFeeRate();
|
const outputFeeRate = getRandomFeeRate();
|
||||||
@@ -444,7 +390,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
outputFeeRate,
|
outputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||||
@@ -457,12 +402,10 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.lte(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not over fill one order with output fees', () => {
|
it('does not over fill one order with output fees', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const outputFeeRate = getRandomFeeRate();
|
const outputFeeRate = getRandomFeeRate();
|
||||||
@@ -471,7 +414,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
fillableOutput,
|
fillableOutput,
|
||||||
outputFeeRate,
|
outputFeeRate,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
});
|
});
|
||||||
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
||||||
@@ -484,19 +426,16 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(1);
|
expect(result.protocolFee).to.bignumber.eq(1);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not charge a protocol fee for rfq orders', () => {
|
it('does not charge a protocol fee for rfq orders', () => {
|
||||||
const side = randomSide();
|
const side = randomSide();
|
||||||
const fillsCount = _.random(1, 3);
|
|
||||||
const fillableInput = getRandomOrderSize();
|
const fillableInput = getRandomOrderSize();
|
||||||
const fillableOutput = getRandomOrderSize();
|
const fillableOutput = getRandomOrderSize();
|
||||||
const fillOrders = createQuoteFillOrders({
|
const fillOrders = createQuoteFillOrders({
|
||||||
fillableInput,
|
fillableInput,
|
||||||
fillableOutput,
|
fillableOutput,
|
||||||
side,
|
side,
|
||||||
fillsCount,
|
|
||||||
count: 1,
|
count: 1,
|
||||||
type: FillQuoteTransformerOrderType.Rfq,
|
type: FillQuoteTransformerOrderType.Rfq,
|
||||||
});
|
});
|
||||||
@@ -506,7 +445,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(0);
|
expect(result.protocolFee).to.bignumber.eq(0);
|
||||||
expect(result.gas).to.eq(fillsCount);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -522,7 +460,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||||
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partial fill orders', () => {
|
it('can partial fill orders', () => {
|
||||||
@@ -551,7 +488,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
||||||
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can exactly fill orders with input fees', () => {
|
it('can exactly fill orders with input fees', () => {
|
||||||
@@ -574,7 +510,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partial fill orders with input fees', () => {
|
it('can partial fill orders with input fees', () => {
|
||||||
@@ -598,7 +533,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
||||||
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not over fill orders with input fees', () => {
|
it('does not over fill orders with input fees', () => {
|
||||||
@@ -622,7 +556,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
||||||
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can exactly fill orders with output fees', () => {
|
it('can exactly fill orders with output fees', () => {
|
||||||
@@ -645,7 +578,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can partial fill orders with output fees', () => {
|
it('can partial fill orders with output fees', () => {
|
||||||
@@ -669,7 +601,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
||||||
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not over fill orders with output fees', () => {
|
it('does not over fill orders with output fees', () => {
|
||||||
@@ -693,7 +624,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
||||||
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
||||||
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
||||||
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -771,7 +701,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
gasPrice: ONE,
|
gasPrice: ONE,
|
||||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||||
});
|
});
|
||||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
|
||||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||||
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
||||||
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
||||||
@@ -895,7 +824,6 @@ describe('quote_simulation tests', async () => {
|
|||||||
gasPrice: ONE,
|
gasPrice: ONE,
|
||||||
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
||||||
});
|
});
|
||||||
expect(result.gas).to.eq(countCollapsedFills(orders));
|
|
||||||
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
||||||
|
|
||||||
assertRoughlyEquals(result.makerAssetAmount, fillableInput);
|
assertRoughlyEquals(result.makerAssetAmount, fillableInput);
|
||||||
|
@@ -28,7 +28,6 @@ export * from '../test/generated-wrappers/i_mooniswap';
|
|||||||
export * from '../test/generated-wrappers/i_multi_bridge';
|
export * from '../test/generated-wrappers/i_multi_bridge';
|
||||||
export * from '../test/generated-wrappers/i_platypus';
|
export * from '../test/generated-wrappers/i_platypus';
|
||||||
export * from '../test/generated-wrappers/i_shell';
|
export * from '../test/generated-wrappers/i_shell';
|
||||||
export * from '../test/generated-wrappers/i_smoothy';
|
|
||||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
||||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
||||||
export * from '../test/generated-wrappers/igmx';
|
export * from '../test/generated-wrappers/igmx';
|
||||||
@@ -42,7 +41,6 @@ export * from '../test/generated-wrappers/native_order_sampler';
|
|||||||
export * from '../test/generated-wrappers/platypus_sampler';
|
export * from '../test/generated-wrappers/platypus_sampler';
|
||||||
export * from '../test/generated-wrappers/sampler_utils';
|
export * from '../test/generated-wrappers/sampler_utils';
|
||||||
export * from '../test/generated-wrappers/shell_sampler';
|
export * from '../test/generated-wrappers/shell_sampler';
|
||||||
export * from '../test/generated-wrappers/smoothy_sampler';
|
|
||||||
export * from '../test/generated-wrappers/test_native_order_sampler';
|
export * from '../test/generated-wrappers/test_native_order_sampler';
|
||||||
export * from '../test/generated-wrappers/two_hop_sampler';
|
export * from '../test/generated-wrappers/two_hop_sampler';
|
||||||
export * from '../test/generated-wrappers/uniswap_sampler';
|
export * from '../test/generated-wrappers/uniswap_sampler';
|
||||||
|
@@ -32,7 +32,6 @@
|
|||||||
"test/generated-artifacts/IMultiBridge.json",
|
"test/generated-artifacts/IMultiBridge.json",
|
||||||
"test/generated-artifacts/IPlatypus.json",
|
"test/generated-artifacts/IPlatypus.json",
|
||||||
"test/generated-artifacts/IShell.json",
|
"test/generated-artifacts/IShell.json",
|
||||||
"test/generated-artifacts/ISmoothy.json",
|
|
||||||
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
||||||
"test/generated-artifacts/IUniswapV2Router01.json",
|
"test/generated-artifacts/IUniswapV2Router01.json",
|
||||||
"test/generated-artifacts/KyberDmmSampler.json",
|
"test/generated-artifacts/KyberDmmSampler.json",
|
||||||
@@ -45,7 +44,6 @@
|
|||||||
"test/generated-artifacts/PlatypusSampler.json",
|
"test/generated-artifacts/PlatypusSampler.json",
|
||||||
"test/generated-artifacts/SamplerUtils.json",
|
"test/generated-artifacts/SamplerUtils.json",
|
||||||
"test/generated-artifacts/ShellSampler.json",
|
"test/generated-artifacts/ShellSampler.json",
|
||||||
"test/generated-artifacts/SmoothySampler.json",
|
|
||||||
"test/generated-artifacts/TestNativeOrderSampler.json",
|
"test/generated-artifacts/TestNativeOrderSampler.json",
|
||||||
"test/generated-artifacts/TwoHopSampler.json",
|
"test/generated-artifacts/TwoHopSampler.json",
|
||||||
"test/generated-artifacts/UniswapSampler.json",
|
"test/generated-artifacts/UniswapSampler.json",
|
||||||
|
Reference in New Issue
Block a user