Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
@0x/asset-swapper: Add DFB support + refactor swap quote calculatio…
Browse files Browse the repository at this point in the history
…n utils
  • Loading branch information
dorothy-zbornak committed Apr 1, 2020
1 parent 424cbd4 commit 4b98f93
Show file tree
Hide file tree
Showing 15 changed files with 548 additions and 417 deletions.
4 changes: 4 additions & 0 deletions packages/asset-swapper/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
{
"note": "Fix `getBatchMarketBuyOrdersAsync` throwing NO_OPTIMAL_PATH",
"pr": 2533
},
{
"note": "Add DFB support + refactor swap quote calculator utils",
"pr": 2536
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion packages/asset-swapper/src/swap_quoter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export class SwapQuoter {
},
liquidityProviderRegistryAddress,
);
this._swapQuoteCalculator = new SwapQuoteCalculator(this._protocolFeeUtils, this._marketOperationUtils);
this._swapQuoteCalculator = new SwapQuoteCalculator(this._marketOperationUtils);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
feeSchedule: {},
gasSchedule: {},
allowFallback: true,
shouldBatchBridgeOrders: true,
};

/**
Expand Down
16 changes: 7 additions & 9 deletions packages/asset-swapper/src/utils/market_operation_utils/fills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,27 +233,25 @@ export function clipPathToInput(path: Fill[], targetInput: BigNumber = POSITIVE_
return clipped;
}

export function collapsePath(side: MarketOperation, path: Fill[]): CollapsedFill[] {
export function collapsePath(path: Fill[]): CollapsedFill[] {
const collapsed: Array<CollapsedFill | NativeCollapsedFill> = [];
for (const fill of path) {
const makerAssetAmount = side === MarketOperation.Sell ? fill.output : fill.input;
const takerAssetAmount = side === MarketOperation.Sell ? fill.input : fill.output;
const source = fill.source;
if (collapsed.length !== 0 && source !== ERC20BridgeSource.Native) {
const prevFill = collapsed[collapsed.length - 1];
// If the last fill is from the same source, merge them.
if (prevFill.source === source) {
prevFill.totalMakerAssetAmount = prevFill.totalMakerAssetAmount.plus(makerAssetAmount);
prevFill.totalTakerAssetAmount = prevFill.totalTakerAssetAmount.plus(takerAssetAmount);
prevFill.subFills.push({ makerAssetAmount, takerAssetAmount });
prevFill.input = prevFill.input.plus(fill.input);
prevFill.output = prevFill.output.plus(fill.output);
prevFill.subFills.push(fill);
continue;
}
}
collapsed.push({
source: fill.source,
totalMakerAssetAmount: makerAssetAmount,
totalTakerAssetAmount: takerAssetAmount,
subFills: [{ makerAssetAmount, takerAssetAmount }],
input: fill.input,
output: fill.output,
subFills: [fill],
nativeOrder: fill.source === ERC20BridgeSource.Native ? (fill.fillData as NativeFillData).order : undefined,
});
}
Expand Down
142 changes: 104 additions & 38 deletions packages/asset-swapper/src/utils/market_operation_utils/orders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { DexForwaderBridgeData, dexForwarderBridgeDataEncoder } from '@0x/contracts-asset-proxy';
import { assetDataUtils, ERC20AssetData, generatePseudoRandomSalt, orderCalculationUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';
import { ERC20BridgeAssetData, SignedOrder } from '@0x/types';
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';

import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';

Expand Down Expand Up @@ -71,12 +72,7 @@ export function convertNativeOrderToFullyFillableOptimizedOrders(order: SignedOr
fillableMakerAssetAmount: order.makerAssetAmount,
fillableTakerAssetAmount: order.takerAssetAmount,
fillableTakerFeeAmount: order.takerFee,
fill: {
source: ERC20BridgeSource.Native,
totalMakerAssetAmount: order.makerAssetAmount,
totalTakerAssetAmount: order.takerAssetAmount,
subFills: [],
},
fills: [],
};
}

Expand Down Expand Up @@ -124,14 +120,28 @@ export interface CreateOrderFromPathOpts {

// Convert sell fills into orders.
export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder[] {
const collapsedPath = collapsePath(opts.side, path);
const collapsedPath = collapsePath(path);
const orders: OptimizedMarketOrder[] = [];
for (const fill of collapsedPath) {
if (fill.source === ERC20BridgeSource.Native) {
orders.push(createNativeOrder(fill));
for (let i = 0; i < collapsedPath.length;) {
if (collapsedPath[i].source === ERC20BridgeSource.Native) {
orders.push(createNativeOrder(collapsedPath[i]));
++i;
continue;
}
// If there are contiguous bridge orders, we can batch them together.
const contiguousBridgeFills = [];
for (let j = i + 1; j < collapsedPath.length; ++j) {
if (collapsedPath[j].source !== ERC20BridgeSource.Native) {
break;
}
contiguousBridgeFills.push(collapsedPath[j]);
}
if (contiguousBridgeFills.length === 1) {
orders.push(createBridgeOrder(contiguousBridgeFills[i], opts));
} else {
orders.push(createBridgeOrder(fill, opts));
orders.push(createBatchedBridgeOrder(contiguousBridgeFills, opts));
}
i += contiguousBridgeFills.length;
}
return orders;
}
Expand Down Expand Up @@ -161,8 +171,7 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrder
}

function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts): OptimizedMarketOrder {
const takerToken = opts.side === MarketOperation.Sell ? opts.inputToken : opts.outputToken;
const makerToken = opts.side === MarketOperation.Sell ? opts.outputToken : opts.inputToken;
const [makerToken, takerToken] = getMakerTakerTokens(opts);
const bridgeAddress = getBridgeAddressFromSource(fill.source, opts);

let makerAssetData;
Expand All @@ -182,14 +191,66 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts):
createBridgeData(takerToken),
);
}
const [slippedMakerAssetAmount, slippedTakerAssetAmount] = getSlippedBridgeAssetAmounts(fill, opts);
return {
makerAddress: bridgeAddress,
fills: [fill],
makerAssetData,
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
...createCommonBridgeOrderFields(fill, opts),
makerAddress: bridgeAddress,
makerAssetAmount: slippedMakerAssetAmount,
takerAssetAmount: slippedTakerAssetAmount,
fillableMakerAssetAmount: slippedMakerAssetAmount,
fillableTakerAssetAmount: slippedTakerAssetAmount,
...createCommonBridgeOrderFields(opts),
};
}

function createBatchedBridgeOrder(fills: CollapsedFill[], opts: CreateOrderFromPathOpts): OptimizedMarketOrder {
const [makerToken, takerToken] = getMakerTakerTokens(opts);
let totalMakerAssetAmount = ZERO_AMOUNT;
let totalTakerAssetAmount = ZERO_AMOUNT;
const batchedBridgeData: DexForwaderBridgeData = {
inputToken: takerToken,
calls: [],
};
for (const fill of fills) {
const bridgeOrder = createBridgeOrder(fill, opts);
totalMakerAssetAmount = totalMakerAssetAmount.plus(bridgeOrder.makerAssetAmount);
totalTakerAssetAmount = totalTakerAssetAmount.plus(bridgeOrder.takerAssetAmount);
const { bridgeAddress, bridgeData: orderBridgeData } =
assetDataUtils.decodeAssetDataOrThrow(bridgeOrder.makerAssetData) as ERC20BridgeAssetData;
batchedBridgeData.calls.push({
target: bridgeAddress,
bridgeData: orderBridgeData,
inputTokenAmount: bridgeOrder.takerAssetAmount,
outputTokenAmount: bridgeOrder.makerAssetAmount,
});
}
const batchedBridgeAddress = opts.contractAddresses.dexForwarderBridge;
const batchedMakerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
batchedBridgeAddress,
dexForwarderBridgeDataEncoder.encode(batchedBridgeData),
);
return {
fills,
makerAssetData: batchedMakerAssetData,
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
makerAddress: batchedBridgeAddress,
makerAssetAmount: totalMakerAssetAmount,
takerAssetAmount: totalTakerAssetAmount,
fillableMakerAssetAmount: totalMakerAssetAmount,
fillableTakerAssetAmount: totalTakerAssetAmount,
...createCommonBridgeOrderFields(opts),
};
}

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];
}

function createBridgeData(tokenAddress: string): string {
const encoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
return encoder.encode({ tokenAddress });
Expand All @@ -210,22 +271,36 @@ function createCurveBridgeData(
return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx, version]);
}

function getSlippedBridgeAssetAmounts(fill: CollapsedFill, opts: CreateOrderFromPathOpts): [BigNumber, BigNumber] {
return [
// Maker asset amount.
opts.side === MarketOperation.Sell
? fill.output.times(1 - opts.bridgeSlippage).integerValue(BigNumber.ROUND_DOWN)
: fill.output,
// Taker asset amount.
opts.side === MarketOperation.Sell
? fill.input
: fill.input.times(opts.bridgeSlippage + 1).integerValue(BigNumber.ROUND_UP),
];
}

type CommonBridgeOrderFields = Pick<
OptimizedMarketOrder,
Exclude<keyof OptimizedMarketOrder, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
Exclude<
keyof OptimizedMarketOrder,
'fills'
| 'makerAddress'
| 'makerAssetData'
| 'takerAssetData'
| 'makerAssetAmount'
| 'takerAssetAmount'
| 'fillableMakerAssetAmount'
| 'fillableTakerAssetAmount'
>
>;

function createCommonBridgeOrderFields(fill: CollapsedFill, opts: CreateOrderFromPathOpts): CommonBridgeOrderFields {
const makerAssetAmountAdjustedWithSlippage =
opts.side === MarketOperation.Sell
? fill.totalMakerAssetAmount.times(1 - opts.bridgeSlippage).integerValue(BigNumber.ROUND_DOWN)
: fill.totalMakerAssetAmount;
const takerAssetAmountAdjustedWithSlippage =
opts.side === MarketOperation.Sell
? fill.totalTakerAssetAmount
: fill.totalTakerAssetAmount.times(opts.bridgeSlippage + 1).integerValue(BigNumber.ROUND_UP);
function createCommonBridgeOrderFields(opts: CreateOrderFromPathOpts): CommonBridgeOrderFields {
return {
fill,
takerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS,
Expand All @@ -235,10 +310,6 @@ function createCommonBridgeOrderFields(fill: CollapsedFill, opts: CreateOrderFro
takerFeeAssetData: NULL_BYTES,
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
makerAssetAmount: makerAssetAmountAdjustedWithSlippage,
fillableMakerAssetAmount: makerAssetAmountAdjustedWithSlippage,
takerAssetAmount: takerAssetAmountAdjustedWithSlippage,
fillableTakerAssetAmount: takerAssetAmountAdjustedWithSlippage,
fillableTakerFeeAmount: ZERO_AMOUNT,
signature: WALLET_SIGNATURE,
...opts.orderDomain,
Expand All @@ -247,12 +318,7 @@ function createCommonBridgeOrderFields(fill: CollapsedFill, opts: CreateOrderFro

function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder {
return {
fill: {
source: fill.source,
totalMakerAssetAmount: fill.totalMakerAssetAmount,
totalTakerAssetAmount: fill.totalTakerAssetAmount,
subFills: fill.subFills,
},
fills: [fill],
...(fill as NativeCollapsedFill).nativeOrder,
};
}
23 changes: 14 additions & 9 deletions packages/asset-swapper/src/utils/market_operation_utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,19 @@ export interface CollapsedFill {
*/
source: ERC20BridgeSource;
/**
* Total maker asset amount.
* Total input amount (sum of `subFill`s)
*/
totalMakerAssetAmount: BigNumber;
input: BigNumber;
/**
* Total taker asset amount.
* Total output amount (sum of `subFill`s)
*/
totalTakerAssetAmount: BigNumber;
output: BigNumber;
/**
* All the fill asset amounts that were collapsed into this node.
* Quantities of all the fills that were collapsed.
*/
subFills: Array<{
makerAssetAmount: BigNumber;
takerAssetAmount: BigNumber;
input: BigNumber;
output: BigNumber;
}>;
}

Expand All @@ -127,7 +127,7 @@ export interface OptimizedMarketOrder extends SignedOrderWithFillableAmounts {
/**
* The optimized fills that generated this order.
*/
fill: CollapsedFill;
fills: CollapsedFill[];
}

/**
Expand Down Expand Up @@ -180,9 +180,14 @@ export interface GetMarketOrdersOpts {
gasSchedule: { [source: string]: number };
/**
* Whether to pad the quote with a redundant fallback quote using different
* sources.
* sources. Defaults to `true`.
*/
allowFallback: boolean;
/**
* Whether to combine contiguous bridge orders into a single DexForwarderBridge
* order. Defaults to `true`.
*/
shouldBatchBridgeOrders: boolean;
}

/**
Expand Down
18 changes: 0 additions & 18 deletions packages/asset-swapper/src/utils/protocol_fee_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ export class ProtocolFeeUtils {
this._initializeHeartBeat();
}

// TODO(dave4506) at some point, we should add a heart beat to the multiplier, or some RPC call to fetch latest multiplier.
// tslint:disable-next-line:prefer-function-over-method
public async getProtocolFeeMultiplierAsync(): Promise<BigNumber> {
return constants.PROTOCOL_FEE_MULTIPLIER;
}

public async getGasPriceEstimationOrThrowAsync(shouldHardRefresh?: boolean): Promise<BigNumber> {
if (this.gasPriceEstimation.eq(constants.ZERO_AMOUNT)) {
return this._getGasPriceFromGasStationOrThrowAsync();
Expand All @@ -39,18 +33,6 @@ export class ProtocolFeeUtils {
this._gasPriceHeart.kill();
}

/**
* Calculates protocol fee with protofol fee multiplier for each fill.
*/
public async calculateWorstCaseProtocolFeeAsync<T extends Order>(
orders: T[],
gasPrice: BigNumber,
): Promise<BigNumber> {
const protocolFeeMultiplier = await this.getProtocolFeeMultiplierAsync();
const protocolFee = new BigNumber(orders.length).times(protocolFeeMultiplier).times(gasPrice);
return protocolFee;
}

// tslint:disable-next-line: prefer-function-over-method
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
try {
Expand Down
Loading

0 comments on commit 4b98f93

Please sign in to comment.