* Set AssetSwapper protocol fee multiplier to zero * Set market_operation_utils protocol fee multiplier to zero * Updated CHANGELOG.json * Removed whitespace in CHANGELOG.json * Remove unnecessary timestamp in packages/asset-swapper/CHANGELOG.json Co-authored-by: Lawrence Forman <lawrence@0xproject.com> * Updated param for quote simulation test * Updated quote simulation test * fix failing tests Co-authored-by: Lawrence Forman <lawrence@0xproject.com> Co-authored-by: Lawrence Forman <me@merklejerk.com>
1000 lines
49 KiB
TypeScript
1000 lines
49 KiB
TypeScript
import { constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
|
import { FillQuoteTransformerOrderType, SignatureType } from '@0x/protocol-utils';
|
|
import { BigNumber, hexUtils, NULL_BYTES } from '@0x/utils';
|
|
import * as _ from 'lodash';
|
|
|
|
import { MarketOperation } from '../src/types';
|
|
import {
|
|
CollapsedFill,
|
|
ERC20BridgeSource,
|
|
NativeLimitOrderFillData,
|
|
OptimizedMarketOrder,
|
|
OptimizedMarketOrderBase,
|
|
} from '../src/utils/market_operation_utils/types';
|
|
import {
|
|
fillQuoteOrders,
|
|
QuoteFillOrderCall,
|
|
simulateBestCaseFill,
|
|
simulateWorstCaseFill,
|
|
} from '../src/utils/quote_simulation';
|
|
|
|
// tslint:disable: custom-no-magic-numbers
|
|
|
|
describe('quote_simulation tests', async () => {
|
|
const { NULL_ADDRESS } = constants;
|
|
const ZERO = new BigNumber(0);
|
|
const ONE = new BigNumber(1);
|
|
const MAKER_TOKEN = randomAddress();
|
|
const TAKER_TOKEN = randomAddress();
|
|
const GAS_SCHEDULE = { [ERC20BridgeSource.Uniswap]: _.constant(1), [ERC20BridgeSource.Native]: _.constant(1) };
|
|
|
|
// Check if two numbers are within `maxError` error rate within each other.
|
|
function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-10): void {
|
|
// |n2-n1| / max(|n1|, |n2|)
|
|
const err = n2
|
|
.minus(n1)
|
|
.abs()
|
|
.div(BigNumber.max(n1.abs(), n2.abs()));
|
|
expect(err).to.bignumber.lt(maxError);
|
|
}
|
|
|
|
function createQuoteFillOrders(
|
|
opts: Partial<{
|
|
fillableInput: BigNumber;
|
|
fillableOutput: BigNumber;
|
|
inputFeeRate: number;
|
|
outputFeeRate: number;
|
|
count: number;
|
|
fillsCount: number;
|
|
side: MarketOperation;
|
|
type?: FillQuoteTransformerOrderType;
|
|
}> = {},
|
|
): QuoteFillOrderCall[] {
|
|
const { fillableInput, fillableOutput, inputFeeRate, outputFeeRate, count, fillsCount, side, type } = {
|
|
fillableInput: getRandomOrderSize(),
|
|
fillableOutput: getRandomOrderSize(),
|
|
inputFeeRate: 0,
|
|
outputFeeRate: 0,
|
|
count: 3,
|
|
fillsCount: 3,
|
|
side: MarketOperation.Sell,
|
|
...opts,
|
|
};
|
|
const _inputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const _outputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
|
|
const fillableInputs = subdivideAmount(fillableInput, count);
|
|
const fillableOutputs = subdivideAmount(fillableOutput, count);
|
|
const filledInputs = subdivideAmount(fillableInput.times(0.5), count);
|
|
const filledOutputs: BigNumber[] = [];
|
|
const totalInputs: BigNumber[] = [];
|
|
const totalOutputs: BigNumber[] = [];
|
|
const inputFees: BigNumber[] = [];
|
|
const outputFees: BigNumber[] = [];
|
|
_.times(count).forEach(i => {
|
|
const f = filledInputs[i].div(fillableInputs[i]);
|
|
filledOutputs.push(fillableOutputs[i].times(f).integerValue(BigNumber.ROUND_DOWN));
|
|
totalInputs.push(fillableInputs[i].plus(filledInputs[i]));
|
|
totalOutputs.push(fillableOutputs[i].plus(filledOutputs[i]));
|
|
inputFees.push(totalInputs[i].times(_inputFeeRate).integerValue());
|
|
outputFees.push(totalOutputs[i].times(_outputFeeRate).integerValue());
|
|
});
|
|
return _.times(count, i => {
|
|
return {
|
|
order: createQuoteFillOrderOrder(totalInputs[i], totalOutputs[i], {
|
|
side,
|
|
fillsCount,
|
|
filledInput: filledInputs[i],
|
|
takerInputFee: inputFees[i].abs(),
|
|
takerOutputFee: outputFees[i].abs(),
|
|
type,
|
|
}),
|
|
totalOrderInput: totalInputs[i],
|
|
totalOrderOutput: totalOutputs[i],
|
|
totalOrderInputFee: inputFees[i],
|
|
totalOrderOutputFee: outputFees[i],
|
|
};
|
|
});
|
|
}
|
|
|
|
function createQuoteFillOrderOrder(
|
|
input: BigNumber,
|
|
output: BigNumber,
|
|
opts: Partial<{
|
|
filledInput: BigNumber;
|
|
fillsCount: number;
|
|
side: MarketOperation;
|
|
takerInputFee: BigNumber;
|
|
takerOutputFee: BigNumber;
|
|
type: FillQuoteTransformerOrderType;
|
|
}> = {},
|
|
): OptimizedMarketOrderBase<NativeLimitOrderFillData> {
|
|
const { filledInput, fillsCount, side, takerInputFee, takerOutputFee, type } = _.merge(
|
|
{},
|
|
{
|
|
side: MarketOperation.Sell,
|
|
filledInput: ZERO,
|
|
fillsCount: 3,
|
|
takerInputFee: ZERO,
|
|
takerOutputFee: ZERO,
|
|
type: FillQuoteTransformerOrderType.Limit,
|
|
},
|
|
opts,
|
|
);
|
|
const filledOutput = filledInput
|
|
.div(input)
|
|
.times(output)
|
|
.integerValue(BigNumber.ROUND_DOWN);
|
|
const fillableInput = input.minus(filledInput);
|
|
const fillableOutput = output.minus(filledOutput);
|
|
const makerAmount = side === MarketOperation.Sell ? output : input;
|
|
const takerAmount = side === MarketOperation.Sell ? input : output;
|
|
const fillableMakerAmount = side === MarketOperation.Sell ? fillableOutput : fillableInput;
|
|
const fillableTakerAmount = side === MarketOperation.Sell ? fillableInput : fillableOutput;
|
|
const takerFee = BigNumber.max(takerInputFee, takerOutputFee);
|
|
|
|
const order: OptimizedMarketOrderBase<NativeLimitOrderFillData> = {
|
|
source: ERC20BridgeSource.Native,
|
|
makerToken: MAKER_TOKEN,
|
|
takerToken: TAKER_TOKEN,
|
|
makerAmount: fillableMakerAmount,
|
|
takerAmount: fillableTakerAmount,
|
|
fillData: {
|
|
order: {
|
|
makerToken: MAKER_TOKEN,
|
|
makerAmount,
|
|
takerToken: TAKER_TOKEN,
|
|
takerAmount,
|
|
maker: NULL_ADDRESS,
|
|
taker: NULL_ADDRESS,
|
|
sender: NULL_ADDRESS,
|
|
salt: ZERO,
|
|
chainId: 1,
|
|
pool: NULL_BYTES,
|
|
verifyingContract: NULL_ADDRESS,
|
|
expiry: ZERO,
|
|
feeRecipient: NULL_ADDRESS,
|
|
takerTokenFeeAmount: takerFee,
|
|
},
|
|
signature: { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign },
|
|
maxTakerTokenFillAmount: fillableTakerAmount,
|
|
},
|
|
type,
|
|
fills: createOrderCollapsedFills(fillableInput, fillableOutput, fillsCount),
|
|
};
|
|
return order;
|
|
}
|
|
const nativeSourcePathId = hexUtils.random();
|
|
function createOrderCollapsedFills(input: BigNumber, output: BigNumber, count: number): CollapsedFill[] {
|
|
const inputs = subdivideAmount(input, count);
|
|
const outputs = subdivideAmount(output, count);
|
|
return _.times(count, i => {
|
|
const subFillInputs = subdivideAmount(inputs[i], count);
|
|
const subFillOutputs = subdivideAmount(outputs[i], count);
|
|
return {
|
|
type: FillQuoteTransformerOrderType.Bridge,
|
|
sourcePathId: nativeSourcePathId,
|
|
source: ERC20BridgeSource.Uniswap,
|
|
fillData: {},
|
|
input: inputs[i],
|
|
output: outputs[i],
|
|
subFills: _.times(count, j => ({
|
|
input: subFillInputs[j],
|
|
output: subFillOutputs[j],
|
|
})),
|
|
};
|
|
});
|
|
}
|
|
|
|
function countCollapsedFills(fillOrders: QuoteFillOrderCall[] | OptimizedMarketOrder[]): number {
|
|
let count = 0;
|
|
if ((fillOrders as any)[0].fills) {
|
|
const orders = (fillOrders as any) as OptimizedMarketOrder[];
|
|
for (const o of orders) {
|
|
count += o.fills.length;
|
|
}
|
|
} else {
|
|
const orders = (fillOrders as any) as QuoteFillOrderCall[];
|
|
for (const fo of orders) {
|
|
count += fo.order.fills.length;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function randomSide(): MarketOperation {
|
|
return _.sampleSize(Object.values(MarketOperation), 1)[0];
|
|
}
|
|
|
|
function getRandomOrderSize(): BigNumber {
|
|
return getRandomInteger('100e18', '1000e18');
|
|
}
|
|
|
|
function getRandomFeeRate(): number {
|
|
return _.random(0.01, 0.25, true);
|
|
}
|
|
|
|
function assertEqualRates(actual: number | BigNumber, expected: number | BigNumber): void {
|
|
expect(new BigNumber(actual).times(1e4).integerValue()).to.bignumber.eq(
|
|
new BigNumber(expected).times(1e4).integerValue(),
|
|
);
|
|
}
|
|
|
|
function subdivideAmount(amount: BigNumber, count: number): BigNumber[] {
|
|
const amounts = [];
|
|
for (let i = 0; i < count; ++i) {
|
|
const remaining = amount.minus(BigNumber.sum(0, ...amounts));
|
|
if (i !== count - 1) {
|
|
amounts.push(remaining.times(Math.random()).integerValue());
|
|
} else {
|
|
amounts.push(remaining.integerValue());
|
|
}
|
|
}
|
|
return amounts;
|
|
}
|
|
|
|
describe('fillQuoteOrders()', () => {
|
|
describe('single order', () => {
|
|
it('can exactly fill one order', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('can partially fill one simple order', () => {
|
|
const side = randomSide();
|
|
const fillsCount = 1;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
|
const expectedOutputFilledAmount = inputFillAmount
|
|
.div(fillableInput)
|
|
.times(fillableOutput)
|
|
.integerValue();
|
|
assertRoughlyEquals(totalFilledOutput, expectedOutputFilledAmount);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(1);
|
|
});
|
|
|
|
it('can partially fill one batched order', () => {
|
|
const side = randomSide();
|
|
const fillsCount = 3;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.gte(1);
|
|
expect(result.gas).to.lte(fillsCount);
|
|
});
|
|
|
|
it('does not over fill one order', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('can exactly fill one order with input fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('can partially fill one order with input fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const inputFillAmount = totalFillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.lte(fillsCount);
|
|
});
|
|
|
|
it('does not over fill one order with input fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const inputFillAmount = totalFillableInput.times(3 / 2).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('can exactly fill one order with output fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('can partial fill one order with output fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.lte(fillsCount);
|
|
});
|
|
|
|
it('does not over fill one order with output fees', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(1);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
|
|
it('does not charge a protocol fee for rfq orders', () => {
|
|
const side = randomSide();
|
|
const fillsCount = _.random(1, 3);
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
fillsCount,
|
|
count: 1,
|
|
type: FillQuoteTransformerOrderType.Rfq,
|
|
});
|
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(0);
|
|
expect(result.gas).to.eq(fillsCount);
|
|
});
|
|
});
|
|
|
|
describe('multiple orders', () => {
|
|
it('can exactly fill orders', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
|
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('can partial fill orders', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.gte(1);
|
|
});
|
|
|
|
it('does not over fill orders', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
|
const fillOrders = createQuoteFillOrders({ fillableInput, fillableOutput, side });
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
expect(totalFilledInput).to.bignumber.eq(fillableInput);
|
|
expect(totalFilledOutput).to.bignumber.eq(fillableOutput);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('can exactly fill orders with input fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, totalFillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('can partial fill orders with input fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const inputFillAmount = totalFillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
|
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('does not over fill orders with input fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
});
|
|
const signedInputFeeRate = side === MarketOperation.Sell ? inputFeeRate : -inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const inputFillAmount = totalFillableInput.times(3 / 2).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, totalFillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, fillableOutput);
|
|
assertEqualRates(result.inputFee.div(result.input), signedInputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('can exactly fill orders with output fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, fillableInput, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('can partial fill orders with output fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, inputFillAmount);
|
|
expect(totalFilledOutput).to.bignumber.lt(totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.lte(fillOrders.length);
|
|
expect(result.gas).to.lte(countCollapsedFills(fillOrders));
|
|
});
|
|
|
|
it('does not over fill orders with output fees', () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
});
|
|
const signedOutputFeeRate = side === MarketOperation.Sell ? -outputFeeRate : outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const inputFillAmount = fillableInput.times(3 / 2).integerValue();
|
|
const result = fillQuoteOrders(fillOrders, inputFillAmount, ONE, GAS_SCHEDULE);
|
|
const totalFilledInput = result.input.plus(result.inputFee);
|
|
const totalFilledOutput = result.output.plus(result.outputFee);
|
|
assertRoughlyEquals(totalFilledInput, fillableInput);
|
|
assertRoughlyEquals(totalFilledOutput, totalFillableOutput);
|
|
assertEqualRates(result.outputFee.div(result.output), signedOutputFeeRate);
|
|
expect(result.protocolFee).to.bignumber.eq(fillOrders.length);
|
|
expect(result.gas).to.eq(countCollapsedFills(fillOrders));
|
|
});
|
|
});
|
|
});
|
|
|
|
function slipOrder(
|
|
order: OptimizedMarketOrderBase<NativeLimitOrderFillData>,
|
|
orderSlippage: number,
|
|
side: MarketOperation,
|
|
): OptimizedMarketOrder {
|
|
const makerScaling = side === MarketOperation.Sell ? 1 - orderSlippage : 1;
|
|
const takerScaling = side === MarketOperation.Sell ? 1 : orderSlippage + 1;
|
|
|
|
// tslint:disable:next-line no-unnecessary-type-assertion
|
|
const nativeFillData = order.fillData!;
|
|
const slippedFillData = {
|
|
order: {
|
|
...nativeFillData.order,
|
|
takerAmount: nativeFillData.order.takerAmount.times(takerScaling),
|
|
makerAmount: nativeFillData.order.makerAmount.times(makerScaling),
|
|
},
|
|
signature: nativeFillData.signature,
|
|
maxTakerTokenFillAmount: nativeFillData.maxTakerTokenFillAmount.times(takerScaling),
|
|
};
|
|
return {
|
|
...order,
|
|
makerAmount: order.makerAmount.times(makerScaling),
|
|
takerAmount: order.takerAmount.times(takerScaling),
|
|
fillData: slippedFillData,
|
|
};
|
|
}
|
|
|
|
describe('simulateBestCaseFill()', () => {
|
|
it('ignores order slippage', async () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const orderSlippage = getRandomFeeRate();
|
|
const fillOrders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
});
|
|
const orders = fillOrders.map(fo =>
|
|
slipOrder(fo.order as OptimizedMarketOrderBase<NativeLimitOrderFillData>, orderSlippage, side),
|
|
);
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
if (side === MarketOperation.Sell) {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.eq(fillableInput);
|
|
} else {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableInput);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
|
}
|
|
});
|
|
|
|
it('can fully fill orders', async () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
expect(result.gas).to.eq(countCollapsedFills(orders));
|
|
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
|
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerAssetAmount).to.bignumber.eq(result.totalTakerAssetAmount);
|
|
if (side === MarketOperation.Sell) {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.eq(fillableInput);
|
|
} else {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(fillableInput);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.eq(fillableOutput);
|
|
}
|
|
});
|
|
|
|
it('can partial fill orders', async () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
const inputFillAmount = fillableInput.times(Math.random()).integerValue();
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: inputFillAmount,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
expect(result.gas).to.gt(0);
|
|
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
|
expect(result.takerFeeTakerAssetAmount).to.bignumber.eq(0);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerAssetAmount).to.bignumber.eq(result.totalTakerAssetAmount);
|
|
if (side === MarketOperation.Sell) {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.lt(fillableOutput);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.eq(inputFillAmount);
|
|
} else {
|
|
expect(result.totalMakerAssetAmount).to.be.bignumber.eq(inputFillAmount);
|
|
expect(result.totalTakerAssetAmount).to.be.bignumber.lt(fillableOutput);
|
|
}
|
|
});
|
|
|
|
it('can fully fill sell orders with "input" fees', async () => {
|
|
const side = MarketOperation.Sell;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
}).map(fo => fo.order);
|
|
const signedInputFeeRate = inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: totalFillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
|
|
assertRoughlyEquals(result.takerAssetAmount, fillableInput);
|
|
assertRoughlyEquals(result.totalTakerAssetAmount, totalFillableInput);
|
|
assertRoughlyEquals(result.makerAssetAmount, fillableOutput);
|
|
assertRoughlyEquals(result.totalMakerAssetAmount, fillableOutput);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
});
|
|
|
|
it('can partially fill sell orders with "input" fees', async () => {
|
|
const side = MarketOperation.Sell;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const inputFeeRate = getRandomFeeRate();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
inputFeeRate,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
const signedInputFeeRate = inputFeeRate;
|
|
const totalFillableInput = fillableInput.times(signedInputFeeRate + 1).integerValue();
|
|
const inputFillAmount = totalFillableInput.times(2 / 3).integerValue();
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: inputFillAmount,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
expect(result.gas).to.gt(0);
|
|
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
|
assertRoughlyEquals(result.totalTakerAssetAmount, inputFillAmount);
|
|
expect(result.makerAssetAmount).to.bignumber.lt(fillableOutput);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
});
|
|
|
|
it('can fully fill buy orders with "output" fees', async () => {
|
|
const side = MarketOperation.Buy;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
const signedOutputFeeRate = outputFeeRate;
|
|
const totalFillableOutput = fillableOutput.times(signedOutputFeeRate + 1).integerValue();
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
expect(result.gas).to.eq(countCollapsedFills(orders));
|
|
expect(result.protocolFeeAmount).to.bignumber.eq(orders.length);
|
|
|
|
assertRoughlyEquals(result.makerAssetAmount, fillableInput);
|
|
assertRoughlyEquals(result.totalMakerAssetAmount, fillableInput);
|
|
assertRoughlyEquals(result.takerAssetAmount, fillableOutput);
|
|
assertRoughlyEquals(result.totalTakerAssetAmount, totalFillableOutput);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
});
|
|
|
|
it('can partially fill buy orders with "output" fees', async () => {
|
|
const side = MarketOperation.Buy;
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const outputFeeRate = getRandomFeeRate();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
outputFeeRate,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
const inputFillAmount = fillableInput.times(2 / 3).integerValue();
|
|
const result = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: inputFillAmount,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
expect(result.gas).to.gt(0);
|
|
expect(result.protocolFeeAmount).to.bignumber.gt(0);
|
|
assertRoughlyEquals(result.totalMakerAssetAmount, inputFillAmount);
|
|
expect(result.takerAssetAmount).to.bignumber.lt(fillableOutput);
|
|
expect(result.makerAssetAmount).to.bignumber.eq(result.totalMakerAssetAmount);
|
|
expect(result.takerFeeMakerAssetAmount).to.bignumber.eq(0);
|
|
});
|
|
});
|
|
|
|
describe('simulateWorstCaseFill()', () => {
|
|
it('includes order slippage', async () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const slippage = getRandomFeeRate();
|
|
const orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
}).map(fo => fo.order);
|
|
|
|
const result = simulateWorstCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE, slippage },
|
|
});
|
|
if (side === MarketOperation.Sell) {
|
|
const slippedOutput = fillableOutput.times(1 - slippage).integerValue();
|
|
assertRoughlyEquals(result.totalMakerAssetAmount, slippedOutput);
|
|
assertRoughlyEquals(result.totalTakerAssetAmount, fillableInput);
|
|
} else {
|
|
const slippedOutput = fillableOutput.times(slippage + 1).integerValue();
|
|
assertRoughlyEquals(result.totalMakerAssetAmount, fillableInput);
|
|
assertRoughlyEquals(result.totalTakerAssetAmount, slippedOutput);
|
|
}
|
|
});
|
|
|
|
it('expects worse price than the best case, even if orders are unsorted', async () => {
|
|
const side = randomSide();
|
|
const fillableInput = getRandomOrderSize();
|
|
const fillableOutput = getRandomOrderSize();
|
|
const orderSlippage = getRandomFeeRate();
|
|
let orders = createQuoteFillOrders({
|
|
fillableInput,
|
|
fillableOutput,
|
|
side,
|
|
}).map(fo =>
|
|
slipOrder(fo.order as OptimizedMarketOrderBase<NativeLimitOrderFillData>, orderSlippage, side),
|
|
);
|
|
orders = [...orders.slice(1), orders[0]];
|
|
const bestCase = simulateBestCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, protocolFeeMultiplier: ONE },
|
|
});
|
|
const worstCase = simulateWorstCaseFill({
|
|
orders,
|
|
side,
|
|
fillAmount: fillableInput,
|
|
gasPrice: ONE,
|
|
opts: { gasSchedule: GAS_SCHEDULE, slippage: orderSlippage },
|
|
});
|
|
const bestPrice = bestCase.makerAssetAmount.div(bestCase.totalTakerAssetAmount);
|
|
const worstPrice = worstCase.makerAssetAmount.div(worstCase.totalTakerAssetAmount);
|
|
expect(worstPrice).to.be.bignumber.lt(bestPrice);
|
|
});
|
|
});
|
|
}); // tslint:disable: max-file-line-count
|