import { blockchainTests, constants, expect, getRandomInteger, getRandomPortion, hexConcat, hexHash, hexLeftPad, hexRandom, randomAddress, toHex, } from '@0x/contracts-test-utils'; import { Order, OrderInfo } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { TestERC20BridgeSamplerContract } from './wrappers'; blockchainTests('erc20-bridge-sampler', env => { let testContract: TestERC20BridgeSamplerContract; let allSources: { [name: string]: string }; const RATE_DENOMINATOR = constants.ONE_ETHER; const MIN_RATE = new BigNumber('0.01'); const MAX_RATE = new BigNumber('100'); const MIN_DECIMALS = 4; const MAX_DECIMALS = 20; const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; const KYBER_SALT = '0x0ff3ca9d46195c39f9a12afb74207b4970349fb3cfb1e459bbf170298d326bc7'; const ETH2DAI_SALT = '0xb713b61bb9bb2958a0f5d1534b21e94fc68c4c0c034b0902ed844f2f6cd1b4f7'; const UNISWAP_BASE_SALT = '0x1d6a6a0506b0b4a554b907a4c29d9f4674e461989d9c1921feb17b26716385ab'; const ERC20_PROXY_ID = '0xf47261b0'; const INVALID_ASSET_PROXY_ASSET_DATA = hexConcat('0xf47261b1', hexLeftPad(randomAddress())); const INVALID_ASSET_DATA = hexRandom(37); const SELL_SOURCES = ['Eth2Dai', 'Kyber', 'Uniswap']; const BUY_SOURCES = ['Eth2Dai', 'Uniswap']; const EMPTY_ORDERS_ERROR = 'EMPTY_ORDERS'; const UNSUPPORTED_ASSET_PROXY_ERROR = 'UNSUPPORTED_ASSET_PROXY'; const INVALID_ASSET_DATA_ERROR = 'INVALID_ASSET_DATA'; const UNSUPPORTED_UNISWAP_EXCHANGE_ERROR = 'UNSUPPORTED_UNISWAP_EXCHANGE'; const UNSUPPORTED_SOURCE_ERROR = 'UNSUPPORTED_SOURCE'; const INVALID_TOKEN_PAIR_ERROR = 'INVALID_TOKEN_PAIR'; before(async () => { testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( artifacts.TestERC20BridgeSampler, env.provider, env.txDefaults, {}, ); allSources = _.zipObject( ['Uniswap', 'Eth2Dai', 'Kyber'], [ await testContract.uniswap().callAsync(), await testContract.eth2Dai().callAsync(), await testContract.kyber().callAsync(), ], ); }); function getPackedHash(...args: string[]): string { return hexHash(hexConcat(...args.map(a => toHex(a)))); } function getUniswapExchangeSalt(tokenAddress: string): string { return getPackedHash(UNISWAP_BASE_SALT, tokenAddress); } function getDeterministicRate(salt: string, sellToken: string, buyToken: string): BigNumber { const hash = getPackedHash(salt, sellToken, buyToken); const _minRate = RATE_DENOMINATOR.times(MIN_RATE); const _maxRate = RATE_DENOMINATOR.times(MAX_RATE); return new BigNumber(hash) .mod(_maxRate.minus(_minRate)) .plus(_minRate) .div(RATE_DENOMINATOR); } function getDeterministicTokenDecimals(token: string): number { if (token === WETH_ADDRESS) { return 18; } // HACK(dorothy-zbornak): Linter will complain about the addition not being // between two numbers, even though they are. // tslint:disable-next-line restrict-plus-operands return new BigNumber(getPackedHash(token)).mod(MAX_DECIMALS - MIN_DECIMALS).toNumber() + MIN_DECIMALS; } function getDeterministicSellQuote( salt: string, sellToken: string, buyToken: string, sellAmount: BigNumber, ): BigNumber { const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); const rate = getDeterministicRate(salt, sellToken, buyToken); return sellAmount .times(rate) .times(buyBase) .dividedToIntegerBy(sellBase); } function getDeterministicBuyQuote( salt: string, sellToken: string, buyToken: string, buyAmount: BigNumber, ): BigNumber { const sellBase = new BigNumber(10).pow(getDeterministicTokenDecimals(sellToken)); const buyBase = new BigNumber(10).pow(getDeterministicTokenDecimals(buyToken)); const rate = getDeterministicRate(salt, sellToken, buyToken); return buyAmount .times(sellBase) .dividedToIntegerBy(rate) .dividedToIntegerBy(buyBase); } function areAddressesEqual(a: string, b: string): boolean { return a.toLowerCase() === b.toLowerCase(); } function getDeterministicUniswapSellQuote(sellToken: string, buyToken: string, sellAmount: BigNumber): BigNumber { if (areAddressesEqual(buyToken, WETH_ADDRESS)) { return getDeterministicSellQuote(getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount); } if (areAddressesEqual(sellToken, WETH_ADDRESS)) { return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, sellAmount); } const ethBought = getDeterministicSellQuote( getUniswapExchangeSalt(sellToken), sellToken, WETH_ADDRESS, sellAmount, ); return getDeterministicSellQuote(getUniswapExchangeSalt(buyToken), buyToken, WETH_ADDRESS, ethBought); } function getDeterministicUniswapBuyQuote(sellToken: string, buyToken: string, buyAmount: BigNumber): BigNumber { if (areAddressesEqual(buyToken, WETH_ADDRESS)) { return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, buyAmount); } if (areAddressesEqual(sellToken, WETH_ADDRESS)) { return getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); } const ethSold = getDeterministicBuyQuote(getUniswapExchangeSalt(buyToken), WETH_ADDRESS, buyToken, buyAmount); return getDeterministicBuyQuote(getUniswapExchangeSalt(sellToken), WETH_ADDRESS, sellToken, ethSold); } function getDeterministicSellQuotes( sellToken: string, buyToken: string, sources: string[], sampleAmounts: BigNumber[], ): BigNumber[][] { const quotes: BigNumber[][] = []; for (const source of sources) { const sampleOutputs = []; for (const amount of sampleAmounts) { if (source === 'Kyber' || source === 'Eth2Dai') { sampleOutputs.push( getDeterministicSellQuote( source === 'Kyber' ? KYBER_SALT : ETH2DAI_SALT, sellToken, buyToken, amount, ), ); } else if (source === 'Uniswap') { sampleOutputs.push(getDeterministicUniswapSellQuote(sellToken, buyToken, amount)); } } quotes.push(sampleOutputs); } return quotes; } function getDeterministicBuyQuotes( sellToken: string, buyToken: string, sources: string[], sampleAmounts: BigNumber[], ): BigNumber[][] { const quotes: BigNumber[][] = []; for (const source of sources) { const sampleOutputs = []; for (const amount of sampleAmounts) { if (source === 'Eth2Dai') { sampleOutputs.push(getDeterministicBuyQuote(ETH2DAI_SALT, sellToken, buyToken, amount)); } else if (source === 'Uniswap') { sampleOutputs.push(getDeterministicUniswapBuyQuote(sellToken, buyToken, amount)); } } quotes.push(sampleOutputs); } return quotes; } function getDeterministicOrderInfo(order: Order): OrderInfo { const hash = getPackedHash(hexLeftPad(order.salt, 32)); return { orderHash: hash, orderStatus: new BigNumber(hash).mod(255).toNumber(), orderTakerAssetFilledAmount: new BigNumber(hash).mod(order.takerAssetAmount), }; } function getERC20AssetData(tokenAddress: string): string { return hexConcat(ERC20_PROXY_ID, hexLeftPad(tokenAddress)); } function getSampleAmounts(tokenAddress: string, count?: number): BigNumber[] { const tokenDecimals = getDeterministicTokenDecimals(tokenAddress); const _upperLimit = getRandomPortion(getRandomInteger(1, 1000).times(10 ** tokenDecimals)); const _count = count || _.random(1, 16); const d = _upperLimit.div(_count); return _.times(_count, i => d.times((i + 1) / _count).integerValue()); } function createOrder(makerToken: string, takerToken: string): Order { return { chainId: 1337, exchangeAddress: randomAddress(), makerAddress: randomAddress(), takerAddress: randomAddress(), senderAddress: randomAddress(), feeRecipientAddress: randomAddress(), makerAssetAmount: getRandomInteger(1, 1e18), takerAssetAmount: getRandomInteger(1, 1e18), makerFee: getRandomInteger(1, 1e18), takerFee: getRandomInteger(1, 1e18), makerAssetData: getERC20AssetData(makerToken), takerAssetData: getERC20AssetData(takerToken), makerFeeAssetData: getERC20AssetData(randomAddress()), takerFeeAssetData: getERC20AssetData(randomAddress()), salt: new BigNumber(hexRandom()), expirationTimeSeconds: getRandomInteger(0, 2 ** 32), }; } function createOrders(makerToken: string, takerToken: string, count?: number): Order[] { return _.times(count || _.random(1, 16), () => createOrder(makerToken, takerToken)); } describe('queryOrders()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); it('returns the results of `getOrderInfo()` for each order', async () => { const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const expected = orders.map(getDeterministicOrderInfo); const actual = await testContract.queryOrders(orders).callAsync(); expect(actual).to.deep.eq(expected); }); it('returns empty for no orders', async () => { const actual = await testContract.queryOrders([]).callAsync(); expect(actual).to.deep.eq([]); }); }); describe('queryOrdersAndSampleSells()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('returns the results of `getOrderInfo()` for each order', async () => { const takerTokenAmounts = getSampleAmounts(TAKER_TOKEN); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const expectedOrderInfos = orders.map(getDeterministicOrderInfo); const [orderInfos] = await testContract .queryOrdersAndSampleSells(orders, SELL_SOURCES.map(n => allSources[n]), takerTokenAmounts) .callAsync(); expect(orderInfos).to.deep.eq(expectedOrderInfos); }); it('can return quotes for all sources', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); const [, quotes] = await testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN), SELL_SOURCES.map(n => allSources[n]), sampleAmounts, ) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws if no orders are passed in', async () => { const tx = testContract .queryOrdersAndSampleSells([], SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN)) .callAsync(); return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); }); it('throws with an unsupported source', async () => { const tx = testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN), [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); it('throws with non-ERC20 maker asset data', async () => { const tx = testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); }); it('throws with non-ERC20 taker asset data', async () => { const tx = testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); }); it('throws with invalid maker asset data', async () => { const tx = testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, makerAssetData: INVALID_ASSET_DATA, })), SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); }); it('throws with invalid taker asset data', async () => { const tx = testContract .queryOrdersAndSampleSells( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, takerAssetData: INVALID_ASSET_DATA, })), SELL_SOURCES.map(n => allSources[n]), getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); }); }); describe('queryOrdersAndSampleBuys()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('returns the results of `getOrderInfo()` for each order', async () => { const takerTokenAmounts = getSampleAmounts(MAKER_TOKEN); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const expectedOrderInfos = orders.map(getDeterministicOrderInfo); const [orderInfos] = await testContract .queryOrdersAndSampleBuys(orders, BUY_SOURCES.map(n => allSources[n]), takerTokenAmounts) .callAsync(); expect(orderInfos).to.deep.eq(expectedOrderInfos); }); it('can return quotes for all sources', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); const [, quotes] = await testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN), BUY_SOURCES.map(n => allSources[n]), sampleAmounts, ) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws if no orders are passed in', async () => { const tx = testContract .queryOrdersAndSampleBuys([], BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN)) .callAsync(); return expect(tx).to.revertWith(EMPTY_ORDERS_ERROR); }); it('throws with an unsupported source', async () => { const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN), [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); it('throws if kyber is passed in as a source', async () => { const sources = [...BUY_SOURCES, 'Kyber']; const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN), sources.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); it('throws with non-ERC20 maker asset data', async () => { const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, makerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); }); it('throws with non-ERC20 taker asset data', async () => { const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, takerAssetData: INVALID_ASSET_PROXY_ASSET_DATA, })), BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_ASSET_PROXY_ERROR); }); it('throws with invalid maker asset data', async () => { const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, makerAssetData: INVALID_ASSET_DATA, })), BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); }); it('throws with invalid taker asset data', async () => { const tx = testContract .queryOrdersAndSampleBuys( createOrders(MAKER_TOKEN, TAKER_TOKEN).map(o => ({ ...o, takerAssetData: INVALID_ASSET_DATA, })), BUY_SOURCES.map(n => allSources[n]), getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(INVALID_ASSET_DATA_ERROR); }); }); describe('sampleSells()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('returns empty quotes with no sample amounts', async () => { const emptyQuotes = _.times(SELL_SOURCES.length, () => []); const quotes = await testContract .sampleSells(SELL_SOURCES.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, []) .callAsync(); expect(quotes).to.deep.eq(emptyQuotes); }); it('can return quotes for all sources', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, SELL_SOURCES, sampleAmounts); const quotes = await testContract .sampleSells(SELL_SOURCES.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can return quotes for some sources', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const sources = _.sampleSize(SELL_SOURCES, 1); const expectedQuotes = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts); const quotes = await testContract .sampleSells(sources.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws with an unsupported source', async () => { const tx = testContract .sampleSells( [...SELL_SOURCES.map(n => allSources[n]), randomAddress()], TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(TAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); }); describe('sampleBuys()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('returns empty quotes with no sample amounts', async () => { const emptyQuotes = _.times(BUY_SOURCES.length, () => []); const quotes = await testContract .sampleBuys(BUY_SOURCES.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, []) .callAsync(); expect(quotes).to.deep.eq(emptyQuotes); }); it('can return quotes for all sources', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, BUY_SOURCES, sampleAmounts); const quotes = await testContract .sampleBuys(BUY_SOURCES.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can return quotes for some sources', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const sources = _.sampleSize(BUY_SOURCES, 1); const expectedQuotes = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, sources, sampleAmounts); const quotes = await testContract .sampleBuys(sources.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws with an unsupported source', async () => { const tx = testContract .sampleBuys( [...BUY_SOURCES.map(n => allSources[n]), randomAddress()], TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(MAKER_TOKEN), ) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); it('throws if kyber is passed in as a source', async () => { const sources = [...BUY_SOURCES, 'Kyber']; const tx = testContract .sampleBuys(sources.map(n => allSources[n]), TAKER_TOKEN, MAKER_TOKEN, getSampleAmounts(MAKER_TOKEN)) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_SOURCE_ERROR); }); }); describe('sampleSellsFromKyberNetwork()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('throws if tokens are the same', async () => { const tx = testContract.sampleSellsFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); it('can return many quotes', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); const quotes = await testContract .sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); const quotes = await testContract .sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); const quotes = await testContract .sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); }); describe('sampleSellsFromEth2Dai()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('throws if tokens are the same', async () => { const tx = testContract.sampleSellsFromEth2Dai(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); it('can return many quotes', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleSellsFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleSellsFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleSellsFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); }); describe('sampleBuysFromEth2Dai()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('throws if tokens are the same', async () => { const tx = testContract.sampleBuysFromEth2Dai(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { const quotes = await testContract.sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); it('can return many quotes', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleBuysFromEth2Dai(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleBuysFromEth2Dai(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Eth2Dai'], sampleAmounts); const quotes = await testContract .sampleBuysFromEth2Dai(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); }); describe('sampleSellsFromUniswap()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('throws if tokens are the same', async () => { const tx = testContract.sampleSellsFromUniswap(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { const quotes = await testContract.sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); it('can return many quotes', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleSellsFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleSellsFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleSellsFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws if no exchange exists for the maker token', async () => { const nonExistantToken = randomAddress(); const tx = testContract .sampleSellsFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); it('throws if no exchange exists for the taker token', async () => { const nonExistantToken = randomAddress(); const tx = testContract .sampleSellsFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); }); describe('sampleBuysFromUniswap()', () => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); before(async () => { await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); }); it('throws if tokens are the same', async () => { const tx = testContract.sampleBuysFromUniswap(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { const quotes = await testContract.sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); expect(quotes).to.deep.eq([]); }); it('can return many quotes', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleBuysFromUniswap(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleBuysFromUniswap(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(MAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Uniswap'], sampleAmounts); const quotes = await testContract .sampleBuysFromUniswap(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); it('throws if no exchange exists for the maker token', async () => { const nonExistantToken = randomAddress(); const tx = testContract .sampleBuysFromUniswap(TAKER_TOKEN, nonExistantToken, getSampleAmounts(TAKER_TOKEN)) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); it('throws if no exchange exists for the taker token', async () => { const nonExistantToken = randomAddress(); const tx = testContract .sampleBuysFromUniswap(nonExistantToken, MAKER_TOKEN, getSampleAmounts(nonExistantToken)) .callAsync(); return expect(tx).to.revertWith(UNSUPPORTED_UNISWAP_EXCHANGE_ERROR); }); }); });