protocol/packages/asset-swapper/test/dex_sampler_test.ts
Jacob Evans 3f4bb933d1
feat: v4 final (#136)
* v4 FillQuoteTransformer (#104)

* Update FQT to support v4 orders

* `@0x/contracts-zero-ex`: Tweak FQT
`@0x/contracts-zero-ex`: Drop `ERC20BridgeTransfer` event and add `PartialQuoteFill` event.

* `@0x/contracts-utils`: Add `LibSafeMathV06.downcastToUint128()`

* `@0x/protocol-utils`: Update transformer utils for V4 FQT

* `@0x/contracts-zero-ex`: Fixing FQT tests...

* `@0x/contracts-zero-ex`: rename FQT bridge event

* `@0x/contracts-zero-ex`: Un-`only` tests

* `@0x/migrations`: Update `BridgeAdapter` deployment

* `@0x/contracts-integrations`: Delete `mtx_tests`

* `@0x/protocol-utils`: Address review comments

* `@0x/contracts-zero-ex`: Address review comments

* `@0x/migrations`: Update migrations

Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* v4: Asset-swapper (main branch) (#113)

* refactor quote_requestor

* WIP v4/asset-swapper: Clean up SwapQuoter and remove @0x/orderbook

* Start replacing SignedOrder everywhere

* wip: new order type

* wip

* remove order-utils from most places

* hack: Play around with VerboseX types (#119)

* hack: Play around with VerboseX types

* More hacks

* Fix up the bridgeData encodings

* Rework Orderbook return type

* feat: Don't charge a protocol fee for RFQ orders WIP (#121)

* fix simple build errors

* simplify types a little

* remove SwapQuoteCalculator: unnecessary abstraction

* Fix all ./src build errors; make types consistent

* export more types for use in 0x API; modify Orderbook interface

* stop overriding APIOrder

* feat: RFQ v4 + consolidated bridge encoders (#125)

* feat: check if taker address is contract

* Rework bridge data

* Worst case adjustments

* RFQT v4

* Future/v4 validate orders (#126)

* RFQT v4

* v4 validate native orders

* use default invalid signature

* refactor rfqt validations in swap quoter

* fix types

* fix RFQT unlisted api key

* remove priceAwareRFQFlag

* adjust maker/taker amounts

* update JSON schemas

* filter zero fillable orders

Co-authored-by: xianny <xianny@gmail.com>

* fix type export

Co-authored-by: xianny <xianny@gmail.com>

* remove order-utils as much as possible

* work on tests compile

* Comment out quote reporter test

* updated tests

* restore order-utils accidental changes

* some lints

* Remove old fill_test

* ts lint disable for now

* update quote report

* Re-enable quote report tests

* make fill data required field

* fix lint

* type guards

* force fillData as required

* fix lint

* fix naming

* exports

* adjust MultiBridge by slippage

* cleanups (checkpoint 1)

* cleanup types (checkpoint #2)

* remove unused deps

* `@0x/contract-addresses`: Deploy new FQT (#129)

Co-authored-by: Lawrence Forman <me@merklejerk.com>

* commit bump to republish

* DRY up the rfqt mocker

* fix: Balancer load top pools (#131)

* fix: Balancer load top 250 pools

* refetch top pools on an interval

Co-authored-by: Jacob Evans <jacob@dekz.net>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* Update post rebase

* prettier

* Remove test helpers exported in asset-swapper

* Clean up from review comments

* prettier

* lint

* recreate rfqt mocker

* change merge and INVALID_SIGNATURE

Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>
Co-authored-by: Xianny <8582774+xianny@users.noreply.github.com>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
2021-02-10 19:20:15 +10:00

678 lines
31 KiB
TypeScript

import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import {
constants,
expect,
getRandomFloat,
getRandomInteger,
randomAddress,
toBaseUnitAmount,
} from '@0x/contracts-test-utils';
import { FillQuoteTransformerOrderType, LimitOrderFields, SignatureType } from '@0x/protocol-utils';
import { BigNumber, hexUtils, NULL_ADDRESS, NULL_BYTES } from '@0x/utils';
import { Pool } from '@balancer-labs/sor/dist/types';
import * as _ from 'lodash';
import { SignedOrder } from '../src/types';
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
import { ERC20BridgeSource, TokenAdjacencyGraph } from '../src/utils/market_operation_utils/types';
import { MockBalancerPoolsCache } from './utils/mock_balancer_pools_cache';
import { MockSamplerContract } from './utils/mock_sampler_contract';
import { generatePseudoRandomSalt } from './utils/utils';
const CHAIN_ID = 1;
// tslint:disable: custom-no-magic-numbers
describe('DexSampler tests', () => {
const MAKER_TOKEN = randomAddress();
const TAKER_TOKEN = randomAddress();
const wethAddress = getContractAddressesForChainOrThrow(CHAIN_ID).etherToken;
const exchangeAddress = getContractAddressesForChainOrThrow(CHAIN_ID).exchange;
const exchangeProxyAddress = getContractAddressesForChainOrThrow(CHAIN_ID).exchangeProxy;
const tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [wethAddress] };
describe('getSampleAmounts()', () => {
const FILL_AMOUNT = getRandomInteger(1, 1e18);
const NUM_SAMPLES = 16;
it('generates the correct number of amounts', () => {
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
expect(amounts).to.be.length(NUM_SAMPLES);
});
it('first amount is nonzero', () => {
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
expect(amounts[0]).to.not.bignumber.eq(0);
});
it('last amount is the fill amount', () => {
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
expect(amounts[NUM_SAMPLES - 1]).to.bignumber.eq(FILL_AMOUNT);
});
it('can generate a single amount', () => {
const amounts = getSampleAmounts(FILL_AMOUNT, 1);
expect(amounts).to.be.length(1);
expect(amounts[0]).to.bignumber.eq(FILL_AMOUNT);
});
it('generates ascending amounts', () => {
const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
for (const i of _.times(NUM_SAMPLES).slice(1)) {
const prev = amounts[i - 1];
const amount = amounts[i];
expect(prev).to.bignumber.lt(amount);
}
});
});
function createOrder(overrides?: Partial<LimitOrderFields>): SignedOrder<LimitOrderFields> {
const o: SignedOrder<LimitOrderFields> = {
order: {
salt: generatePseudoRandomSalt(),
expiry: getRandomInteger(0, 2 ** 64),
makerToken: MAKER_TOKEN,
takerToken: TAKER_TOKEN,
makerAmount: getRandomInteger(1, 1e18),
takerAmount: getRandomInteger(1, 1e18),
takerTokenFeeAmount: constants.ZERO_AMOUNT,
chainId: CHAIN_ID,
pool: NULL_BYTES,
feeRecipient: NULL_ADDRESS,
sender: NULL_ADDRESS,
maker: NULL_ADDRESS,
taker: NULL_ADDRESS,
verifyingContract: exchangeProxyAddress,
...overrides,
},
signature: { v: 1, r: hexUtils.random(), s: hexUtils.random(), signatureType: SignatureType.EthSign },
type: FillQuoteTransformerOrderType.Limit,
};
return o;
}
const ORDERS = _.times(4, () => createOrder());
const SIMPLE_ORDERS = ORDERS.map(o => _.omit(o, ['signature', 'chainId']));
describe('operations', () => {
// TODO jacob
it.skip('getLimitOrderFillableMakerAssetAmounts()', async () => {
const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
const sampler = new MockSamplerContract({
getLimitOrderFillableMakerAssetAmounts: (orders, signatures) => {
expect(orders).to.deep.eq(SIMPLE_ORDERS);
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
return expectedFillableAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeAddress),
);
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
});
// TODO jacob
it.skip('getLimitOrderFillableTakerAssetAmounts()', async () => {
const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
const sampler = new MockSamplerContract({
getLimitOrderFillableTakerAssetAmounts: (orders, signatures) => {
expect(orders).to.deep.eq(SIMPLE_ORDERS);
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
return expectedFillableAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeAddress),
);
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
});
it('getKyberSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const sampler = new MockSamplerContract({
sampleSellsFromKyberNetwork: (_reserveOffset, takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return ['0x', '0x', expectedMakerFillAmounts];
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getKyberSellQuotes(
new BigNumber(0),
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
),
);
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
});
it('getLiquidityProviderSellQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const poolAddress = randomAddress();
const gasCost = 123;
const sampler = new MockSamplerContract({
sampleSellsFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
expect(providerAddress).to.eq(poolAddress);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [toBaseUnitAmount(1001)];
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
{
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
},
async () => undefined,
);
const [result] = await dexOrderSampler.executeAsync(
dexOrderSampler.getSellQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
),
);
expect(result).to.deep.equal([
[
{
source: 'LiquidityProvider',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
fillData: { poolAddress, gasCost },
},
],
]);
});
it('getLiquidityProviderBuyQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const poolAddress = randomAddress();
const gasCost = 321;
const sampler = new MockSamplerContract({
sampleBuysFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
expect(providerAddress).to.eq(poolAddress);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [toBaseUnitAmount(999)];
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
{
[poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
},
async () => undefined,
);
const [result] = await dexOrderSampler.executeAsync(
dexOrderSampler.getBuyQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
),
);
expect(result).to.deep.equal([
[
{
source: 'LiquidityProvider',
output: toBaseUnitAmount(999),
input: toBaseUnitAmount(1000),
fillData: { poolAddress, gasCost },
},
],
]);
});
it('getEth2DaiSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const sampler = new MockSamplerContract({
sampleSellsFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return expectedMakerFillAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getEth2DaiSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts),
);
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
});
it('getUniswapSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const sampler = new MockSamplerContract({
sampleSellsFromUniswap: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return expectedMakerFillAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getUniswapSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts),
);
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
});
it('getUniswapV2SellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const sampler = new MockSamplerContract({
sampleSellsFromUniswapV2: (_router, path, fillAmounts) => {
expect(path).to.deep.eq([expectedMakerToken, expectedTakerToken]);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return expectedMakerFillAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getUniswapV2SellQuotes(
[expectedMakerToken, expectedTakerToken],
expectedTakerFillAmounts,
),
);
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
});
it('getEth2DaiBuyQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const sampler = new MockSamplerContract({
sampleBuysFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
return expectedTakerFillAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getEth2DaiBuyQuotes(expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
);
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
});
it('getUniswapBuyQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
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 expectedTakerFillAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getUniswapBuyQuotes(expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
);
expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
});
interface RatesBySource {
[src: string]: BigNumber;
}
it('getSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
const ratesBySource: RatesBySource = {
[ERC20BridgeSource.Kyber]: getRandomFloat(0, 100),
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
[ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
};
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
let uniswapRouter: string;
const sampler = new MockSamplerContract({
sampleSellsFromUniswap: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
},
sampleSellsFromEth2Dai: (takerToken, makerToken, fillAmounts) => {
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
},
sampleSellsFromUniswapV2: (router, path, fillAmounts) => {
uniswapRouter = router;
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,
undefined,
undefined,
undefined,
tokenAdjacencyGraph,
undefined,
async () => undefined,
);
const [quotes] = await dexOrderSampler.executeAsync(
dexOrderSampler.getSellQuotes(
sources,
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
),
);
const expectedQuotes = sources.map(s =>
expectedTakerFillAmounts.map(a => ({
source: s,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
fillData:
s === ERC20BridgeSource.UniswapV2
? { router: uniswapRouter, tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
: {},
})),
);
const uniswapV2ETHQuotes = [
expectedTakerFillAmounts.map(a => ({
source: ERC20BridgeSource.UniswapV2,
input: a,
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
fillData: {
router: uniswapRouter,
tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
},
})),
];
// extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB)
const additionalSourceCount = 1;
expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount);
expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
});
it('getSellQuotes() fetches pools but not samples from Balancer', async () => {
// HACK
// We disabled the off-chain sampling due to incorrect data observed between
// on-chain and off-chain sampling
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
const pools: Pool[] = [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(
new MockSamplerContract({}),
undefined,
balancerPoolsCache,
undefined,
undefined,
undefined,
async () => undefined,
);
const quotes = await dexOrderSampler.getBalancerSellQuotesOffChainAsync(
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
);
expect(quotes).to.have.lengthOf(0);
});
it('getBuyQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
const ratesBySource: RatesBySource = {
[ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100),
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
[ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
};
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
let uniswapRouter: string;
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: (router, path, fillAmounts) => {
uniswapRouter = router;
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,
undefined,
undefined,
undefined,
tokenAdjacencyGraph,
undefined,
async () => undefined,
);
const [quotes] = await dexOrderSampler.executeAsync(
dexOrderSampler.getBuyQuotes(sources, expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
);
const expectedQuotes = sources.map(s =>
expectedMakerFillAmounts.map(a => ({
source: s,
input: a,
output: a.times(ratesBySource[s]).integerValue(),
fillData:
s === ERC20BridgeSource.UniswapV2
? { router: uniswapRouter, tokenAddressPath: [expectedTakerToken, expectedMakerToken] }
: {},
})),
);
const uniswapV2ETHQuotes = [
expectedMakerFillAmounts.map(a => ({
source: ERC20BridgeSource.UniswapV2,
input: a,
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
fillData: {
router: uniswapRouter,
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() uses samples from Balancer', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
const pools: Pool[] = [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(
new MockSamplerContract({}),
undefined,
balancerPoolsCache,
undefined,
undefined,
undefined,
async () => undefined,
);
const quotes = await dexOrderSampler.getBalancerBuyQuotesOffChainAsync(
expectedMakerToken,
expectedTakerToken,
expectedMakerFillAmounts,
);
expect(quotes).to.have.lengthOf(0);
});
});
describe('batched operations', () => {
it.skip('getLimitOrderFillableMakerAssetAmounts(), getLimitOrderFillableTakerAssetAmounts()', async () => {
const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
const expectedFillableMakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
const sampler = new MockSamplerContract({
getLimitOrderFillableMakerAssetAmounts: (orders, signatures) => {
expect(orders).to.deep.eq(SIMPLE_ORDERS);
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
return expectedFillableMakerAmounts;
},
getLimitOrderFillableTakerAssetAmounts: (orders, signatures) => {
expect(orders).to.deep.eq(SIMPLE_ORDERS);
expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
return expectedFillableTakerAmounts;
},
});
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
async () => undefined,
);
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeAddress),
dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeAddress),
);
expect(fillableMakerAmounts).to.deep.eq(expectedFillableMakerAmounts);
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
});
});
});
function generateBalancerPool(): Pool {
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