Update asset-swapper WIP

This commit is contained in:
Jacob Evans 2020-01-08 18:54:02 +10:00
parent 4a2575136f
commit 08640e8575
No known key found for this signature in database
GPG Key ID: 2036DA2ADDFB0842
9 changed files with 657 additions and 13 deletions

View File

@ -41,11 +41,6 @@ contract ERC20BridgeSampler is
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3;
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 250e3;
struct OrdersAndSample {
uint256[] orderFillableTakerAssetAmounts;
uint256[][] makerTokenAmountsBySource;
}
function queryMultipleOrdersAndSampleBuys(
LibOrder.Order[][] memory orders,
bytes[][] memory orderSignatures,

View File

@ -23,6 +23,34 @@ import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
interface IERC20BridgeSampler {
struct OrdersAndSample {
uint256[] orderFillableTakerAssetAmounts;
uint256[][] makerTokenAmountsBySource;
}
function queryMultipleOrdersAndSampleBuys(
LibOrder.Order[][] calldata orders,
bytes[][] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata makerTokenAmounts
)
external
view
returns (
OrdersAndSample[] memory ordersAndSamples
);
function queryMultipleOrdersAndSampleSells(
LibOrder.Order[][] calldata orders,
bytes[][] calldata orderSignatures,
address[] calldata sources,
uint256[] calldata makerTokenAmounts
)
external
view
returns (
OrdersAndSample[] memory ordersAndSamples
);
/// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query.

View File

@ -226,6 +226,54 @@ export class SwapQuoter {
options,
)) as MarketBuySwapQuote;
}
public async getMultipleMarketBuySwapQuoteForAssetDataAsync(
makerAssetData: string[],
takerAssetData: string,
makerAssetBuyAmount: BigNumber[],
options: Partial<SwapQuoteRequestOpts> = {},
): Promise<Array<MarketBuySwapQuote | undefined>> {
makerAssetBuyAmount.map((a, i) => assert.isBigNumber(`makerAssetBuyAmount[${i}]`, a));
let gasPrice: BigNumber;
const { slippagePercentage, ...calculateSwapQuoteOpts } = _.merge(
{},
constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
options,
);
if (!!options.gasPrice) {
gasPrice = options.gasPrice;
assert.isBigNumber('gasPrice', gasPrice);
} else {
gasPrice = await this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
}
const orders = await Promise.all(
makerAssetData.map(async m => {
// get the relevant orders for the makerAsset
let prunedOrders = await this._getSignedOrdersAsync(m, takerAssetData);
// if no native orders, pass in a dummy order for the sampler to have required metadata for sampling
if (prunedOrders.length === 0) {
prunedOrders = [
dummyOrderUtils.createDummyOrderForSampler(
m,
takerAssetData,
this._contractAddresses.uniswapBridge,
),
];
}
return prunedOrders;
}),
);
const swapQuotes = await this._swapQuoteCalculator.calculateMultipleMarketBuySwapQuoteAsync(
orders,
makerAssetBuyAmount,
slippagePercentage,
gasPrice,
calculateSwapQuoteOpts,
);
return swapQuotes;
}
/**
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.

View File

@ -137,26 +137,69 @@ export class MarketOperationUtils {
DexOrderSampler.getSampleAmounts(makerAmount, _opts.numSamples),
difference(BUY_SOURCES, _opts.excludedSources),
);
const signedOrderWithFillableAmounts = this._createBuyOrdersPathFromSamplerResultIfExists(
nativeOrders,
makerAmount,
fillableAmounts,
dexQuotes,
_opts,
);
if (!signedOrderWithFillableAmounts) {
throw new Error(AggregationError.NoOptimalPath);
}
return signedOrderWithFillableAmounts;
}
public async getMultipleMarketBuyOrdersAsync(
nativeOrders: SignedOrder[][],
makerAmount: BigNumber[],
opts?: Partial<GetMarketOrdersOpts>,
): Promise<Array<SignedOrderWithFillableAmounts[] | undefined>> {
if (nativeOrders.length === 0) {
throw new Error(AggregationError.EmptyOrders);
}
const _opts = {
...DEFAULT_GET_MARKET_ORDERS_OPTS,
...opts,
};
const multipleSampleResults = await this._dexSampler.getMultipleFillableAmountsAndSampleMarketBuyAsync(
nativeOrders,
makerAmount,
difference(BUY_SOURCES, _opts.excludedSources),
);
return multipleSampleResults.map((r, i) =>
this._createBuyOrdersPathFromSamplerResultIfExists(nativeOrders[i], makerAmount[i], r[0], r[1], _opts),
);
}
private _createBuyOrdersPathFromSamplerResultIfExists(
nativeOrders: SignedOrder[],
makerAmount: BigNumber,
nativeOrderFillableAmounts: BigNumber[],
dexQuotes: DexSample[][],
opts: GetMarketOrdersOpts,
): SignedOrderWithFillableAmounts[] | undefined {
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
nativeOrders,
fillableAmounts,
nativeOrderFillableAmounts,
MarketOperation.Buy,
);
const prunedNativePath = pruneDustFillsFromNativePath(
createBuyPathFromNativeOrders(nativeOrdersWithFillableAmounts),
makerAmount,
_opts.dustFractionThreshold,
opts.dustFractionThreshold,
);
const clippedNativePath = clipPathToInput(prunedNativePath, makerAmount);
const dexPaths = createPathsFromDexQuotes(dexQuotes, _opts.noConflicts);
const dexPaths = createPathsFromDexQuotes(dexQuotes, opts.noConflicts);
const allPaths = [...dexPaths];
const allFills = flattenDexPaths(dexPaths);
// If native orders are allowed, splice them in.
if (!_opts.excludedSources.includes(ERC20BridgeSource.Native)) {
if (!opts.excludedSources.includes(ERC20BridgeSource.Native)) {
allPaths.splice(0, 0, clippedNativePath);
allFills.splice(0, 0, ...clippedNativePath);
}
const optimizer = new FillsOptimizer(_opts.runLimit, true);
const optimizer = new FillsOptimizer(opts.runLimit, true);
const upperBoundPath = pickBestUpperBoundPath(allPaths, makerAmount, true);
const optimalPath = optimizer.optimize(
// Sorting the orders by price effectively causes the optimizer to walk
@ -167,7 +210,7 @@ export class MarketOperationUtils {
upperBoundPath,
);
if (!optimalPath) {
throw new Error(AggregationError.NoOptimalPath);
return undefined;
}
const [inputToken, outputToken] = getOrderTokens(nativeOrders[0]);
return this._createOrderUtils.createBuyOrdersFromPath(
@ -175,7 +218,7 @@ export class MarketOperationUtils {
inputToken,
outputToken,
simplifyPath(optimalPath),
_opts.bridgeSlippage,
opts.bridgeSlippage,
);
}
}

View File

@ -50,6 +50,37 @@ export class DexOrderSampler {
return [fillableAmount, quotes];
}
public async getMultipleFillableAmountsAndSampleMarketBuyAsync(
nativeOrders: SignedOrder[][],
sampleAmounts: BigNumber[],
sources: ERC20BridgeSource[],
): Promise<Array<[BigNumber[], DexSample[][]]>> {
const signatures = nativeOrders.map(o => o.map(i => i.signature));
const fillableAmountsAndSamples = await this._samplerContract
.queryMultipleOrdersAndSampleBuys(
nativeOrders,
signatures,
sources.map(s => SOURCE_TO_ADDRESS[s]),
sampleAmounts,
)
.callAsync();
const multipleSamples: Array<[BigNumber[], DexSample[][]]> = [];
fillableAmountsAndSamples.map((sampleResult, i) => {
const rawSamples = sampleResult.makerTokenAmountsBySource;
const fillableAmount = sampleResult.orderFillableTakerAssetAmounts;
const quotes = rawSamples.map((rawDexSamples, sourceIdx) => {
const source = sources[sourceIdx];
return rawDexSamples.map(sample => ({
source,
input: sampleAmounts[i],
output: sample,
}));
});
multipleSamples.push([fillableAmount, quotes]);
});
return multipleSamples;
}
public async getFillableAmountsAndSampleMarketSellAsync(
nativeOrders: SignedOrder[],
sampleAmounts: BigNumber[],

View File

@ -63,6 +63,87 @@ export class SwapQuoteCalculator {
)) as MarketBuySwapQuote;
}
public async calculateMultipleMarketBuySwapQuoteAsync(
prunedOrders: SignedOrder[][],
takerAssetFillAmount: BigNumber[],
slippagePercentage: number,
gasPrice: BigNumber,
opts: CalculateSwapQuoteOpts,
): Promise<Array<MarketBuySwapQuote | undefined>> {
return (await this._calculateMultipleBuySwapQuoteAsync(
prunedOrders,
takerAssetFillAmount,
slippagePercentage,
gasPrice,
MarketOperation.Buy,
opts,
)) as Array<MarketBuySwapQuote | undefined>;
}
private async _calculateMultipleBuySwapQuoteAsync(
prunedOrders: SignedOrder[][],
assetFillAmounts: BigNumber[],
slippagePercentage: number,
gasPrice: BigNumber,
operation: MarketOperation,
opts: CalculateSwapQuoteOpts,
): Promise<Array<MarketBuySwapQuote | undefined>> {
const assetFillAmountsWithSlippage = assetFillAmounts.map(a =>
a.plus(a.multipliedBy(slippagePercentage).integerValue()),
);
const resultMultipleOrders = await this._marketOperationUtils.getMultipleMarketBuyOrdersAsync(
prunedOrders,
assetFillAmountsWithSlippage,
opts,
);
const createSwapQuote = async (
makerAssetData: string,
takerAssetData: string,
resultOrders: SignedOrderWithFillableAmounts[],
assetFillAmount: BigNumber,
) => {
const bestCaseQuoteInfo = await this._calculateQuoteInfoAsync(
createBestCaseOrders(resultOrders, operation, opts.bridgeSlippage),
assetFillAmount,
gasPrice,
operation,
);
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts
// such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = await this._calculateQuoteInfoAsync(
_.reverse(resultOrders.slice()),
assetFillAmount,
gasPrice,
operation,
);
const quoteBase = {
takerAssetData,
makerAssetData,
orders: resultOrders,
bestCaseQuoteInfo,
worstCaseQuoteInfo,
gasPrice,
makerAssetFillAmount: assetFillAmount,
type: MarketOperation.Buy,
};
return quoteBase;
};
const swapQuotes: Array<MarketBuySwapQuote | undefined> = [];
for (let i = 0; i < resultMultipleOrders.length; i++) {
// assetData information for the result
const takerAssetData = prunedOrders[i][0].takerAssetData;
const makerAssetData = prunedOrders[i][0].makerAssetData;
const resultOrders = resultMultipleOrders[i];
if (resultOrders) {
const quote = await createSwapQuote(makerAssetData, takerAssetData, resultOrders, assetFillAmounts[i]);
swapQuotes.push(quote as MarketBuySwapQuote);
} else {
swapQuotes.push(undefined);
}
}
return swapQuotes;
}
private async _calculateSwapQuoteAsync(
prunedOrders: SignedOrder[],
assetFillAmount: BigNumber,

View File

@ -22,7 +22,7 @@
"devUtils": "0x5f53f2aa72cb3a9371bf3c58e8fb3a313478b2f4",
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
"erc20BridgeSampler": "0x1b402fdb5ee87f989c11e3963557e89cc313b6c0",
"erc20BridgeSampler": "0xdf291ac755a47ef44e18fecc71f1c13a07d8b303",
"kyberBridge": "0xf342f3a80fdc9b48713d58fe97e17f5cc764ee62",
"eth2DaiBridge": "0xe97ea901d034ba2e018155264f77c417ce7717f9",
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",

View File

@ -71,6 +71,110 @@
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"components": [
{ "internalType": "address", "name": "makerAddress", "type": "address" },
{ "internalType": "address", "name": "takerAddress", "type": "address" },
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
{ "internalType": "address", "name": "senderAddress", "type": "address" },
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
],
"internalType": "struct LibOrder.Order[][]",
"name": "orders",
"type": "tuple[][]"
},
{ "internalType": "bytes[][]", "name": "orderSignatures", "type": "bytes[][]" },
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
],
"name": "queryMultipleOrdersAndSampleBuys",
"outputs": [
{
"components": [
{
"internalType": "uint256[]",
"name": "orderFillableTakerAssetAmounts",
"type": "uint256[]"
},
{
"internalType": "uint256[][]",
"name": "makerTokenAmountsBySource",
"type": "uint256[][]"
}
],
"internalType": "struct IERC20BridgeSampler.OrdersAndSample[]",
"name": "ordersAndSamples",
"type": "tuple[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"components": [
{ "internalType": "address", "name": "makerAddress", "type": "address" },
{ "internalType": "address", "name": "takerAddress", "type": "address" },
{ "internalType": "address", "name": "feeRecipientAddress", "type": "address" },
{ "internalType": "address", "name": "senderAddress", "type": "address" },
{ "internalType": "uint256", "name": "makerAssetAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "takerAssetAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "makerFee", "type": "uint256" },
{ "internalType": "uint256", "name": "takerFee", "type": "uint256" },
{ "internalType": "uint256", "name": "expirationTimeSeconds", "type": "uint256" },
{ "internalType": "uint256", "name": "salt", "type": "uint256" },
{ "internalType": "bytes", "name": "makerAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "takerAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "makerFeeAssetData", "type": "bytes" },
{ "internalType": "bytes", "name": "takerFeeAssetData", "type": "bytes" }
],
"internalType": "struct LibOrder.Order[][]",
"name": "orders",
"type": "tuple[][]"
},
{ "internalType": "bytes[][]", "name": "orderSignatures", "type": "bytes[][]" },
{ "internalType": "address[]", "name": "sources", "type": "address[]" },
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
],
"name": "queryMultipleOrdersAndSampleSells",
"outputs": [
{
"components": [
{
"internalType": "uint256[]",
"name": "orderFillableTakerAssetAmounts",
"type": "uint256[]"
},
{
"internalType": "uint256[][]",
"name": "makerTokenAmountsBySource",
"type": "uint256[][]"
}
],
"internalType": "struct IERC20BridgeSampler.OrdersAndSample[]",
"name": "ordersAndSamples",
"type": "tuple[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [

View File

@ -281,6 +281,204 @@ export class IERC20BridgeSamplerContract extends BaseContract {
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'orders',
type: 'tuple[][]',
components: [
{
name: 'makerAddress',
type: 'address',
},
{
name: 'takerAddress',
type: 'address',
},
{
name: 'feeRecipientAddress',
type: 'address',
},
{
name: 'senderAddress',
type: 'address',
},
{
name: 'makerAssetAmount',
type: 'uint256',
},
{
name: 'takerAssetAmount',
type: 'uint256',
},
{
name: 'makerFee',
type: 'uint256',
},
{
name: 'takerFee',
type: 'uint256',
},
{
name: 'expirationTimeSeconds',
type: 'uint256',
},
{
name: 'salt',
type: 'uint256',
},
{
name: 'makerAssetData',
type: 'bytes',
},
{
name: 'takerAssetData',
type: 'bytes',
},
{
name: 'makerFeeAssetData',
type: 'bytes',
},
{
name: 'takerFeeAssetData',
type: 'bytes',
},
],
},
{
name: 'orderSignatures',
type: 'bytes[][]',
},
{
name: 'sources',
type: 'address[]',
},
{
name: 'makerTokenAmounts',
type: 'uint256[]',
},
],
name: 'queryMultipleOrdersAndSampleBuys',
outputs: [
{
name: 'ordersAndSamples',
type: 'tuple[]',
components: [
{
name: 'orderFillableTakerAssetAmounts',
type: 'uint256[]',
},
{
name: 'makerTokenAmountsBySource',
type: 'uint256[][]',
},
],
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'orders',
type: 'tuple[][]',
components: [
{
name: 'makerAddress',
type: 'address',
},
{
name: 'takerAddress',
type: 'address',
},
{
name: 'feeRecipientAddress',
type: 'address',
},
{
name: 'senderAddress',
type: 'address',
},
{
name: 'makerAssetAmount',
type: 'uint256',
},
{
name: 'takerAssetAmount',
type: 'uint256',
},
{
name: 'makerFee',
type: 'uint256',
},
{
name: 'takerFee',
type: 'uint256',
},
{
name: 'expirationTimeSeconds',
type: 'uint256',
},
{
name: 'salt',
type: 'uint256',
},
{
name: 'makerAssetData',
type: 'bytes',
},
{
name: 'takerAssetData',
type: 'bytes',
},
{
name: 'makerFeeAssetData',
type: 'bytes',
},
{
name: 'takerFeeAssetData',
type: 'bytes',
},
],
},
{
name: 'orderSignatures',
type: 'bytes[][]',
},
{
name: 'sources',
type: 'address[]',
},
{
name: 'makerTokenAmounts',
type: 'uint256[]',
},
],
name: 'queryMultipleOrdersAndSampleSells',
outputs: [
{
name: 'ordersAndSamples',
type: 'tuple[]',
components: [
{
name: 'orderFillableTakerAssetAmounts',
type: 'uint256[]',
},
{
name: 'makerTokenAmountsBySource',
type: 'uint256[][]',
},
],
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
@ -654,6 +852,122 @@ export class IERC20BridgeSamplerContract extends BaseContract {
},
};
}
public queryMultipleOrdersAndSampleBuys(
orders: Array<{
makerAddress: string;
takerAddress: string;
feeRecipientAddress: string;
senderAddress: string;
makerAssetAmount: BigNumber;
takerAssetAmount: BigNumber;
makerFee: BigNumber;
takerFee: BigNumber;
expirationTimeSeconds: BigNumber;
salt: BigNumber;
makerAssetData: string;
takerAssetData: string;
makerFeeAssetData: string;
takerFeeAssetData: string;
}>[],
orderSignatures: string[][],
sources: string[],
makerTokenAmounts: BigNumber[],
): ContractFunctionObj<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
> {
const self = (this as any) as IERC20BridgeSamplerContract;
assert.isArray('orders', orders);
assert.isArray('orderSignatures', orderSignatures);
assert.isArray('sources', sources);
assert.isArray('makerTokenAmounts', makerTokenAmounts);
const functionSignature =
'queryMultipleOrdersAndSampleBuys((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[])';
return {
async callAsync(
callData: Partial<CallData> = {},
defaultBlock?: BlockParam,
): Promise<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
return abiEncoder.strictDecodeReturnValue<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
orders,
orderSignatures,
sources,
makerTokenAmounts,
]);
},
};
}
public queryMultipleOrdersAndSampleSells(
orders: Array<{
makerAddress: string;
takerAddress: string;
feeRecipientAddress: string;
senderAddress: string;
makerAssetAmount: BigNumber;
takerAssetAmount: BigNumber;
makerFee: BigNumber;
takerFee: BigNumber;
expirationTimeSeconds: BigNumber;
salt: BigNumber;
makerAssetData: string;
takerAssetData: string;
makerFeeAssetData: string;
takerFeeAssetData: string;
}>[],
orderSignatures: string[][],
sources: string[],
makerTokenAmounts: BigNumber[],
): ContractFunctionObj<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
> {
const self = (this as any) as IERC20BridgeSamplerContract;
assert.isArray('orders', orders);
assert.isArray('orderSignatures', orderSignatures);
assert.isArray('sources', sources);
assert.isArray('makerTokenAmounts', makerTokenAmounts);
const functionSignature =
'queryMultipleOrdersAndSampleSells((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[][],bytes[][],address[],uint256[])';
return {
async callAsync(
callData: Partial<CallData> = {},
defaultBlock?: BlockParam,
): Promise<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
return abiEncoder.strictDecodeReturnValue<
Array<{ orderFillableTakerAssetAmounts: BigNumber[]; makerTokenAmountsBySource: BigNumber[][] }>
>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
orders,
orderSignatures,
sources,
makerTokenAmounts,
]);
},
};
}
/**
* Query native orders and sample buy quotes on multiple DEXes at once.
* @param orders Native orders to query.