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:
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user