@0x/asset-swapper: Rebase and fix some minor bugs.

This commit is contained in:
Lawrence Forman 2020-03-06 18:40:38 -05:00
parent f901c401b7
commit d0805d4bbb
8 changed files with 25 additions and 252 deletions

View File

@ -21,8 +21,9 @@ import {
} from './types';
import { assert } from './utils/assert';
import { calculateLiquidity } from './utils/calculate_liquidity';
import { DexOrderSampler, MarketOperationUtils } from './utils/market_operation_utils';
import { MarketOperationUtils } from './utils/market_operation_utils';
import { createDummyOrderForSampler } from './utils/market_operation_utils/orders';
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
import { orderPrunerUtils } from './utils/order_prune_utils';
import { OrderStateUtils } from './utils/order_state_utils';
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';

View File

@ -1,234 +0,0 @@
import { assert } from '@0x/assert';
import { ContractAddresses } from '@0x/contract-addresses';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';
import { constants } from '../../constants';
import { constants as marketOperationUtilConstants } from './constants';
import {
AggregationError,
CollapsedFill,
ERC20BridgeSource,
NativeCollapsedFill,
OptimizedMarketOrder,
OrderDomain,
} from './types';
const { NULL_BYTES, NULL_ADDRESS, ZERO_AMOUNT } = constants;
const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstants;
export class CreateOrderUtils {
private readonly _contractAddress: ContractAddresses;
// utility function for asset-swapper to ignore market operation utils for specific asset types
public static convertNativeOrderToFullyFillableOptimizedOrders(order: SignedOrder): OptimizedMarketOrder {
return {
...order,
fillableMakerAssetAmount: order.makerAssetAmount,
fillableTakerAssetAmount: order.takerAssetAmount,
fillableTakerFeeAmount: order.takerFee,
fill: {
source: ERC20BridgeSource.Native,
totalMakerAssetAmount: order.makerAssetAmount,
totalTakerAssetAmount: order.takerAssetAmount,
subFills: [],
},
};
}
constructor(contractAddress: ContractAddresses) {
this._contractAddress = contractAddress;
}
// Convert sell fills into orders.
public createSellOrdersFromPath(
orderDomain: OrderDomain,
inputToken: string,
outputToken: string,
path: CollapsedFill[],
bridgeSlippage: number,
liquidityProviderAddress?: string,
): OptimizedMarketOrder[] {
const orders: OptimizedMarketOrder[] = [];
for (const fill of path) {
if (fill.source === ERC20BridgeSource.Native) {
orders.push(createNativeOrder(fill));
} else {
orders.push(
createBridgeOrder(
orderDomain,
fill,
this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress),
outputToken,
inputToken,
bridgeSlippage,
),
);
}
}
return orders;
}
// Convert buy fills into orders.
public createBuyOrdersFromPath(
orderDomain: OrderDomain,
inputToken: string,
outputToken: string,
path: CollapsedFill[],
bridgeSlippage: number,
liquidityProviderAddress?: string,
): OptimizedMarketOrder[] {
const orders: OptimizedMarketOrder[] = [];
for (const fill of path) {
if (fill.source === ERC20BridgeSource.Native) {
orders.push(createNativeOrder(fill));
} else {
orders.push(
createBridgeOrder(
orderDomain,
fill,
this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress),
inputToken,
outputToken,
bridgeSlippage,
true,
),
);
}
}
return orders;
}
private _getBridgeAddressFromSource(source: ERC20BridgeSource, liquidityProviderAddress?: string): string {
switch (source) {
case ERC20BridgeSource.Eth2Dai:
return this._contractAddress.eth2DaiBridge;
case ERC20BridgeSource.Kyber:
return this._contractAddress.kyberBridge;
case ERC20BridgeSource.Uniswap:
return this._contractAddress.uniswapBridge;
case ERC20BridgeSource.CurveUsdcDai:
case ERC20BridgeSource.CurveUsdcDaiUsdt:
case ERC20BridgeSource.CurveUsdcDaiUsdtTusd:
case ERC20BridgeSource.CurveUsdcDaiUsdtBusd:
return this._contractAddress.curveBridge;
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderAddress === undefined) {
throw new Error(
'Cannot create a LiquidityProvider order without a LiquidityProvider pool address.',
);
}
assert.isETHAddressHex('liquidityProviderAddress', liquidityProviderAddress);
return liquidityProviderAddress;
default:
break;
}
throw new Error(AggregationError.NoBridgeForSource);
}
}
function createBridgeOrder(
orderDomain: OrderDomain,
fill: CollapsedFill,
bridgeAddress: string,
makerToken: string,
takerToken: string,
slippage: number,
isBuy: boolean = false,
): OptimizedMarketOrder {
let makerAssetData;
if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(fill.source)) {
const { curveAddress, tokens, version } = constants.DEFAULT_CURVE_OPTS[fill.source];
const fromTokenIdx = tokens.indexOf(takerToken);
const toTokenIdx = tokens.indexOf(makerToken);
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createCurveBridgeData(curveAddress, fromTokenIdx, toTokenIdx, version),
);
} else {
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createBridgeData(takerToken),
);
}
return {
makerAddress: bridgeAddress,
makerAssetData,
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken),
...createCommonOrderFields(orderDomain, fill, slippage, isBuy),
};
}
function createBridgeData(tokenAddress: string): string {
const encoder = AbiEncoder.create([{ name: 'tokenAddress', type: 'address' }]);
return encoder.encode({ tokenAddress });
}
function createCurveBridgeData(
curveAddress: string,
fromTokenIdx: number,
toTokenIdx: number,
version: number,
): string {
const curveBridgeDataEncoder = AbiEncoder.create([
{ name: 'curveAddress', type: 'address' },
{ name: 'fromTokenIdx', type: 'int128' },
{ name: 'toTokenIdx', type: 'int128' },
{ name: 'version', type: 'int128' },
]);
return curveBridgeDataEncoder.encode([curveAddress, fromTokenIdx, toTokenIdx, version]);
}
type CommonOrderFields = Pick<
OptimizedMarketOrder,
Exclude<keyof OptimizedMarketOrder, 'makerAddress' | 'makerAssetData' | 'takerAssetData'>
>;
function createCommonOrderFields(
orderDomain: OrderDomain,
fill: CollapsedFill,
slippage: number,
isBuy: boolean = false,
): CommonOrderFields {
const makerAssetAmountAdjustedWithSlippage = isBuy
? fill.totalMakerAssetAmount
: fill.totalMakerAssetAmount.times(1 - slippage).integerValue(BigNumber.ROUND_DOWN);
const takerAssetAmountAdjustedWithSlippage = isBuy
? fill.totalTakerAssetAmount.times(slippage + 1).integerValue(BigNumber.ROUND_UP)
: fill.totalTakerAssetAmount;
return {
fill,
takerAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
feeRecipientAddress: NULL_ADDRESS,
salt: generatePseudoRandomSalt(),
expirationTimeSeconds: INFINITE_TIMESTAMP_SEC,
makerFeeAssetData: NULL_BYTES,
takerFeeAssetData: NULL_BYTES,
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
makerAssetAmount: makerAssetAmountAdjustedWithSlippage,
fillableMakerAssetAmount: makerAssetAmountAdjustedWithSlippage,
takerAssetAmount: takerAssetAmountAdjustedWithSlippage,
fillableTakerAssetAmount: takerAssetAmountAdjustedWithSlippage,
fillableTakerFeeAmount: ZERO_AMOUNT,
signature: WALLET_SIGNATURE,
...orderDomain,
};
}
function createNativeOrder(fill: CollapsedFill): OptimizedMarketOrder {
return {
fill: {
source: fill.source,
totalMakerAssetAmount: fill.totalMakerAssetAmount,
totalTakerAssetAmount: fill.totalTakerAssetAmount,
subFills: fill.subFills,
},
...(fill as NativeCollapsedFill).nativeOrder,
};
}

View File

@ -20,8 +20,6 @@ import {
OrderDomain,
} from './types';
export { DexOrderSampler } from './sampler';
export class MarketOperationUtils {
private readonly _wethAddress: string;
@ -298,6 +296,7 @@ export class MarketOperationUtils {
orderDomain: this._orderDomain,
contractAddresses: this.contractAddresses,
bridgeSlippage: opts.bridgeSlippage || 0,
liquidityProviderAddress: opts.liquidityProviderAddress,
});
}

View File

@ -119,6 +119,7 @@ export interface CreateOrderFromPathOpts {
orderDomain: OrderDomain;
contractAddresses: ContractAddresses;
bridgeSlippage: number;
liquidityProviderAddress?: string;
}
// Convert sell fills into orders.
@ -135,19 +136,24 @@ export function createOrdersFromPath(path: Fill[], opts: CreateOrderFromPathOpts
return orders;
}
function getBridgeAddressFromSource(source: ERC20BridgeSource, contractAddresses: ContractAddresses): string {
function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrderFromPathOpts): string {
switch (source) {
case ERC20BridgeSource.Eth2Dai:
return contractAddresses.eth2DaiBridge;
return opts.contractAddresses.eth2DaiBridge;
case ERC20BridgeSource.Kyber:
return contractAddresses.kyberBridge;
return opts.contractAddresses.kyberBridge;
case ERC20BridgeSource.Uniswap:
return contractAddresses.uniswapBridge;
return opts.contractAddresses.uniswapBridge;
case ERC20BridgeSource.CurveUsdcDai:
case ERC20BridgeSource.CurveUsdcDaiUsdt:
case ERC20BridgeSource.CurveUsdcDaiUsdtTusd:
case ERC20BridgeSource.CurveUsdcDaiUsdtBusd:
return contractAddresses.curveBridge;
return opts.contractAddresses.curveBridge;
case ERC20BridgeSource.LiquidityProvider:
if (opts.liquidityProviderAddress === undefined) {
throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.');
}
return opts.liquidityProviderAddress;
default:
break;
}
@ -157,7 +163,7 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, contractAddresses
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 bridgeAddress = getBridgeAddressFromSource(fill.source, opts.contractAddresses);
const bridgeAddress = getBridgeAddressFromSource(fill.source, opts);
let makerAssetData;
if (Object.keys(DEFAULT_CURVE_OPTS).includes(fill.source)) {

View File

@ -30,7 +30,7 @@ const RATE_DECIMALS = 8;
function hillClimbToOptimalPath(paths: Fill[][], targetInput: BigNumber): Fill[] {
// Flatten and sort path fills by descending ADJUSTED rate.
const fills = paths
.reduce((acc, p) => acc.concat(p))
.reduce((acc, p) => acc.concat(p), [])
.sort((a, b) => b.adjustedRate.dp(RATE_DECIMALS).comparedTo(a.adjustedRate.dp(RATE_DECIMALS)));
// Build up a path by picking the next best, valid fill until we meet our input target.
const path: Fill[] = [];

View File

@ -1,6 +1,6 @@
import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..';
import { constants } from '../../constants';
import { DEFAULT_CURVE_OPTS } from './constants';
import { BatchedOperation, DexSample } from './types';
/**
@ -233,8 +233,8 @@ export const samplerOperations = {
},
getLiquidityProviderFromRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
takerToken: string,
): BatchedOperation<string> {
return {
encodeCall: contract => {
@ -263,8 +263,8 @@ export const samplerOperations = {
batchedOperation = samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (source === ERC20BridgeSource.Kyber) {
batchedOperation = samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(source)) {
const { curveAddress, tokens } = constants.DEFAULT_CURVE_OPTS[source];
} else if (Object.keys(DEFAULT_CURVE_OPTS).includes(source)) {
const { curveAddress, tokens } = DEFAULT_CURVE_OPTS[source];
const fromTokenIdx = tokens.indexOf(takerToken);
const toTokenIdx = tokens.indexOf(makerToken);
if (fromTokenIdx !== -1 && toTokenIdx !== -1) {

View File

@ -652,7 +652,7 @@ describe('MarketOperationUtils tests', () => {
}),
],
Web3Wrapper.toBaseUnitAmount(10, 18),
{ excludedSources: SELL_SOURCES, numSamples: 4 },
{ excludedSources: SELL_SOURCES, numSamples: 4, bridgeSlippage: 0 },
);
expect(result.length).to.eql(1);
expect(result[0].makerAddress).to.eql(liquidityProviderAddress);
@ -667,8 +667,8 @@ describe('MarketOperationUtils tests', () => {
expect(getSellQuotesParams.sources).contains(ERC20BridgeSource.LiquidityProvider);
expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress);
expect(getLiquidityProviderParams.registryAddress).is.eql(registryAddress);
expect(getLiquidityProviderParams.makerToken).is.eql(xAsset);
expect(getLiquidityProviderParams.takerToken).is.eql(yAsset);
expect(getLiquidityProviderParams.makerToken).is.eql(yAsset);
expect(getLiquidityProviderParams.takerToken).is.eql(xAsset);
});
});

View File

@ -8,8 +8,9 @@ import 'mocha';
import { constants } from '../src/constants';
import { CalculateSwapQuoteOpts, SignedOrderWithFillableAmounts } from '../src/types';
import { DexOrderSampler, MarketOperationUtils } from '../src/utils/market_operation_utils/';
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
import { DEFAULT_GET_MARKET_ORDERS_OPTS, SELL_SOURCES } from '../src/utils/market_operation_utils/constants';
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
import { SwapQuoteCalculator } from '../src/utils/swap_quote_calculator';