feat: asset-swapper tweak the gas schedule + return decimals (#34)

* feat: asset-swapper Return decimals from sampler in quote

* feat: asset-swapper tweak the gas schedule

* fix lint

* CHANGELOG
This commit is contained in:
Jacob Evans 2020-11-17 11:36:53 +10:00 committed by GitHub
parent 3133c509f9
commit 4f82543bdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 2010 deletions

View File

@ -1,4 +1,17 @@
[
{
"version": "5.2.0",
"changes": [
{
"note": "Update Gas schedules",
"pr": 34
},
{
"note": "Return the maker/taker token decimals from the sampler as part of the `SwapQuote`",
"pr": 34
}
]
},
{
"version": "5.1.1",
"changes": [

View File

@ -64,7 +64,7 @@
"@0x/dev-utils": "^4.0.1",
"@0x/json-schemas": "^5.3.3",
"@0x/order-utils": "^10.4.6",
"@0x/orderbook": "^2.2.7",
"@0x/orderbook": "0xProject/gitpkg-registry#0x-orderbook-v2.2.7-e10a81023",
"@0x/quote-server": "^3.1.0",
"@0x/types": "^3.3.0",
"@0x/typescript-typings": "^5.1.5",

View File

@ -503,6 +503,8 @@ export class SwapQuoter {
return {
bids: getMarketDepthSide(bids),
asks: getMarketDepthSide(asks),
makerTokenDecimals: asks.makerTokenDecimals,
takerTokenDecimals: asks.takerTokenDecimals,
};
}

View File

@ -188,6 +188,8 @@ export interface SwapQuoteBase {
sourceBreakdown: SwapQuoteOrdersBreakdown;
quoteReport?: QuoteReport;
isTwoHop: boolean;
makerTokenDecimals: number;
takerTokenDecimals: number;
}
/**

View File

@ -429,13 +429,20 @@ export const BRIDGE_ADDRESSES_BY_CHAIN: { [chainId in ChainId]: BridgeContractAd
[ChainId.Ganache]: EMPTY_BRIDGE_ADDRESSES,
};
/**
* Calculated gross gas cost of the underlying exchange.
* The cost of switching from one source to another, assuming
* we are in the middle of a transaction.
* I.e remove the overhead cost of ExchangeProxy (130k) and
* the ethereum transaction cost (21k)
*/
// tslint:disable:custom-no-magic-numbers
export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
[ERC20BridgeSource.Native]: () => 150e3,
[ERC20BridgeSource.Uniswap]: () => 90e3,
[ERC20BridgeSource.LiquidityProvider]: () => 140e3,
[ERC20BridgeSource.Eth2Dai]: () => 400e3,
[ERC20BridgeSource.Kyber]: () => 500e3,
[ERC20BridgeSource.Kyber]: () => 450e3,
[ERC20BridgeSource.Curve]: fillData => {
const poolAddress = (fillData as CurveFillData).pool.poolAddress.toLowerCase();
switch (poolAddress) {
@ -452,12 +459,14 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
case POOLS.curve_BUSD:
return 850e3;
// Metapools
case POOLS.curve_GUSD:
case POOLS.curve_HUSD:
case POOLS.curve_USDN:
case POOLS.curve_mUSD:
return 300e3;
case POOLS.curve_GUSD:
case POOLS.curve_HUSD:
return 310e3;
case POOLS.curve_tBTC:
return 650e3;
return 370e3;
default:
throw new Error(`Unrecognized Curve address: ${poolAddress}`);
}
@ -482,11 +491,11 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
return gas;
},
[ERC20BridgeSource.Balancer]: () => 120e3,
[ERC20BridgeSource.Cream]: () => 300e3,
[ERC20BridgeSource.Cream]: () => 120e3,
[ERC20BridgeSource.MStable]: () => 700e3,
[ERC20BridgeSource.Mooniswap]: () => 220e3,
[ERC20BridgeSource.Mooniswap]: () => 130e3,
[ERC20BridgeSource.Swerve]: () => 150e3,
[ERC20BridgeSource.Shell]: () => 300e3,
[ERC20BridgeSource.Shell]: () => 170e3,
[ERC20BridgeSource.MultiHop]: (fillData?: FillData) => {
const firstHop = (fillData as MultiHopFillData).firstHopSource;
const secondHop = (fillData as MultiHopFillData).secondHopSource;
@ -501,7 +510,7 @@ export const DEFAULT_GAS_SCHEDULE: Required<FeeSchedule> = {
const isSellBase = (fillData as DODOFillData).isSellBase;
// Sell base is cheaper as it is natively supported
// sell quote requires additional calculation and overhead
return isSellBase ? 440e3 : 540e3;
return isSellBase ? 180e3 : 300e3;
},
[ERC20BridgeSource.SnowSwap]: fillData => {
switch ((fillData as SnowSwapFillData).pool.poolAddress.toLowerCase()) {

View File

@ -4,7 +4,7 @@ import { BigNumber, NULL_ADDRESS } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import { AssetSwapperContractAddresses, MarketOperation, Omit } from '../../types';
import { AssetSwapperContractAddresses, MarketOperation } from '../../types';
import { QuoteRequestor } from '../quote_requestor';
import { getPriceAwareRFQRolloutFlags } from '../utils';
@ -38,7 +38,6 @@ import {
GenerateOptimizedOrdersOpts,
GetMarketOrdersOpts,
MarketSideLiquidity,
OptimizedMarketOrder,
OptimizerResult,
OptimizerResultWithReport,
OrderDomain,
@ -406,7 +405,7 @@ export class MarketOperationUtils {
batchNativeOrders: SignedOrder[][],
makerAmounts: BigNumber[],
opts?: Partial<GetMarketOrdersOpts>,
): Promise<Array<OptimizedMarketOrder[] | undefined>> {
): Promise<Array<OptimizerResult | undefined>> {
if (batchNativeOrders.length === 0) {
throw new Error(AggregationError.EmptyOrders);
}
@ -437,12 +436,16 @@ export class MarketOperationUtils {
[makerAmounts[i]],
),
),
...batchNativeOrders.map(orders =>
this._sampler.getTokenDecimals(getNativeOrderTokens(orders[0])[0], getNativeOrderTokens(orders[0])[1]),
),
];
const executeResults = await this._sampler.executeBatchAsync(ops);
const batchOrderFillableAmounts = executeResults.splice(0, batchNativeOrders.length) as BigNumber[][];
const batchEthToTakerAssetRate = executeResults.splice(0, batchNativeOrders.length) as BigNumber[];
const batchDexQuotes = executeResults.splice(0, batchNativeOrders.length) as DexSample[][][];
const batchTokenDecimals = executeResults.splice(0, batchNativeOrders.length) as number[][];
const ethToInputRate = ZERO_AMOUNT;
return Promise.all(
@ -456,7 +459,7 @@ export class MarketOperationUtils {
const dexQuotes = batchDexQuotes[i];
const makerAmount = makerAmounts[i];
try {
const { optimizedOrders } = await this._generateOptimizedOrdersAsync(
const optimizerResult = await this._generateOptimizedOrdersAsync(
{
side: MarketOperation.Buy,
nativeOrders,
@ -470,6 +473,8 @@ export class MarketOperationUtils {
outputToken: takerToken,
twoHopQuotes: [],
quoteSourceFilters,
makerTokenDecimals: batchTokenDecimals[i][0],
takerTokenDecimals: batchTokenDecimals[i][1],
},
{
bridgeSlippage: _opts.bridgeSlippage,
@ -479,7 +484,7 @@ export class MarketOperationUtils {
allowFallback: _opts.allowFallback,
},
);
return optimizedOrders;
return optimizerResult;
} catch (e) {
// It's possible for one of the pairs to have no path
// rather than throw NO_OPTIMAL_PATH we return undefined
@ -490,7 +495,7 @@ export class MarketOperationUtils {
}
public async _generateOptimizedOrdersAsync(
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
marketSideLiquidity: MarketSideLiquidity,
opts: GenerateOptimizedOrdersOpts,
): Promise<OptimizerResult> {
const {
@ -553,6 +558,7 @@ export class MarketOperationUtils {
optimizedOrders: twoHopOrders,
liquidityDelivered: bestTwoHopQuote,
sourceFlags: SOURCE_FLAGS[ERC20BridgeSource.MultiHop],
marketSideLiquidity,
};
}
@ -582,6 +588,7 @@ export class MarketOperationUtils {
optimizedOrders: collapsedPath.orders,
liquidityDelivered: collapsedPath.collapsedFills as CollapsedFill[],
sourceFlags: collapsedPath.sourceFlags,
marketSideLiquidity,
};
}
@ -606,7 +613,7 @@ export class MarketOperationUtils {
side === MarketOperation.Sell
? this.getMarketSellLiquidityAsync.bind(this)
: this.getMarketBuyLiquidityAsync.bind(this);
const marketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
const marketSideLiquidity: MarketSideLiquidity = await marketLiquidityFnAsync(nativeOrders, amount, _opts);
let optimizerResult: OptimizerResult | undefined;
try {
optimizerResult = await this._generateOptimizedOrdersAsync(marketSideLiquidity, optimizerOpts);

View File

@ -339,6 +339,7 @@ export interface OptimizerResult {
optimizedOrders: OptimizedMarketOrder[];
sourceFlags: number;
liquidityDelivered: CollapsedFill[] | DexSample<MultiHopFillData>;
marketSideLiquidity: MarketSideLiquidity;
}
export interface OptimizerResultWithReport extends OptimizerResult {
@ -350,6 +351,8 @@ export type MarketDepthSide = Array<Array<DexSample<FillData>>>;
export interface MarketDepth {
bids: MarketDepthSide;
asks: MarketDepthSide;
makerTokenDecimals: number;
takerTokenDecimals: number;
}
export interface MarketSideLiquidity {

View File

@ -1,5 +1,4 @@
import { assetDataUtils } from '@0x/order-utils';
import { AssetProxyId, SignedOrder } from '@0x/types';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@ -17,7 +16,6 @@ import {
import { MarketOperationUtils } from './market_operation_utils';
import { SOURCE_FLAGS } from './market_operation_utils/constants';
import { convertNativeOrderToFullyFillableOptimizedOrders } from './market_operation_utils/orders';
import {
ERC20BridgeSource,
FeeSchedule,
@ -89,24 +87,26 @@ export class SwapQuoteCalculator {
operation: MarketOperation,
opts: CalculateSwapQuoteOpts,
): Promise<Array<SwapQuote | undefined>> {
const batchSignedOrders = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync(
const optimizerResults = await this._marketOperationUtils.getBatchMarketBuyOrdersAsync(
batchPrunedOrders,
assetFillAmounts,
opts,
);
const batchSwapQuotes = await Promise.all(
batchSignedOrders.map(async (orders, i) => {
if (orders) {
optimizerResults.map(async (result, i) => {
if (result) {
const { makerAssetData, takerAssetData } = batchPrunedOrders[i][0];
return createSwapQuote(
makerAssetData,
takerAssetData,
orders,
result.optimizedOrders,
operation,
assetFillAmounts[i],
gasPrice,
opts.gasSchedule,
result.marketSideLiquidity.makerTokenDecimals,
result.marketSideLiquidity.takerTokenDecimals,
);
} else {
return undefined;
@ -122,7 +122,7 @@ export class SwapQuoteCalculator {
operation: MarketOperation,
opts: CalculateSwapQuoteOpts,
): Promise<SwapQuote> {
// checks if maker asset is ERC721 or ERC20 and taker asset is ERC20
// checks if maker asset is ERC20 and taker asset is ERC20
if (!isSupportedAssetDataInOrders(prunedOrders)) {
throw Error(SwapQuoterError.AssetDataUnsupported);
}
@ -131,6 +131,8 @@ export class SwapQuoteCalculator {
let optimizedOrders: OptimizedMarketOrder[];
let quoteReport: QuoteReport | undefined;
let sourceFlags: number = 0;
let makerTokenDecimals: number;
let takerTokenDecimals: number;
// Scale fees by gas price.
const _opts: GetMarketOrdersOpts = {
@ -141,34 +143,16 @@ export class SwapQuoteCalculator {
exchangeProxyOverhead: flags => gasPrice.times(opts.exchangeProxyOverhead(flags)),
};
const firstOrderMakerAssetData = !!prunedOrders[0]
? assetDataUtils.decodeAssetDataOrThrow(prunedOrders[0].makerAssetData)
: { assetProxyId: '' };
const result =
operation === MarketOperation.Buy
? await this._marketOperationUtils.getMarketBuyOrdersAsync(prunedOrders, assetFillAmount, _opts)
: await this._marketOperationUtils.getMarketSellOrdersAsync(prunedOrders, assetFillAmount, _opts);
if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) {
// HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable
optimizedOrders = prunedOrders.map(o => convertNativeOrderToFullyFillableOptimizedOrders(o));
} else {
if (operation === MarketOperation.Buy) {
const buyResult = await this._marketOperationUtils.getMarketBuyOrdersAsync(
prunedOrders,
assetFillAmount,
_opts,
);
optimizedOrders = buyResult.optimizedOrders;
quoteReport = buyResult.quoteReport;
sourceFlags = buyResult.sourceFlags;
} else {
const sellResult = await this._marketOperationUtils.getMarketSellOrdersAsync(
prunedOrders,
assetFillAmount,
_opts,
);
optimizedOrders = sellResult.optimizedOrders;
quoteReport = sellResult.quoteReport;
sourceFlags = sellResult.sourceFlags;
}
}
optimizedOrders = result.optimizedOrders;
quoteReport = result.quoteReport;
sourceFlags = result.sourceFlags;
makerTokenDecimals = result.marketSideLiquidity.makerTokenDecimals;
takerTokenDecimals = result.marketSideLiquidity.takerTokenDecimals;
// assetData information for the result
const { makerAssetData, takerAssetData } = prunedOrders[0];
@ -182,6 +166,8 @@ export class SwapQuoteCalculator {
assetFillAmount,
gasPrice,
opts.gasSchedule,
makerTokenDecimals,
takerTokenDecimals,
quoteReport,
)
: createSwapQuote(
@ -192,6 +178,8 @@ export class SwapQuoteCalculator {
assetFillAmount,
gasPrice,
opts.gasSchedule,
makerTokenDecimals,
takerTokenDecimals,
quoteReport,
);
// Use the raw gas, not scaled by gas price
@ -210,6 +198,8 @@ function createSwapQuote(
assetFillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: FeeSchedule,
makerTokenDecimals: number,
takerTokenDecimals: number,
quoteReport?: QuoteReport,
): SwapQuote {
const bestCaseFillResult = simulateBestCaseFill({
@ -245,12 +235,16 @@ function createSwapQuote(
...quoteBase,
type: MarketOperation.Buy,
makerAssetFillAmount: assetFillAmount,
makerTokenDecimals,
takerTokenDecimals,
};
} else {
return {
...quoteBase,
type: MarketOperation.Sell,
takerAssetFillAmount: assetFillAmount,
makerTokenDecimals,
takerTokenDecimals,
};
}
}
@ -263,6 +257,8 @@ function createTwoHopSwapQuote(
assetFillAmount: BigNumber,
gasPrice: BigNumber,
gasSchedule: FeeSchedule,
makerTokenDecimals: number,
takerTokenDecimals: number,
quoteReport?: QuoteReport,
): SwapQuote {
const [firstHopOrder, secondHopOrder] = optimizedOrders;
@ -312,12 +308,16 @@ function createTwoHopSwapQuote(
...quoteBase,
type: MarketOperation.Buy,
makerAssetFillAmount: assetFillAmount,
makerTokenDecimals,
takerTokenDecimals,
};
} else {
return {
...quoteBase,
type: MarketOperation.Sell,
takerAssetFillAmount: assetFillAmount,
makerTokenDecimals,
takerTokenDecimals,
};
}
}

View File

@ -33,8 +33,7 @@ export function isSupportedAssetDataInOrders(orders: SignedOrder[]): boolean {
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(o.takerAssetData);
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(o.makerAssetData);
return (
(makerAssetData.assetProxyId === AssetProxyId.ERC20 ||
makerAssetData.assetProxyId === AssetProxyId.ERC721) &&
makerAssetData.assetProxyId === AssetProxyId.ERC20 &&
takerAssetData.assetProxyId === AssetProxyId.ERC20 &&
firstOrderMakerAssetData.assetProxyId === makerAssetData.assetProxyId
); // checks that all native order maker assets are of the same type

View File

@ -1,5 +1,4 @@
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { ERC20BridgeSource } from '../../src';
import { constants } from '../../src/constants';
@ -47,12 +46,16 @@ export async function getFullyFillableSwapQuoteWithNoFeesAsync(
...quoteBase,
type: MarketOperation.Buy,
makerAssetFillAmount,
makerTokenDecimals: 18,
takerTokenDecimals: 18,
};
} else {
return {
...quoteBase,
type: MarketOperation.Sell,
takerAssetFillAmount: totalTakerAssetAmount,
makerTokenDecimals: 18,
takerTokenDecimals: 18,
};
}
}

1961
yarn.lock

File diff suppressed because it is too large Load Diff