Files
protocol/packages/asset-swapper/src/quote_consumers/quote_consumer_utils.ts
Lawrence Forman 2727dc3968 @0x/asset-swapper: hack together basic sampler service integration
`@0x/asset-swapper`: use gasCost from sampler service

cleanup

`@0x/asset-swapper`: Handle per-fill gas cost correctly in more places

getting multihop back up + refactors for treating all quotes as n-hop

multihop with nice breakdowns

get vips working

get multiplex working

get rfq working

remove unused functions

fix multihop buys

plz work multihop buys

multihop buys seem ok now.
fetch less samples for multihop legs

get quote report and price comparisons working

clean up
2022-02-26 15:20:30 -05:00

172 lines
5.6 KiB
TypeScript

import { FillQuoteTransformerData, FillQuoteTransformerOrderType } from '@0x/protocol-utils';
import { ExchangeProxyContractOpts, MarketBuySwapQuote, MarketOperation, SwapQuote } from '../types';
import {
getErc20BridgeSourceToBridgeSource,
} from '../utils/market_operation_utils/orders';
import {
ERC20BridgeSource,
} from '../utils/market_operation_utils/types';
import {
SwapQuoteGenericBridgeOrder,
SwapQuoteOrder,
SwapQuoteLimitOrder,
SwapQuoteRfqOrder,
} from '../types';
const MULTIPLEX_BATCH_FILL_SOURCES = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.Native,
ERC20BridgeSource.UniswapV3,
];
/**
* Returns true iff a quote can be filled via `MultiplexFeature.batchFill`.
*/
export function isMultiplexBatchFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
// Must not be multi-hop.
if (quote.hops.length > 1) {
return false;
}
// Must not contain limit orders.
const allOrderTypes = quote.hops.map(h => h.orders.map(o => o.type)).flat(2);
if (allOrderTypes.includes(FillQuoteTransformerOrderType.Limit)) {
return false;
}
// Use Multiplex if the non-fallback sources are a subset of
// {UniswapV2, Sushiswap, RFQ, PLP, UniswapV3}
const nonFallbackSources = quote.hops.map(h => h.orders.filter(o => !o.isFallback).map(o => o.source)).flat(2);
return nonFallbackSources.every(s => MULTIPLEX_BATCH_FILL_SOURCES.includes(s));
}
const MULTIPLEX_MULTIHOP_FILL_SOURCES = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.LiquidityProvider,
ERC20BridgeSource.UniswapV3,
];
/**
* Returns true iff a quote can be filled via `MultiplexFeature.multiHopFill`.
*/
export function isMultiplexMultiHopFillCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
// Must be multi-hop.
if (quote.hops.length < 2) {
return false;
}
const sources = quote.hops.map(h => h.orders.map(o => o.source)).flat(2);
return sources.every(s => MULTIPLEX_MULTIHOP_FILL_SOURCES.includes(s));
}
/**
* Returns true iff a quote can be filled via a VIP feature.
*/
export function isDirectSwapCompatible(
quote: SwapQuote,
opts: ExchangeProxyContractOpts,
directSources: ERC20BridgeSource[],
): boolean {
if (requiresTransformERC20(opts)) {
return false;
}
// Must be a single hop with a single order.
if (quote.hops.length !== 1 || quote.hops[0].orders.length !== 1) {
return false;
}
const order = quote.hops[0].orders[0];
if (!directSources.includes(order.source)) {
return false;
}
return true;
}
/**
* Whether a quote is a market buy or not.
*/
export function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
function isBridgeOrder(x: SwapQuoteOrder): x is SwapQuoteGenericBridgeOrder {
return x.type === FillQuoteTransformerOrderType.Bridge;
}
// function isOptimizedLimitOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeLimitOrderFillData> {
// return x.type === FillQuoteTransformerOrderType.Limit;
// }
//
// function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeRfqOrderFillData> {
// return x.type === FillQuoteTransformerOrderType.Rfq;
// }
/**
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
* FillQuoteTransformer.
*/
export function getFQTTransformerDataFromOptimizedOrders(
orders: SwapQuoteOrder[],
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
bridgeOrders: [],
limitOrders: [],
rfqOrders: [],
fillSequence: [],
};
for (const order of orders) {
if (isBridgeOrder(order)) {
fqtData.bridgeOrders.push({
bridgeData: order.fillData.encodedFillData,
makerTokenAmount: order.minMakerAmount,
takerTokenAmount: order.maxTakerAmount,
source: getErc20BridgeSourceToBridgeSource(order.source),
});
// } else if (isOptimizedLimitOrder(order)) {
// fqtData.limitOrders.push({
// order: order.fillData.order,
// signature: order.fillData.signature,
// maxTakerTokenFillAmount: order.takerAmount,
// });
// } else if (isOptimizedRfqOrder(order)) {
// fqtData.rfqOrders.push({
// order: order.fillData.order,
// signature: order.fillData.signature,
// maxTakerTokenFillAmount: order.takerAmount,
// });
} else {
// Should never happen
throw new Error('Unknown Order type');
}
fqtData.fillSequence.push(order.type);
}
return fqtData;
}
/**
* Returns true if swap quote must go through `tranformERC20`.
*/
export function requiresTransformERC20(opts: ExchangeProxyContractOpts): boolean {
// Is a mtx.
if (opts.isMetaTransaction) {
return true;
}
// Has an affiliate fee.
if (!opts.affiliateFee.buyTokenFeeAmount.eq(0) || !opts.affiliateFee.sellTokenFeeAmount.eq(0)) {
return true;
}
// VIP does not support selling the entire balance
if (opts.shouldSellEntireBalance) {
return true;
}
return false;
}