Merge branch 'development' into feat/asset-swapper/use-quote-server
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import {
|
||||
constants,
|
||||
expect,
|
||||
@@ -24,6 +25,8 @@ describe('DexSampler tests', () => {
|
||||
const MAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(MAKER_TOKEN);
|
||||
const TAKER_ASSET_DATA = assetDataUtils.encodeERC20AssetData(TAKER_TOKEN);
|
||||
|
||||
const wethAddress = getContractAddressesForChainOrThrow(CHAIN_ID).etherToken;
|
||||
|
||||
describe('getSampleAmounts()', () => {
|
||||
const FILL_AMOUNT = getRandomInteger(1, 1e18);
|
||||
const NUM_SAMPLES = 16;
|
||||
@@ -160,6 +163,7 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
wethAddress,
|
||||
registry,
|
||||
),
|
||||
);
|
||||
@@ -193,6 +197,7 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
wethAddress,
|
||||
registry,
|
||||
),
|
||||
);
|
||||
@@ -207,6 +212,48 @@ describe('DexSampler tests', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('getMultiBridgeSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const multiBridge = randomAddress();
|
||||
|
||||
const sampler = new MockSamplerContract({
|
||||
sampleSellsFromMultiBridge: (
|
||||
multiBridgeAddress,
|
||||
takerToken,
|
||||
_intermediateToken,
|
||||
makerToken,
|
||||
_fillAmounts,
|
||||
) => {
|
||||
expect(multiBridgeAddress).to.eq(multiBridge);
|
||||
expect(takerToken).to.eq(expectedTakerToken);
|
||||
expect(makerToken).to.eq(expectedMakerToken);
|
||||
return [toBaseUnitAmount(1001)];
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [result] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getSellQuotes(
|
||||
[ERC20BridgeSource.MultiBridge],
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
[toBaseUnitAmount(1000)],
|
||||
randomAddress(),
|
||||
randomAddress(),
|
||||
multiBridge,
|
||||
),
|
||||
);
|
||||
expect(result).to.deep.equal([
|
||||
[
|
||||
{
|
||||
source: 'MultiBridge',
|
||||
output: toBaseUnitAmount(1001),
|
||||
input: toBaseUnitAmount(1000),
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('getEth2DaiSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
@@ -255,6 +302,28 @@ describe('DexSampler tests', () => {
|
||||
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: (path, fillAmounts) => {
|
||||
expect(path).to.deep.eq([expectedMakerToken, expectedTakerToken]);
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return expectedMakerFillAmounts;
|
||||
},
|
||||
});
|
||||
const dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getUniswapV2SellQuotes(
|
||||
[expectedMakerToken, expectedTakerToken],
|
||||
expectedTakerFillAmounts,
|
||||
),
|
||||
);
|
||||
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
||||
});
|
||||
|
||||
it('getEth2DaiBuyQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
@@ -310,11 +379,17 @@ describe('DexSampler tests', () => {
|
||||
it('getSellQuotes()', async () => {
|
||||
const expectedTakerToken = randomAddress();
|
||||
const expectedMakerToken = randomAddress();
|
||||
const sources = [ERC20BridgeSource.Kyber, ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap];
|
||||
const sources = [
|
||||
ERC20BridgeSource.Kyber,
|
||||
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);
|
||||
const sampler = new MockSamplerContract({
|
||||
@@ -336,6 +411,11 @@ describe('DexSampler tests', () => {
|
||||
expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
|
||||
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Eth2Dai]).integerValue());
|
||||
},
|
||||
sampleSellsFromUniswapV2: (path, fillAmounts) => {
|
||||
expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
|
||||
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(
|
||||
@@ -344,6 +424,43 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
wethAddress,
|
||||
),
|
||||
);
|
||||
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(),
|
||||
})),
|
||||
);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('getSellQuotes() includes ETH for Uniswap_V2_ETH', 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 dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getSellQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedTakerFillAmounts,
|
||||
wethAddress,
|
||||
),
|
||||
);
|
||||
expect(quotes).to.be.length(sources.length);
|
||||
@@ -364,6 +481,7 @@ describe('DexSampler tests', () => {
|
||||
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);
|
||||
const sampler = new MockSamplerContract({
|
||||
@@ -379,6 +497,11 @@ describe('DexSampler tests', () => {
|
||||
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, expectedMakerToken]);
|
||||
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(
|
||||
@@ -387,6 +510,56 @@ describe('DexSampler tests', () => {
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
wethAddress,
|
||||
),
|
||||
);
|
||||
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(),
|
||||
})),
|
||||
);
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
it('getBuyQuotes() includes ETH for Uniswap_V2_ETH', 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 dexOrderSampler = new DexOrderSampler(sampler);
|
||||
const [quotes] = await dexOrderSampler.executeAsync(
|
||||
DexOrderSampler.ops.getBuyQuotes(
|
||||
sources,
|
||||
expectedMakerToken,
|
||||
expectedTakerToken,
|
||||
expectedMakerFillAmounts,
|
||||
wethAddress,
|
||||
),
|
||||
);
|
||||
expect(quotes).to.be.length(sources.length);
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { constants as contractConstants, getRandomInteger, Numberish, randomAddress } from '@0x/contracts-test-utils';
|
||||
import {
|
||||
decodeFillQuoteTransformerData,
|
||||
decodePayTakerTransformerData,
|
||||
decodeWethTransformerData,
|
||||
ETH_TOKEN_ADDRESS,
|
||||
FillQuoteTransformerSide,
|
||||
} from '@0x/contracts-zero-ex';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import {
|
||||
ExchangeProxySwapQuoteConsumer,
|
||||
getTransformerAddress,
|
||||
} from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
|
||||
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
|
||||
import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const { NULL_ADDRESS } = constants;
|
||||
const { MAX_UINT256 } = contractConstants;
|
||||
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
|
||||
describe('ExchangeProxySwapQuoteConsumer', () => {
|
||||
const CHAIN_ID = 1;
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TRANSFORMER_DEPLOYER = randomAddress();
|
||||
const contractAddresses = {
|
||||
...getContractAddressesForChainOrThrow(CHAIN_ID),
|
||||
exchangeProxy: randomAddress(),
|
||||
exchangeProxyAllowanceTarget: randomAddress(),
|
||||
exchangeProxyTransformerDeployer: TRANSFORMER_DEPLOYER,
|
||||
transformers: {
|
||||
wethTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 1),
|
||||
payTakerTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 2),
|
||||
fillQuoteTransformer: getTransformerAddress(TRANSFORMER_DEPLOYER, 3),
|
||||
},
|
||||
};
|
||||
let consumer: ExchangeProxySwapQuoteConsumer;
|
||||
|
||||
before(async () => {
|
||||
const fakeProvider = {
|
||||
async sendAsync(): Promise<void> {
|
||||
/* noop */
|
||||
},
|
||||
};
|
||||
consumer = new ExchangeProxySwapQuoteConsumer(fakeProvider, contractAddresses, { chainId: CHAIN_ID });
|
||||
});
|
||||
|
||||
function getRandomAmount(maxAmount: Numberish = '1e18'): BigNumber {
|
||||
return getRandomInteger(1, maxAmount);
|
||||
}
|
||||
|
||||
function createAssetData(token?: string): string {
|
||||
return assetDataUtils.encodeERC20AssetData(token || randomAddress());
|
||||
}
|
||||
|
||||
function getRandomOrder(): OptimizedMarketOrder {
|
||||
return {
|
||||
fillableMakerAssetAmount: getRandomAmount(),
|
||||
fillableTakerFeeAmount: getRandomAmount(),
|
||||
fillableTakerAssetAmount: getRandomAmount(),
|
||||
fills: [],
|
||||
chainId: CHAIN_ID,
|
||||
exchangeAddress: contractAddresses.exchange,
|
||||
expirationTimeSeconds: getRandomInteger(1, 2e9),
|
||||
feeRecipientAddress: randomAddress(),
|
||||
makerAddress: randomAddress(),
|
||||
makerAssetAmount: getRandomAmount(),
|
||||
takerAssetAmount: getRandomAmount(),
|
||||
makerFee: getRandomAmount(),
|
||||
takerFee: getRandomAmount(),
|
||||
salt: getRandomAmount(2e9),
|
||||
signature: hexUtils.random(66),
|
||||
senderAddress: NULL_ADDRESS,
|
||||
takerAddress: NULL_ADDRESS,
|
||||
makerAssetData: createAssetData(MAKER_TOKEN),
|
||||
takerAssetData: createAssetData(TAKER_TOKEN),
|
||||
makerFeeAssetData: createAssetData(),
|
||||
takerFeeAssetData: createAssetData(),
|
||||
};
|
||||
}
|
||||
|
||||
function getRandomQuote(side: MarketOperation): MarketBuySwapQuote | MarketSellSwapQuote {
|
||||
return {
|
||||
gasPrice: getRandomInteger(1, 1e9),
|
||||
type: side,
|
||||
makerAssetData: createAssetData(MAKER_TOKEN),
|
||||
takerAssetData: createAssetData(TAKER_TOKEN),
|
||||
orders: [getRandomOrder()],
|
||||
bestCaseQuoteInfo: {
|
||||
feeTakerAssetAmount: getRandomAmount(),
|
||||
makerAssetAmount: getRandomAmount(),
|
||||
gas: Math.floor(Math.random() * 8e6),
|
||||
protocolFeeInWeiAmount: getRandomAmount(),
|
||||
takerAssetAmount: getRandomAmount(),
|
||||
totalTakerAssetAmount: getRandomAmount(),
|
||||
},
|
||||
worstCaseQuoteInfo: {
|
||||
feeTakerAssetAmount: getRandomAmount(),
|
||||
makerAssetAmount: getRandomAmount(),
|
||||
gas: Math.floor(Math.random() * 8e6),
|
||||
protocolFeeInWeiAmount: getRandomAmount(),
|
||||
takerAssetAmount: getRandomAmount(),
|
||||
totalTakerAssetAmount: getRandomAmount(),
|
||||
},
|
||||
...(side === MarketOperation.Buy
|
||||
? { makerAssetFillAmount: getRandomAmount() }
|
||||
: { takerAssetFillAmount: getRandomAmount() }),
|
||||
} as any;
|
||||
}
|
||||
|
||||
function getRandomSellQuote(): MarketSellSwapQuote {
|
||||
return getRandomQuote(MarketOperation.Sell) as MarketSellSwapQuote;
|
||||
}
|
||||
|
||||
function getRandomBuyQuote(): MarketBuySwapQuote {
|
||||
return getRandomQuote(MarketOperation.Buy) as MarketBuySwapQuote;
|
||||
}
|
||||
|
||||
type PlainOrder = Exclude<Order, ['chainId', 'exchangeAddress']>;
|
||||
|
||||
function cleanOrders(orders: OptimizedMarketOrder[]): PlainOrder[] {
|
||||
return orders.map(
|
||||
o =>
|
||||
_.omit(o, [
|
||||
'chainId',
|
||||
'exchangeAddress',
|
||||
'fillableMakerAssetAmount',
|
||||
'fillableTakerAssetAmount',
|
||||
'fillableTakerFeeAmount',
|
||||
'fills',
|
||||
'signature',
|
||||
]) as PlainOrder,
|
||||
);
|
||||
}
|
||||
|
||||
const callDataEncoder = AbiEncoder.createMethod('transformERC20', [
|
||||
{ type: 'address', name: 'inputToken' },
|
||||
{ type: 'address', name: 'outputToken' },
|
||||
{ type: 'uint256', name: 'inputTokenAmount' },
|
||||
{ type: 'uint256', name: 'minOutputTokenAmount' },
|
||||
{
|
||||
type: 'tuple[]',
|
||||
name: 'transformations',
|
||||
components: [{ type: 'uint32', name: 'deploymentNonce' }, { type: 'bytes', name: 'data' }],
|
||||
},
|
||||
]);
|
||||
|
||||
interface CallArgs {
|
||||
inputToken: string;
|
||||
outputToken: string;
|
||||
inputTokenAmount: BigNumber;
|
||||
minOutputTokenAmount: BigNumber;
|
||||
transformations: Array<{
|
||||
deploymentNonce: BigNumber;
|
||||
data: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
describe('getCalldataOrThrow()', () => {
|
||||
it('can produce a sell quote', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
|
||||
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
|
||||
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
|
||||
expect(callArgs.minOutputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||
expect(callArgs.transformations).to.be.length(2);
|
||||
expect(
|
||||
callArgs.transformations[0].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.fillQuoteTransformer,
|
||||
);
|
||||
expect(
|
||||
callArgs.transformations[1].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.payTakerTransformer,
|
||||
);
|
||||
const fillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[0].data);
|
||||
expect(fillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Sell);
|
||||
expect(fillQuoteTransformerData.fillAmount).to.bignumber.eq(quote.takerAssetFillAmount);
|
||||
expect(fillQuoteTransformerData.orders).to.deep.eq(cleanOrders(quote.orders));
|
||||
expect(fillQuoteTransformerData.signatures).to.deep.eq(quote.orders.map(o => o.signature));
|
||||
expect(fillQuoteTransformerData.sellToken).to.eq(TAKER_TOKEN);
|
||||
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
|
||||
it('can produce a buy quote', async () => {
|
||||
const quote = getRandomBuyQuote();
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
|
||||
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
|
||||
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
|
||||
expect(callArgs.minOutputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.makerAssetAmount);
|
||||
expect(callArgs.transformations).to.be.length(2);
|
||||
expect(
|
||||
callArgs.transformations[0].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.fillQuoteTransformer,
|
||||
);
|
||||
expect(
|
||||
callArgs.transformations[1].deploymentNonce.toNumber() ===
|
||||
consumer.transformerNonces.payTakerTransformer,
|
||||
);
|
||||
const fillQuoteTransformerData = decodeFillQuoteTransformerData(callArgs.transformations[0].data);
|
||||
expect(fillQuoteTransformerData.side).to.eq(FillQuoteTransformerSide.Buy);
|
||||
expect(fillQuoteTransformerData.fillAmount).to.bignumber.eq(quote.makerAssetFillAmount);
|
||||
expect(fillQuoteTransformerData.orders).to.deep.eq(cleanOrders(quote.orders));
|
||||
expect(fillQuoteTransformerData.signatures).to.deep.eq(quote.orders.map(o => o.signature));
|
||||
expect(fillQuoteTransformerData.sellToken).to.eq(TAKER_TOKEN);
|
||||
expect(fillQuoteTransformerData.buyToken).to.eq(MAKER_TOKEN);
|
||||
const payTakerTransformerData = decodePayTakerTransformerData(callArgs.transformations[1].data);
|
||||
expect(payTakerTransformerData.amounts).to.deep.eq([]);
|
||||
expect(payTakerTransformerData.tokens).to.deep.eq([TAKER_TOKEN, MAKER_TOKEN, ETH_TOKEN_ADDRESS]);
|
||||
});
|
||||
|
||||
it('ERC20 -> ERC20 does not have a WETH transformer', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
const nonces = callArgs.transformations.map(t => t.deploymentNonce);
|
||||
expect(nonces).to.not.include(consumer.transformerNonces.wethTransformer);
|
||||
});
|
||||
|
||||
it('ETH -> ERC20 has a WETH transformer before the fill', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||
extensionContractOpts: { isFromETH: true },
|
||||
});
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
expect(callArgs.transformations[0].deploymentNonce.toNumber()).to.eq(
|
||||
consumer.transformerNonces.wethTransformer,
|
||||
);
|
||||
const wethTransformerData = decodeWethTransformerData(callArgs.transformations[0].data);
|
||||
expect(wethTransformerData.amount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
|
||||
expect(wethTransformerData.token).to.eq(ETH_TOKEN_ADDRESS);
|
||||
});
|
||||
|
||||
it('ERC20 -> ETH has a WETH transformer after the fill', async () => {
|
||||
const quote = getRandomSellQuote();
|
||||
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
|
||||
extensionContractOpts: { isToETH: true },
|
||||
});
|
||||
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
|
||||
expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq(
|
||||
consumer.transformerNonces.wethTransformer,
|
||||
);
|
||||
const wethTransformerData = decodeWethTransformerData(callArgs.transformations[1].data);
|
||||
expect(wethTransformerData.amount).to.bignumber.eq(MAX_UINT256);
|
||||
expect(wethTransformerData.token).to.eq(contractAddresses.etherToken);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -30,10 +30,11 @@ import { DexSample, ERC20BridgeSource, NativeFillData } from '../src/utils/marke
|
||||
// tslint:disable: custom-no-magic-numbers
|
||||
describe('MarketOperationUtils tests', () => {
|
||||
const CHAIN_ID = 1;
|
||||
const contractAddresses = getContractAddressesForChainOrThrow(CHAIN_ID);
|
||||
const contractAddresses = { ...getContractAddressesForChainOrThrow(CHAIN_ID), multiBridge: NULL_ADDRESS };
|
||||
const ETH2DAI_BRIDGE_ADDRESS = contractAddresses.eth2DaiBridge;
|
||||
const KYBER_BRIDGE_ADDRESS = contractAddresses.kyberBridge;
|
||||
const UNISWAP_BRIDGE_ADDRESS = contractAddresses.uniswapBridge;
|
||||
const UNISWAP_V2_BRIDGE_ADDRESS = contractAddresses.uniswapV2Bridge;
|
||||
const CURVE_BRIDGE_ADDRESS = contractAddresses.curveBridge;
|
||||
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
@@ -89,6 +90,8 @@ describe('MarketOperationUtils tests', () => {
|
||||
return ERC20BridgeSource.Eth2Dai;
|
||||
case UNISWAP_BRIDGE_ADDRESS.toLowerCase():
|
||||
return ERC20BridgeSource.Uniswap;
|
||||
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,
|
||||
@@ -151,11 +154,18 @@ describe('MarketOperationUtils tests', () => {
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
liquidityProviderAddress?: string,
|
||||
) => DexSample[][];
|
||||
|
||||
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return (
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
) => {
|
||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s]));
|
||||
};
|
||||
}
|
||||
@@ -173,17 +183,31 @@ describe('MarketOperationUtils tests', () => {
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
liquidityProviderAddress?: string,
|
||||
) => {
|
||||
liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress;
|
||||
liquidityPoolParams.sources = sources;
|
||||
return tradeOperation(rates)(sources, makerToken, takerToken, fillAmounts, liquidityProviderAddress);
|
||||
return tradeOperation(rates)(
|
||||
sources,
|
||||
makerToken,
|
||||
takerToken,
|
||||
fillAmounts,
|
||||
wethAddress,
|
||||
liquidityProviderAddress,
|
||||
);
|
||||
};
|
||||
return [liquidityPoolParams, fn];
|
||||
}
|
||||
|
||||
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
|
||||
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return (
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
) => {
|
||||
return sources.map(s => createSamplesFromRates(s, fillAmounts, rates[s].map(r => new BigNumber(1).div(r))));
|
||||
};
|
||||
}
|
||||
@@ -193,6 +217,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
liquidityProviderAddress?: string,
|
||||
) => BigNumber;
|
||||
|
||||
@@ -203,7 +228,13 @@ describe('MarketOperationUtils tests', () => {
|
||||
) => string;
|
||||
|
||||
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
|
||||
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
|
||||
return (
|
||||
sources: ERC20BridgeSource[],
|
||||
makerToken: string,
|
||||
takerToken: string,
|
||||
fillAmounts: BigNumber[],
|
||||
wethAddress: string,
|
||||
) => {
|
||||
return new BigNumber(rate);
|
||||
};
|
||||
}
|
||||
@@ -228,7 +259,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const fn = (registryAddress: string, takerToken: string, makerToken: string): string => {
|
||||
callArgs.makerToken = makerToken;
|
||||
callArgs.takerToken = takerToken;
|
||||
callArgs.registryAddress = registryAddress;
|
||||
if (registryAddress !== constants.NULL_ADDRESS) {
|
||||
callArgs.registryAddress = registryAddress;
|
||||
}
|
||||
return liquidityProviderAddress;
|
||||
};
|
||||
return [callArgs, fn];
|
||||
@@ -255,12 +288,15 @@ 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.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
|
||||
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
|
||||
};
|
||||
|
||||
const DEFAULT_OPS = {
|
||||
@@ -310,7 +346,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[],
|
||||
excludedSources: [
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2Eth,
|
||||
...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]),
|
||||
],
|
||||
allowFallback: false,
|
||||
shouldBatchBridgeOrders: false,
|
||||
};
|
||||
@@ -323,9 +363,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const numSamples = _.random(1, NUM_SAMPLES);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -338,9 +378,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -379,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) => {
|
||||
getSellQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getSellQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -445,7 +485,7 @@ 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.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [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({
|
||||
@@ -459,7 +499,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
];
|
||||
@@ -474,7 +514,7 @@ 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.Uniswap]: [0.96, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.UniswapV2]: [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],
|
||||
};
|
||||
@@ -495,7 +535,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Native,
|
||||
];
|
||||
@@ -511,7 +551,7 @@ 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.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||
[ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4)
|
||||
@@ -531,7 +571,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
];
|
||||
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
|
||||
});
|
||||
@@ -540,7 +580,7 @@ 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.Uniswap]: [0.96, 0.2, 0.1, 0.1],
|
||||
[ERC20BridgeSource.UniswapV2]: [0.96, 0.2, 0.1, 0.1],
|
||||
[ERC20BridgeSource.Native]: [0.95, 0.2, 0.2, 0.1],
|
||||
};
|
||||
replaceSamplerOps({
|
||||
@@ -555,7 +595,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.Native,
|
||||
];
|
||||
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
|
||||
@@ -564,7 +604,7 @@ 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.Uniswap] = [0.6, 0.05, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [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({
|
||||
@@ -580,7 +620,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
];
|
||||
const secondSources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber];
|
||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||
@@ -590,7 +630,7 @@ 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.Uniswap] = [1, 1, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [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({
|
||||
@@ -602,7 +642,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.Uniswap];
|
||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2];
|
||||
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());
|
||||
@@ -666,7 +706,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
|
||||
it('batches contiguous bridge sources', async () => {
|
||||
const rates: RatesBySource = {};
|
||||
rates[ERC20BridgeSource.Uniswap] = [1, 0.01, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [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];
|
||||
@@ -689,7 +729,7 @@ 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.Uniswap],
|
||||
[ERC20BridgeSource.UniswapV2],
|
||||
[ERC20BridgeSource.Native],
|
||||
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.CurveUsdcDai],
|
||||
]);
|
||||
@@ -707,7 +747,12 @@ describe('MarketOperationUtils tests', () => {
|
||||
sampleDistributionBase: 1,
|
||||
bridgeSlippage: 0,
|
||||
maxFallbackSlippage: 100,
|
||||
excludedSources: [...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]), ERC20BridgeSource.Kyber],
|
||||
excludedSources: [
|
||||
...(Object.keys(DEFAULT_CURVE_OPTS) as ERC20BridgeSource[]),
|
||||
ERC20BridgeSource.Kyber,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2Eth,
|
||||
],
|
||||
allowFallback: false,
|
||||
shouldBatchBridgeOrders: false,
|
||||
};
|
||||
@@ -720,9 +765,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const numSamples = _.random(1, 16);
|
||||
let actualNumSamples = 0;
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
actualNumSamples = amounts.length;
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -735,9 +780,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
it('polls all DEXes if `excludedSources` is empty', async () => {
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -776,9 +821,9 @@ describe('MarketOperationUtils tests', () => {
|
||||
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
|
||||
let sourcesPolled: ERC20BridgeSource[] = [];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts) => {
|
||||
getBuyQuotes: (sources, makerToken, takerToken, amounts, wethAddress) => {
|
||||
sourcesPolled = sources.slice();
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts);
|
||||
return DEFAULT_OPS.getBuyQuotes(sources, makerToken, takerToken, amounts, wethAddress);
|
||||
},
|
||||
});
|
||||
await marketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
|
||||
@@ -842,7 +887,7 @@ 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.Uniswap] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [0.5, 0.05, 0.05, 0.05];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.6, 0.05, 0.05, 0.05];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
@@ -855,7 +900,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
];
|
||||
@@ -870,7 +915,7 @@ 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.Uniswap]: [0.96, 0.1, 0.1, 0.1],
|
||||
[ERC20BridgeSource.UniswapV2]: [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],
|
||||
};
|
||||
@@ -890,7 +935,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
);
|
||||
const orderSources = improvedOrders.map(o => o.fills[0].source);
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
@@ -905,11 +950,11 @@ describe('MarketOperationUtils tests', () => {
|
||||
const rates: RatesBySource = {
|
||||
[ERC20BridgeSource.Native]: [0.95, 0.1, 0.1, 0.1],
|
||||
// Effectively [0.8, ~0.5, ~0, ~0]
|
||||
[ERC20BridgeSource.Uniswap]: [1, 0.7, 0.2, 0.2],
|
||||
[ERC20BridgeSource.UniswapV2]: [1, 0.7, 0.2, 0.2],
|
||||
[ERC20BridgeSource.Eth2Dai]: [0.92, 0.1, 0.1, 0.1],
|
||||
};
|
||||
const feeSchedule = {
|
||||
[ERC20BridgeSource.Uniswap]: FILL_AMOUNT.div(4)
|
||||
[ERC20BridgeSource.UniswapV2]: FILL_AMOUNT.div(4)
|
||||
.times(uniswapFeeRate)
|
||||
.dividedToIntegerBy(ETH_TO_TAKER_RATE),
|
||||
};
|
||||
@@ -926,7 +971,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
const expectedSources = [
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Eth2Dai,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
];
|
||||
expect(orderSources.sort()).to.deep.eq(expectedSources.sort());
|
||||
});
|
||||
@@ -934,7 +979,7 @@ 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.Uniswap] = [0.6, 0.05, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [0.6, 0.05, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.4, 0.3, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
@@ -949,7 +994,7 @@ describe('MarketOperationUtils tests', () => {
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Native,
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
];
|
||||
const secondSources = [ERC20BridgeSource.Eth2Dai];
|
||||
expect(orderSources.slice(0, firstSources.length).sort()).to.deep.eq(firstSources.sort());
|
||||
@@ -959,7 +1004,7 @@ 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.Uniswap] = [1, 1, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [1, 1, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.Eth2Dai] = [0.49, 0.49, 0.49, 0.49];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
@@ -970,7 +1015,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.Uniswap];
|
||||
const firstSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Native, ERC20BridgeSource.UniswapV2];
|
||||
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());
|
||||
@@ -980,7 +1025,7 @@ 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.Uniswap] = [0.48, 0.47, 0.01, 0.01];
|
||||
rates[ERC20BridgeSource.UniswapV2] = [0.48, 0.47, 0.01, 0.01];
|
||||
replaceSamplerOps({
|
||||
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
|
||||
});
|
||||
@@ -997,7 +1042,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.Uniswap],
|
||||
[ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.UniswapV2],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,12 +21,21 @@ export type SampleBuysHandler = (
|
||||
makerToken: string,
|
||||
makerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleSellsLPHandler = (
|
||||
registryAddress: string,
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
|
||||
export type SampleSellsMBHandler = (
|
||||
multiBridgeAddress: string,
|
||||
takerToken: string,
|
||||
intermediateToken: string,
|
||||
makerToken: string,
|
||||
takerTokenAmounts: BigNumber[],
|
||||
) => SampleResults;
|
||||
|
||||
const DUMMY_PROVIDER = {
|
||||
sendAsync: (...args: any[]): any => {
|
||||
@@ -39,10 +48,13 @@ interface Handlers {
|
||||
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
|
||||
sampleSellsFromKyberNetwork: SampleSellsHandler;
|
||||
sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler;
|
||||
sampleSellsFromMultiBridge: SampleSellsMBHandler;
|
||||
sampleSellsFromEth2Dai: SampleSellsHandler;
|
||||
sampleSellsFromUniswap: SampleSellsHandler;
|
||||
sampleSellsFromUniswapV2: SampleSellsMultihopHandler;
|
||||
sampleBuysFromEth2Dai: SampleBuysHandler;
|
||||
sampleBuysFromUniswap: SampleBuysHandler;
|
||||
sampleBuysFromUniswapV2: SampleBuysMultihopHandler;
|
||||
sampleBuysFromLiquidityProviderRegistry: SampleSellsLPHandler;
|
||||
}
|
||||
|
||||
@@ -127,6 +139,18 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromUniswapV2(
|
||||
path: string[],
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromUniswapV2,
|
||||
this._handlers.sampleSellsFromUniswapV2,
|
||||
path,
|
||||
takerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromLiquidityProviderRegistry(
|
||||
registryAddress: string,
|
||||
takerToken: string,
|
||||
@@ -143,6 +167,24 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
|
||||
);
|
||||
}
|
||||
|
||||
public sampleSellsFromMultiBridge(
|
||||
multiBridgeAddress: string,
|
||||
takerToken: string,
|
||||
intermediateToken: string,
|
||||
makerToken: string,
|
||||
takerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleSellsFromMultiBridge,
|
||||
this._handlers.sampleSellsFromMultiBridge,
|
||||
multiBridgeAddress,
|
||||
takerToken,
|
||||
intermediateToken,
|
||||
makerToken,
|
||||
takerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
public sampleBuysFromEth2Dai(
|
||||
takerToken: string,
|
||||
makerToken: string,
|
||||
@@ -171,6 +213,18 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
|
||||
);
|
||||
}
|
||||
|
||||
public sampleBuysFromUniswapV2(
|
||||
path: string[],
|
||||
makerAssetAmounts: BigNumber[],
|
||||
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
|
||||
return this._wrapCall(
|
||||
super.sampleBuysFromUniswapV2,
|
||||
this._handlers.sampleBuysFromUniswapV2,
|
||||
path,
|
||||
makerAssetAmounts,
|
||||
);
|
||||
}
|
||||
|
||||
private _callEncodedFunction(callData: string): string {
|
||||
// tslint:disable-next-line: custom-no-magic-numbers
|
||||
const selector = hexUtils.slice(callData, 0, 4);
|
||||
|
||||
Reference in New Issue
Block a user