BalancerBridge (#2613)

* Add BalancerBridge and Sampler functions

* Update sampler artifacts/wrappers

* Add Balancer support to AssetSwapper + related refactoring

* Make use of GraphQL instead of sampler

* "fix" build and add mainnet BalancerBridge tests

* address some comments

* add balancer cache and fix DexSampler tests

* lint

* wip: tests for balancer sampler ops

* Fix market operation utils test

* balancer unit tests

* Return a buy quote of 0 if the buy amount exceeds the Balancer pool's balance

* Dynamic fee estimation

* Update contract addresses, export BalancerBridge wrapper

* Update changelogs

* Fix bugs discovered via simbot

* Fix issues in balancer_utils

* override `BigNumber.config` in configured_bignumber.ts

* Special case Balancer subops in  too

* Address some more comments

* Address Balancer performance issue

* Performance improvements

* Address comment

* Fix tests

Co-authored-by: xianny <xianny@gmail.com>
This commit is contained in:
mzhu25
2020-07-14 19:18:50 -07:00
committed by GitHub
parent 18bc701e8b
commit ff9c9241d8
46 changed files with 1279 additions and 602 deletions

View File

@@ -12,9 +12,15 @@ import { SignedOrder } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
import {
BalancerPool,
computeBalancerBuyQuote,
computeBalancerSellQuote,
} from '../src/utils/market_operation_utils/balancer_utils';
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
import { ERC20BridgeSource } from '../src/utils/market_operation_utils/types';
import { ERC20BridgeSource, FillData } from '../src/utils/market_operation_utils/types';
import { MockBalancerPoolsCache } from './utils/mock_balancer_pools_cache';
import { MockSamplerContract } from './utils/mock_sampler_contract';
const CHAIN_ID = 1;
@@ -149,7 +155,7 @@ describe('DexSampler tests', () => {
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const sampler = new MockSamplerContract({
sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => {
sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
expect(registryAddress).to.eq(registry);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
@@ -158,12 +164,13 @@ describe('DexSampler tests', () => {
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
await DexOrderSampler.ops.getSellQuotesAsync(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
wethAddress,
dexOrderSampler.balancerPoolsCache,
registry,
),
);
@@ -173,6 +180,7 @@ describe('DexSampler tests', () => {
source: 'LiquidityProvider',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
fillData: undefined,
},
],
]);
@@ -183,7 +191,7 @@ describe('DexSampler tests', () => {
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const sampler = new MockSamplerContract({
sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => {
sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
expect(registryAddress).to.eq(registry);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
@@ -192,12 +200,13 @@ describe('DexSampler tests', () => {
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getBuyQuotes(
await DexOrderSampler.ops.getBuyQuotesAsync(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
wethAddress,
dexOrderSampler.balancerPoolsCache,
registry,
),
);
@@ -207,6 +216,7 @@ describe('DexSampler tests', () => {
source: 'LiquidityProvider',
output: toBaseUnitAmount(999),
input: toBaseUnitAmount(1000),
fillData: undefined,
},
],
]);
@@ -233,12 +243,13 @@ describe('DexSampler tests', () => {
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
await DexOrderSampler.ops.getSellQuotesAsync(
[ERC20BridgeSource.MultiBridge],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
randomAddress(),
dexOrderSampler.balancerPoolsCache,
randomAddress(),
multiBridge,
),
@@ -249,6 +260,7 @@ describe('DexSampler tests', () => {
source: 'MultiBridge',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
fillData: undefined,
},
],
]);
@@ -412,72 +424,92 @@ describe('DexSampler tests', () => {
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
},
sampleSellsFromUniswapV2: (path, fillAmounts) => {
expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
if (path.length === 2) {
expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
} else if (path.length === 3) {
expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
} else {
expect(path).to.have.lengthOf.within(2, 3);
}
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [quotes] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
await DexOrderSampler.ops.getSellQuotesAsync(
sources,
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
wethAddress,
dexOrderSampler.balancerPoolsCache,
),
);
expect(quotes).to.be.length(sources.length);
const expectedQuotes = sources.map(s =>
expectedTakerFillAmounts.map(a => ({
source: s,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
fillData:
s === ERC20BridgeSource.UniswapV2
? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
: ((undefined as any) as FillData),
})),
);
expect(quotes).to.deep.eq(expectedQuotes);
const uniswapV2ETHQuotes = [
expectedTakerFillAmounts.map(a => ({
source: ERC20BridgeSource.UniswapV2,
input: a,
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
fillData: {
tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
},
})),
];
// extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB)
expect(quotes).to.have.lengthOf(sources.length + 1);
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
});
it('getSellQuotes() includes ETH for Uniswap_V2_ETH', async () => {
it('getSellQuotes() uses samples from Balancer', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const sources = [ERC20BridgeSource.UniswapV2Eth];
const ratesBySource: RatesBySource = {
[ERC20BridgeSource.UniswapV2Eth]: getRandomFloat(0, 100),
};
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
const sampler = new MockSamplerContract({
sampleSellsFromUniswapV2: (path, fillAmounts) => {
expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2Eth]).integerValue());
const pools: BalancerPool[] = [generateBalancerPool(), generateBalancerPool()];
const balancerPoolsCache = new MockBalancerPoolsCache({
getPoolsForPairAsync: async (takerToken: string, makerToken: string) => {
expect(takerToken).equal(expectedTakerToken);
expect(makerToken).equal(expectedMakerToken);
return Promise.resolve(pools);
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(new MockSamplerContract({}), balancerPoolsCache);
const [quotes] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
sources,
await DexOrderSampler.ops.getSellQuotesAsync(
[ERC20BridgeSource.Balancer],
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
wethAddress,
dexOrderSampler.balancerPoolsCache,
),
);
expect(quotes).to.be.length(sources.length);
const expectedQuotes = sources.map(s =>
const expectedQuotes = pools.map(p =>
expectedTakerFillAmounts.map(a => ({
source: s,
source: ERC20BridgeSource.Balancer,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
output: computeBalancerSellQuote(p, a),
fillData: { poolAddress: p.id },
})),
);
expect(quotes).to.have.lengthOf(2); // one array per pool
expect(quotes).to.deep.eq(expectedQuotes);
});
it('getBuyQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap];
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
const ratesBySource: RatesBySource = {
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
@@ -498,78 +530,85 @@ describe('DexSampler tests', () => {
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
},
sampleBuysFromUniswapV2: (path, fillAmounts) => {
expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
if (path.length === 2) {
expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
} else if (path.length === 3) {
expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
} else {
expect(path).to.have.lengthOf.within(2, 3);
}
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [quotes] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getBuyQuotes(
await DexOrderSampler.ops.getBuyQuotesAsync(
sources,
expectedMakerToken,
expectedTakerToken,
expectedMakerFillAmounts,
wethAddress,
dexOrderSampler.balancerPoolsCache,
),
);
expect(quotes).to.be.length(sources.length);
const expectedQuotes = sources.map(s =>
expectedMakerFillAmounts.map(a => ({
source: s,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
fillData:
s === ERC20BridgeSource.UniswapV2
? { tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
: ((undefined as any) as FillData),
})),
);
expect(quotes).to.deep.eq(expectedQuotes);
const uniswapV2ETHQuotes = [
expectedMakerFillAmounts.map(a => ({
source: ERC20BridgeSource.UniswapV2,
input: a,
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
fillData: {
tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
},
})),
];
// extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB)
expect(quotes).to.have.lengthOf(sources.length + 1);
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
});
it('getBuyQuotes() includes ETH for Uniswap_V2_ETH', async () => {
it('getBuyQuotes() uses samples from Balancer', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2Eth];
const ratesBySource: RatesBySource = {
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
[ERC20BridgeSource.UniswapV2Eth]: getRandomFloat(0, 100),
};
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
const sampler = new MockSamplerContract({
sampleBuysFromUniswap: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
},
sampleBuysFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
},
sampleBuysFromUniswapV2: (path, fillAmounts) => {
expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2Eth]).integerValue());
const pools: BalancerPool[] = [generateBalancerPool(), generateBalancerPool()];
const balancerPoolsCache = new MockBalancerPoolsCache({
getPoolsForPairAsync: async (takerToken: string, makerToken: string) => {
expect(takerToken).equal(expectedTakerToken);
expect(makerToken).equal(expectedMakerToken);
return Promise.resolve(pools);
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(new MockSamplerContract({}), balancerPoolsCache);
const [quotes] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getBuyQuotes(
sources,
await DexOrderSampler.ops.getBuyQuotesAsync(
[ERC20BridgeSource.Balancer],
expectedMakerToken,
expectedTakerToken,
expectedMakerFillAmounts,
wethAddress,
dexOrderSampler.balancerPoolsCache,
),
);
expect(quotes).to.be.length(sources.length);
const expectedQuotes = sources.map(s =>
const expectedQuotes = pools.map(p =>
expectedMakerFillAmounts.map(a => ({
source: s,
source: ERC20BridgeSource.Balancer,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
output: computeBalancerBuyQuote(p, a),
fillData: { poolAddress: p.id },
})),
);
expect(quotes).to.have.lengthOf(2); // one set per pool
expect(quotes).to.deep.eq(expectedQuotes);
});
});
@@ -600,4 +639,14 @@ describe('DexSampler tests', () => {
});
});
});
function generateBalancerPool(): BalancerPool {
return {
id: randomAddress(),
balanceIn: getRandomInteger(1, 1e18),
balanceOut: getRandomInteger(1, 1e18),
weightIn: getRandomInteger(0, 1e5),
weightOut: getRandomInteger(0, 1e5),
swapFee: getRandomInteger(0, 1e5),
};
}
// tslint:disable-next-line: max-file-line-count

View File

@@ -16,16 +16,10 @@ import * as _ from 'lodash';
import { MarketOperation, SignedOrderWithFillableAmounts } from '../src';
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
import {
BUY_SOURCES,
DEFAULT_CURVE_OPTS,
POSITIVE_INF,
SELL_SOURCES,
ZERO_AMOUNT,
} from '../src/utils/market_operation_utils/constants';
import { BUY_SOURCES, POSITIVE_INF, SELL_SOURCES, ZERO_AMOUNT } from '../src/utils/market_operation_utils/constants';
import { createFillPaths } from '../src/utils/market_operation_utils/fills';
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
import { DexSample, ERC20BridgeSource, NativeFillData } from '../src/utils/market_operation_utils/types';
import { DexSample, ERC20BridgeSource, FillData, NativeFillData } from '../src/utils/market_operation_utils/types';
// tslint:disable: custom-no-magic-numbers
describe('MarketOperationUtils tests', () => {
@@ -93,10 +87,7 @@ describe('MarketOperationUtils tests', () => {
case UNISWAP_V2_BRIDGE_ADDRESS.toLowerCase():
return ERC20BridgeSource.UniswapV2;
case CURVE_BRIDGE_ADDRESS.toLowerCase():
const curveSource = Object.keys(DEFAULT_CURVE_OPTS).filter(
k => assetData.indexOf(DEFAULT_CURVE_OPTS[k].curveAddress.slice(2)) !== -1,
);
return curveSource[0] as ERC20BridgeSource;
return ERC20BridgeSource.Curve;
default:
break;
}
@@ -132,12 +123,18 @@ describe('MarketOperationUtils tests', () => {
chainId: CHAIN_ID,
};
function createSamplesFromRates(source: ERC20BridgeSource, inputs: Numberish[], rates: Numberish[]): DexSample[] {
function createSamplesFromRates(
source: ERC20BridgeSource,
inputs: Numberish[],
rates: Numberish[],
fillData?: FillData,
): DexSample[] {
const samples: DexSample[] = [];
inputs.forEach((input, i) => {
const rate = rates[i];
samples.push({
source,
fillData: fillData || DEFAULT_FILL_DATA[source],
input: new BigNumber(input),
output: new BigNumber(input)
.minus(i === 0 ? 0 : samples[i - 1].input)
@@ -161,10 +158,10 @@ describe('MarketOperationUtils tests', () => {
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
return (
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
_makerToken: string,
_takerToken: string,
fillAmounts: BigNumber[],
wethAddress: string,
_wethAddress: string,
) => {
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
};
@@ -184,10 +181,11 @@ describe('MarketOperationUtils tests', () => {
takerToken: string,
fillAmounts: BigNumber[],
wethAddress: string,
_balancerPoolsCache?: any,
liquidityProviderAddress?: string,
) => {
liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress;
liquidityPoolParams.sources = sources;
liquidityPoolParams.sources = liquidityPoolParams.sources.concat(sources);
return tradeOperation(rates)(
sources,
makerToken,
@@ -203,10 +201,10 @@ describe('MarketOperationUtils tests', () => {
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
return (
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
_makerToken: string,
_takerToken: string,
fillAmounts: BigNumber[],
wethAddress: string,
_wethAddress: string,
) => {
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))));
};
@@ -229,18 +227,18 @@ describe('MarketOperationUtils tests', () => {
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
return (
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
fillAmounts: BigNumber[],
wethAddress: string,
_sources: ERC20BridgeSource[],
_makerToken: string,
_takerToken: string,
_fillAmounts: BigNumber[],
_wethAddress: string,
) => {
return new BigNumber(rate);
};
}
function getLiquidityProviderFromRegistry(): GetLiquidityProviderFromRegistryOperation {
return (registryAddress: string, takerToken: string, makerToken: string): string => {
return (_registryAddress: string, _takerToken: string, _makerToken: string): string => {
return NULL_ADDRESS;
};
}
@@ -288,17 +286,23 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Eth2Dai]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Kyber]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.Uniswap]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.UniswapV2]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.UniswapV2Eth]: createDecreasingRates(NUM_SAMPLES),
[ERC20BridgeSource.CurveUsdcDai]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdt]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtTusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtBusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtSusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.UniswapV2]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Balancer]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Curve]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
};
interface FillDataBySource {
[source: string]: FillData;
}
const DEFAULT_FILL_DATA: FillDataBySource = {
[ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] },
[ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() },
[ERC20BridgeSource.Curve]: { poolAddress: randomAddress(), fromTokenIdx: 0, toTokenIdx: 1 },
};
const DEFAULT_OPS = {
getOrderFillableTakerAmounts(orders: SignedOrder[]): BigNumber[] {
return orders.map(o => o.takerAssetAmount);
@@ -306,9 +310,9 @@ describe('MarketOperationUtils tests', () => {
getOrderFillableMakerAmounts(orders: SignedOrder[]): BigNumber[] {
return orders.map(o => o.makerAssetAmount);
},
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
getMedianSellRate: createGetMedianSellRate(1),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
getMedianSellRateAsync: createGetMedianSellRate(1),
getLiquidityProviderFromRegistry: getLiquidityProviderFromRegistry(),
};
@@ -346,11 +350,7 @@ describe('MarketOperationUtils tests', () => {
sampleDistributionBase: 1,
bridgeSlippage: 0,
maxFallbackSlippage: 100,
excludedSources: [
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2Eth,
...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]),
],
excludedSources: [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.Curve, ERC20BridgeSource.Balancer],
allowFallback: false,
shouldBatchBridgeOrders: false,
};
@@ -363,9 +363,9 @@ describe('MarketOperationUtils tests', () => {
const numSamples = _.random(1, NUM_SAMPLES);
let actualNumSamples = 0;
replaceSamplerOps({
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
actualNumSamples = amounts.length;
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -378,9 +378,9 @@ describe('MarketOperationUtils tests', () => {
it('polls all DEXes if `excludedSources` is empty', async () => {
let sourcesPolled: ERC20BridgeSource[] = [];
replaceSamplerOps({
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sources.slice();
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sourcesPolled.concat(sources.slice());
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -396,7 +396,7 @@ describe('MarketOperationUtils tests', () => {
DEFAULT_RATES,
);
replaceSamplerOps({
getSellQuotes: fn,
getSellQuotesAsync: fn,
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
@@ -419,9 +419,9 @@ describe('MarketOperationUtils tests', () => {
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
let sourcesPolled: ERC20BridgeSource[] = [];
replaceSamplerOps({
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sources.slice();
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
getSellQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sourcesPolled.concat(sources.slice());
return DEFAULT_OPS.getSellQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -477,7 +477,7 @@ describe('MarketOperationUtils tests', () => {
expect(improvedOrders).to.not.be.length(0);
for (const order of improvedOrders) {
const expectedMakerAmount = order.fills[0].output;
const slippage = 1 - order.makerAssetAmount.div(expectedMakerAmount.plus(1)).toNumber();
const slippage = new BigNumber(1).minus(order.makerAssetAmount.div(expectedMakerAmount.plus(1)));
assertRoughlyEquals(slippage, bridgeSlippage, 1);
}
});
@@ -485,11 +485,11 @@ describe('MarketOperationUtils tests', () => {
it('can mix convex sources', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1];
rates[ERC20BridgeSource.UniswapV2] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Kyber] = [0, 0, 0, 0]; // unused
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -499,7 +499,7 @@ describe('MarketOperationUtils tests', () => {
const orderSources = improvedOrders.map(o => o.fills[0].source);
const expectedSources = [
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
];
@@ -514,18 +514,20 @@ describe('MarketOperationUtils tests', () => {
const nativeFeeRate = 0.06;
const rates: RatesBySource = {
[ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, 0.93, 0.92, 0.91]
[ERC20BridgeSource.UniswapV2]: [0.96, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
};
const feeSchedule = {
[ERC20BridgeSource.Native]: FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
[ERC20BridgeSource.Native]: _.constant(
FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
),
};
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -535,7 +537,7 @@ describe('MarketOperationUtils tests', () => {
const orderSources = improvedOrders.map(o => o.fills[0].source);
const expectedSources = [
ERC20BridgeSource.Native,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Native,
];
@@ -551,16 +553,18 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1],
// Effectively [0.8, ~0.5, ~0, ~0]
[ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2],
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
};
const feeSchedule = {
[ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
[ERC20BridgeSource.Uniswap]: _.constant(
FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_MAKER_RATE),
),
};
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -571,7 +575,7 @@ describe('MarketOperationUtils tests', () => {
const expectedSources = [
ERC20BridgeSource.Native,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
];
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
});
@@ -580,12 +584,12 @@ describe('MarketOperationUtils tests', () => {
const rates: RatesBySource = {
[ERC20BridgeSource.Kyber]: [0, 0, 0, 0], // Won't use
[ERC20BridgeSource.Eth2Dai]: [0.5, 0.85, 0.75, 0.75], // Concave
[ERC20BridgeSource.UniswapV2]: [0.96, 0.2, 0.1, 0.1],
[ERC20BridgeSource.Uniswap]: [0.96, 0.2, 0.1, 0.1],
[ERC20BridgeSource.Native]: [0.95, 0.2, 0.2, 0.1],
};
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_MAKER_RATE),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -595,7 +599,7 @@ describe('MarketOperationUtils tests', () => {
const orderSources = improvedOrders.map(o => o.fills[0].source);
const expectedSources = [
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Native,
];
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
@@ -604,11 +608,11 @@ describe('MarketOperationUtils tests', () => {
it('fallback orders use different sources', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5];
rates[ERC20BridgeSource.UniswapV2] = [0.6, 0.05, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01];
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -620,7 +624,7 @@ describe('MarketOperationUtils tests', () => {
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
];
const secondSources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber];
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
@@ -630,11 +634,11 @@ describe('MarketOperationUtils tests', () => {
it('does not create a fallback if below maxFallbackSlippage', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.UniswapV2] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
rates[ERC20BridgeSource.Kyber] = [0.35, 0.2, 0.01, 0.01];
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -642,7 +646,7 @@ describe('MarketOperationUtils tests', () => {
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
);
const orderSources = improvedOrders.map(o => o.fills[0].source);
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2];
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
const secondSources: ERC20BridgeSource[] = [];
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort());
@@ -667,7 +671,7 @@ describe('MarketOperationUtils tests', () => {
] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityProviderAddress);
replaceSamplerOps({
getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT],
getSellQuotes: getSellQuotesFn,
getSellQuotesAsync: getSellQuotesFn,
getLiquidityProviderFromRegistry: getLiquidityProviderFn,
});
@@ -706,12 +710,12 @@ describe('MarketOperationUtils tests', () => {
it('batches contiguous bridge sources', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.UniswapV2] = [1, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [1, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.CurveUsdcDai] = [0.48, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Curve] = [0.48, 0.01, 0.01, 0.01];
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getSellQuotesAsync: createGetMultipleSellQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -721,7 +725,7 @@ describe('MarketOperationUtils tests', () => {
numSamples: 4,
excludedSources: [
ERC20BridgeSource.Kyber,
..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.CurveUsdcDai),
..._.without(DEFAULT_OPTS.excludedSources, ERC20BridgeSource.Curve),
],
shouldBatchBridgeOrders: true,
},
@@ -729,9 +733,9 @@ describe('MarketOperationUtils tests', () => {
expect(improvedOrders).to.be.length(3);
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
expect(orderFillSources).to.deep.eq([
[ERC20BridgeSource.UniswapV2],
[ERC20BridgeSource.Uniswap],
[ERC20BridgeSource.Native],
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.CurveUsdcDai],
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Curve],
]);
});
});
@@ -748,10 +752,10 @@ describe('MarketOperationUtils tests', () => {
bridgeSlippage: 0,
maxFallbackSlippage: 100,
excludedSources: [
...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]),
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2Eth,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
],
allowFallback: false,
shouldBatchBridgeOrders: false,
@@ -765,9 +769,9 @@ describe('MarketOperationUtils tests', () => {
const numSamples = _.random(1, 16);
let actualNumSamples = 0;
replaceSamplerOps({
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
actualNumSamples = amounts.length;
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -780,9 +784,9 @@ describe('MarketOperationUtils tests', () => {
it('polls all DEXes if `excludedSources` is empty', async () => {
let sourcesPolled: ERC20BridgeSource[] = [];
replaceSamplerOps({
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sources.slice();
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sourcesPolled.concat(sources.slice());
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -798,7 +802,7 @@ describe('MarketOperationUtils tests', () => {
DEFAULT_RATES,
);
replaceSamplerOps({
getBuyQuotes: fn,
getBuyQuotesAsync: fn,
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
@@ -821,9 +825,9 @@ describe('MarketOperationUtils tests', () => {
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
let sourcesPolled: ERC20BridgeSource[] = [];
replaceSamplerOps({
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sources.slice();
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
getBuyQuotesAsync: (sources, makerToken, takerToken, amounts, wethAddress) => {
sourcesPolled = sourcesPolled.concat(sources.slice());
return DEFAULT_OPS.getBuyQuotesAsync(sources, makerToken, takerToken, amounts, wethAddress);
},
});
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
@@ -879,7 +883,7 @@ describe('MarketOperationUtils tests', () => {
expect(improvedOrders).to.not.be.length(0);
for (const order of improvedOrders) {
const expectedTakerAmount = order.fills[0].output;
const slippage = order.takerAssetAmount.div(expectedTakerAmount.plus(1)).toNumber() - 1;
const slippage = order.takerAssetAmount.div(expectedTakerAmount.plus(1)).minus(1);
assertRoughlyEquals(slippage, bridgeSlippage, 1);
}
});
@@ -887,10 +891,10 @@ describe('MarketOperationUtils tests', () => {
it('can mix convex sources', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [0.4, 0.3, 0.2, 0.1];
rates[ERC20BridgeSource.UniswapV2] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Uniswap] = [0.5, 0.05, 0.05, 0.05];
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -900,7 +904,7 @@ describe('MarketOperationUtils tests', () => {
const orderSources = improvedOrders.map(o => o.fills[0].source);
const expectedSources = [
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
];
@@ -915,18 +919,20 @@ describe('MarketOperationUtils tests', () => {
const nativeFeeRate = 0.06;
const rates: RatesBySource = {
[ERC20BridgeSource.Native]: [1, 0.99, 0.98, 0.97], // Effectively [0.94, ~0.93, ~0.92, ~0.91]
[ERC20BridgeSource.UniswapV2]: [0.96, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Uniswap]: [0.96, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Eth2Dai]: [0.95, 0.1, 0.1, 0.1],
[ERC20BridgeSource.Kyber]: [0.1, 0.1, 0.1, 0.1],
};
const feeSchedule = {
[ERC20BridgeSource.Native]: FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
[ERC20BridgeSource.Native]: _.constant(
FILL_AMOUNT.div(4)
.times(nativeFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
),
};
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -935,7 +941,7 @@ describe('MarketOperationUtils tests', () => {
);
const orderSources = improvedOrders.map(o => o.fills[0].source);
const expectedSources = [
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
@@ -950,17 +956,19 @@ describe('MarketOperationUtils tests', () => {
const rates: RatesBySource = {
[ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1],
// Effectively [0.8, ~0.5, ~0, ~0]
[ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2],
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
[ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1],
};
const feeSchedule = {
[ERC20BridgeSource.UniswapV2]: FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
[ERC20BridgeSource.Uniswap]: _.constant(
FILL_AMOUNT.div(4)
.times(uniswapFeeRate)
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
),
};
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
getMedianSellRateAsync: createGetMedianSellRate(ETH_TO_TAKER_RATE),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -971,7 +979,7 @@ describe('MarketOperationUtils tests', () => {
const expectedSources = [
ERC20BridgeSource.Native,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
];
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
});
@@ -979,10 +987,10 @@ describe('MarketOperationUtils tests', () => {
it('fallback orders use different sources', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [0.9, 0.8, 0.5, 0.5];
rates[ERC20BridgeSource.UniswapV2] = [0.6, 0.05, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [0.6, 0.05, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -994,7 +1002,7 @@ describe('MarketOperationUtils tests', () => {
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
ERC20BridgeSource.Native,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Uniswap,
];
const secondSources = [ERC20BridgeSource.Eth2Dai];
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
@@ -1004,10 +1012,10 @@ describe('MarketOperationUtils tests', () => {
it('does not create a fallback if below maxFallbackSlippage', async () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.UniswapV2] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [1, 1, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -1015,7 +1023,7 @@ describe('MarketOperationUtils tests', () => {
{ ...DEFAULT_OPTS, numSamples: 4, allowFallback: true, maxFallbackSlippage: 0.25 },
);
const orderSources = improvedOrders.map(o => o.fills[0].source);
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2];
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.Uniswap];
const secondSources: ERC20BridgeSource[] = [];
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
expect(orderSources.slice(firstSources.length).sort()).to.deep.eq(secondSources.sort());
@@ -1025,9 +1033,9 @@ describe('MarketOperationUtils tests', () => {
const rates: RatesBySource = {};
rates[ERC20BridgeSource.Native] = [0.5, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.01, 0.01, 0.01];
rates[ERC20BridgeSource.UniswapV2] = [0.48, 0.47, 0.01, 0.01];
rates[ERC20BridgeSource.Uniswap] = [0.48, 0.47, 0.01, 0.01];
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getBuyQuotesAsync: createGetMultipleBuyQuotesOperationFromRates(rates),
});
const improvedOrders = await marketOperationUtils.getMarketBuyOrdersAsync(
createOrdersFromBuyRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -1042,7 +1050,7 @@ describe('MarketOperationUtils tests', () => {
const orderFillSources = improvedOrders.map(o => o.fills.map(f => f.source));
expect(orderFillSources).to.deep.eq([
[ERC20BridgeSource.Native],
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.UniswapV2],
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap],
]);
});
});
@@ -1078,7 +1086,7 @@ describe('MarketOperationUtils tests', () => {
};
const orders = [smallOrder, largeOrder];
const feeSchedule = {
[ERC20BridgeSource.Native]: new BigNumber(2e5),
[ERC20BridgeSource.Native]: _.constant(2e5),
};
it('penalizes native fill based on target amount when target is smaller', () => {

View File

@@ -22,7 +22,7 @@ describe('quote_simulation tests', async () => {
const TAKER_TOKEN = randomAddress();
const DEFAULT_MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
const DEFAULT_TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
const GAS_SCHEDULE = { [ERC20BridgeSource.Native]: 1 };
const GAS_SCHEDULE = { [ERC20BridgeSource.Native]: _.constant(1) };
// Check if two numbers are within `maxError` error rate within each other (default 1 bps).
function assertRoughlyEquals(n1: BigNumber, n2: BigNumber, maxError: BigNumber | number = 1e-12): void {

View File

@@ -0,0 +1,24 @@
import { BalancerPool, BalancerPoolsCache } from '../../src/utils/market_operation_utils/balancer_utils';
export interface Handlers {
getPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<BalancerPool[]>;
_fetchPoolsForPairAsync: (takerToken: string, makerToken: string) => Promise<BalancerPool[]>;
}
export class MockBalancerPoolsCache extends BalancerPoolsCache {
constructor(public handlers: Partial<Handlers>) {
super();
}
public async getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
return this.handlers.getPoolsForPairAsync
? this.handlers.getPoolsForPairAsync(takerToken, makerToken)
: super.getPoolsForPairAsync(takerToken, makerToken);
}
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
return this.handlers._fetchPoolsForPairAsync
? this.handlers._fetchPoolsForPairAsync(takerToken, makerToken)
: super._fetchPoolsForPairAsync(takerToken, makerToken);
}
}

View File

@@ -226,6 +226,9 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
}
private _callEncodedFunction(callData: string): string {
if (callData === '0x') {
return callData;
}
// tslint:disable-next-line: custom-no-magic-numbers
const selector = hexUtils.slice(callData, 0, 4);
for (const [name, handler] of Object.entries(this._handlers)) {