* add LibERC721Order.sol * Add ERC721 interface to vendor/ * Add ERC721OrdersFeature interface * Storage lib for ERC721 orders feature * Implement basic functionality for ERC721 orders (buy, sell, cancel, etc) * Add isValidERC721OrderSignature to interface * implement onERC721Received * Implement batchBuyERC721s * left/right orders -> sell/buy orders * Add missing @return comments * Implement matching functions * Use SafeMath where necessary * add rich errors for ERC721OrdersFeature * Add comments * Add presign support for ERC721 orders * Cancel using just the order nonce * Add IERC721OrdersFeature to IZeroEx * Add taker callback * Assembly optimizations in LibERC721Order * Add ERC721Orders TS class * create zero-ex/contracts/test/integration/ and tokens/ directories * TestMintableERC721Token * tmp * address feedback from original PR (#391) * address feedback from original PR * Update contracts/zero-ex/contracts/src/features/ERC721OrdersFeature.sol Co-authored-by: Kim Persson <kimpers@users.noreply.github.com> * address review feedback and improve order parameter naming * Add batchCancel function * Emit order fields in preSign * Fix tests Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: Kim Persson <kimpers@users.noreply.github.com> Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com> * Remove revertIfIncomplete from batchMatch * Sanity check maker address in preSign * ERC1155OrdersFeature contracts * Commence refactor, abstract base contract * ERC721OrdersFeature inherits from NFTOrders * Refactor ERC1155OrdersFeature to inherit from NFTOrders * Fix order hashing * Fix ERC721OrdersFeature tests * Typos * Remove maker address from preSigned mapping * disable dex sampler tests * Refactor TS tooling * Address PR feedback * Rearrange event fields to better align with struct fields * Update comments * update AbiEncoder.create params * Add ERC1155Order to protocol-utils * Add ERC1155OrdersFeeature tests * Bump package versions and regenerate contract wrappers * Add ERC165Feature * NFT orders: address audit findings (#417) * CVF-1: use pragma solidity ^0.6 instead of ^0.6.5 * CVF-11: fix inaccurate comment * CVF-16: Enable taker callbacks for batchBuyERC1155s * CVF-17: use internal call if revertIfIncomplete is true * CVF-21: avoid duplicate SLOAD * CVF-23: merge if statements * CVF-24: Reorder status checks to be consistent with ERC721OrdersFeature * CVF-25: Update unclear comment (canonical hash -> EIP-712 hash) * CVF-31: Document keys of orderState mapping * CVF-45: DRY up fees/properties hashing * CVF-47, CVF-50, CVF-57: calculate properties.length once; hash propertyStructHashArray in-place using assembly * CVF-56: More descriptive names for assembly variables * CVF-71: Update confusing comment about rounding in _payFees * CVF-72: Move ETH assertions outside of loop in _payFees * CVF-74: Move property validation loop to else branch * CVF-82: Update inaccurate comment * CVF-86: Enable taker callbacks for batchBuyERC721s * CVF-87: use internal call if revertIfIncomplete is true * CVF-89: Perform token mismatch checks before stateful operations * CVF-90, CVF-91: Defer ERC20 token mismatch check * CVF-93: Add inline comments for _payFees parameters in matchERC721Orders * CVF-94: Fix comment (Step 7 -> Step 5) * CVF-98: Use binary & operator instead of mod * CVF-99: Update unclear comment (canonical hash -> EIP-712 hash) * CVF-65, CVF-66, CVF-67: Copy params.ethAvailable into local variable; check that ethSpent does not exceed ethAvailable; remove ethAvailable < erc20FillAmount check * CVF-52, CVF-55, CVF-59: calculate fees.length once; hash feeStructHashArray in-place using assembly * CVF-14, CVF-32: OrderState struct; separate storage mapping for 1155 cancellations so orders can be cancelled by nonce * Update changelogs, IZeroEx artifact/wrapper Co-authored-by: Lawrence Forman <lawrence@0xproject.com> Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
568 lines
25 KiB
TypeScript
568 lines
25 KiB
TypeScript
import { ChainId, 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 } from '@0x/utils';
|
|
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 { MockSamplerContract } from './utils/mock_sampler_contract';
|
|
import { generatePseudoRandomSalt } from './utils/utils';
|
|
|
|
const CHAIN_ID = 1;
|
|
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
// tslint:disable: custom-no-magic-numbers
|
|
describe.skip('DexSampler tests', () => {
|
|
const MAKER_TOKEN = randomAddress();
|
|
const TAKER_TOKEN = randomAddress();
|
|
const chainId = ChainId.Mainnet;
|
|
|
|
const wethAddress = getContractAddressesForChainOrThrow(CHAIN_ID).etherToken;
|
|
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: EMPTY_BYTES32,
|
|
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.order, ['chainId', 'verifyingContract']));
|
|
|
|
describe('operations', () => {
|
|
it('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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeProxyAddress),
|
|
);
|
|
expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
|
|
});
|
|
|
|
it('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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeProxyAddress),
|
|
);
|
|
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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getKyberSellQuotes(
|
|
{ hintHandler: randomAddress(), networkProxy: randomAddress(), weth: randomAddress() },
|
|
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(
|
|
chainId,
|
|
sampler,
|
|
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(
|
|
chainId,
|
|
sampler,
|
|
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('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: (_router, 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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getUniswapSellQuotes(
|
|
randomAddress(),
|
|
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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getUniswapV2SellQuotes(
|
|
NULL_ADDRESS,
|
|
[expectedMakerToken, expectedTakerToken],
|
|
expectedTakerFillAmounts,
|
|
),
|
|
);
|
|
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
|
|
});
|
|
|
|
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: (_router, 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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getUniswapBuyQuotes(
|
|
randomAddress(),
|
|
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.Uniswap, ERC20BridgeSource.UniswapV2];
|
|
const ratesBySource: RatesBySource = {
|
|
[ERC20BridgeSource.Kyber]: getRandomFloat(0, 100),
|
|
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
|
|
[ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
|
|
};
|
|
const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
|
let uniswapRouter: string;
|
|
let uniswapV2Router: string;
|
|
const sampler = new MockSamplerContract({
|
|
sampleSellsFromUniswap: (router, takerToken, makerToken, fillAmounts) => {
|
|
uniswapRouter = router;
|
|
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());
|
|
},
|
|
sampleSellsFromUniswapV2: (router, path, fillAmounts) => {
|
|
uniswapV2Router = 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(
|
|
chainId,
|
|
sampler,
|
|
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: (() => {
|
|
if (s === ERC20BridgeSource.UniswapV2) {
|
|
return {
|
|
router: uniswapV2Router,
|
|
tokenAddressPath: [expectedTakerToken, expectedMakerToken],
|
|
};
|
|
}
|
|
// TODO jacob pass through
|
|
if (s === ERC20BridgeSource.Uniswap) {
|
|
return { router: uniswapRouter };
|
|
}
|
|
return {};
|
|
})(),
|
|
})),
|
|
);
|
|
const uniswapV2ETHQuotes = [
|
|
expectedTakerFillAmounts.map(a => ({
|
|
source: ERC20BridgeSource.UniswapV2,
|
|
input: a,
|
|
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
|
|
fillData: {
|
|
router: uniswapV2Router,
|
|
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('getBuyQuotes()', async () => {
|
|
const expectedTakerToken = randomAddress();
|
|
const expectedMakerToken = randomAddress();
|
|
const sources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
|
|
const ratesBySource: RatesBySource = {
|
|
[ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
|
|
[ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
|
|
};
|
|
const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
|
|
let uniswapRouter: string;
|
|
let uniswapV2Router: string;
|
|
const sampler = new MockSamplerContract({
|
|
sampleBuysFromUniswap: (router, takerToken, makerToken, fillAmounts) => {
|
|
uniswapRouter = router;
|
|
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());
|
|
},
|
|
sampleBuysFromUniswapV2: (router, path, fillAmounts) => {
|
|
uniswapV2Router = 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(
|
|
chainId,
|
|
sampler,
|
|
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: (() => {
|
|
if (s === ERC20BridgeSource.UniswapV2) {
|
|
return {
|
|
router: uniswapV2Router,
|
|
tokenAddressPath: [expectedTakerToken, expectedMakerToken],
|
|
};
|
|
}
|
|
if (s === ERC20BridgeSource.Uniswap) {
|
|
return { router: uniswapRouter };
|
|
}
|
|
return {};
|
|
})(),
|
|
})),
|
|
);
|
|
const uniswapV2ETHQuotes = [
|
|
expectedMakerFillAmounts.map(a => ({
|
|
source: ERC20BridgeSource.UniswapV2,
|
|
input: a,
|
|
output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
|
|
fillData: {
|
|
router: uniswapV2Router,
|
|
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));
|
|
});
|
|
describe('batched operations', () => {
|
|
it('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(
|
|
chainId,
|
|
sampler,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async () => undefined,
|
|
);
|
|
const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
|
|
dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeProxyAddress),
|
|
dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeProxyAddress),
|
|
);
|
|
expect(fillableMakerAmounts).to.deep.eq(expectedFillableMakerAmounts);
|
|
expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
// tslint:disable-next-line: max-file-line-count
|