rewritten test cases for SwapQuoter

fixed testing for swap quoter + setup for consumer testing

added framework for testing consumers

added testing and updated some types
This commit is contained in:
David Sun 2019-06-19 17:08:12 -07:00
parent 222f7e6fd4
commit e1ab9aa690
16 changed files with 430 additions and 162 deletions

View File

@ -17,7 +17,7 @@
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"test:circleci": "yarn test:coverage", "test:circleci": "yarn test:coverage",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js lib/test/global_hooks.js --timeout 10000 --bail --exit",
"clean": "shx rm -rf lib test_temp", "clean": "shx rm -rf lib test_temp",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
}, },
@ -39,8 +39,12 @@
"dependencies": { "dependencies": {
"@0x/assert": "^2.0.10", "@0x/assert": "^2.0.10",
"@0x/connect": "^5.0.10", "@0x/connect": "^5.0.10",
"@0x/contract-addresses": "^2.3.3",
"@0x/contract-wrappers": "^9.1.4", "@0x/contract-wrappers": "^9.1.4",
"@0x/dev-utils": "^2.2.3",
"@0x/fill-scenarios": "^3.0.10",
"@0x/json-schemas": "^3.0.10", "@0x/json-schemas": "^3.0.10",
"@0x/migrations": "^4.1.6",
"@0x/order-utils": "^8.1.1", "@0x/order-utils": "^8.1.1",
"@0x/subproviders": "^4.1.0", "@0x/subproviders": "^4.1.0",
"@0x/types": "^2.2.2", "@0x/types": "^2.2.2",

View File

@ -22,7 +22,6 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: ForwarderSwapQuoteGetOutputOpts = { const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: ForwarderSwapQuoteGetOutputOpts = {
feePercentage: 0, feePercentage: 0,
feeRecipient: NULL_ADDRESS, feeRecipient: NULL_ADDRESS,
ethAmount: new BigNumber(0),
}; };
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: ForwarderSwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS; const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: ForwarderSwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;

View File

@ -18,7 +18,8 @@ export {
export { SignedOrder } from '@0x/types'; export { SignedOrder } from '@0x/types';
export { BigNumber } from '@0x/utils'; export { BigNumber } from '@0x/utils';
export { SwapQuoter } from './asset_buyer'; export { ForwarderSwapQuoteConsumer } from './quote_consumers/forwarder_swap_quote_consumer';
export { SwapQuoter } from './swap_quoter';
export { InsufficientAssetLiquidityError } from './errors'; export { InsufficientAssetLiquidityError } from './errors';
export { BasicOrderProvider } from './order_providers/basic_order_provider'; export { BasicOrderProvider } from './order_providers/basic_order_provider';

View File

@ -22,21 +22,13 @@ import { assetDataUtils } from '../utils/asset_data_utils';
import { utils } from '../utils/utils'; import { utils } from '../utils/utils';
export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMarketBuySmartContractParams> { export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMarketBuySmartContractParams> {
public readonly provider: ZeroExProvider; public readonly provider: ZeroExProvider;
public readonly networkId: number; public readonly networkId: number;
private readonly _contractWrappers: ContractWrappers; private readonly _contractWrappers: ContractWrappers;
constructor( constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
supportedProvider: SupportedProvider, const { networkId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
options: Partial<SwapQuoteConsumerOpts> = {},
) {
const { networkId } = _.merge(
{},
constants.DEFAULT_SWAP_QUOTER_OPTS,
options,
);
assert.isNumber('networkId', networkId); assert.isNumber('networkId', networkId);
const provider = providerUtils.standardizeOrThrow(supportedProvider); const provider = providerUtils.standardizeOrThrow(supportedProvider);
@ -64,12 +56,16 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
const calldataHexString = abiEncoder.encode(args); const calldataHexString = abiEncoder.encode(args);
return { return {
calldataHexString, calldataHexString,
methodAbi,
to, to,
ethAmount, ethAmount,
}; };
} }
public getSmartContractParamsOrThrow(quote: SwapQuote, opts: Partial<ForwarderSwapQuoteGetOutputOpts>): SmartContractParamsInfo<ForwarderMarketBuySmartContractParams> { public getSmartContractParamsOrThrow(
quote: SwapQuote,
opts: Partial<ForwarderSwapQuoteGetOutputOpts>,
): SmartContractParamsInfo<ForwarderMarketBuySmartContractParams> {
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge( const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge(
@ -80,14 +76,19 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
assert.isNumber('feePercentage', unFormattedFeePercentage); assert.isNumber('feePercentage', unFormattedFeePercentage);
assert.isETHAddressHex('feeRecipient', feeRecipient); assert.isETHAddressHex('feeRecipient', feeRecipient);
assert.isBigNumber('ethAmount', ethAmount); if (ethAmount !== undefined) {
assert.isBigNumber('ethAmount', ethAmount);
}
const swapQuoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, unFormattedFeePercentage); const swapQuoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(
quote,
unFormattedFeePercentage,
);
const { orders, feeOrders, makerAssetFillAmount, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee; const { orders, feeOrders, makerAssetFillAmount, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee;
const signatures = _.map(orders, o => o.signature); const signatures = _.map(orders, o => o.signature);
const feeSignatures = _.map(orders, o => o.signature); const feeSignatures = _.map(feeOrders, o => o.signature);
const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage); const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage);
@ -101,7 +102,11 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
feeRecipient, feeRecipient,
}; };
const methodAbi = utils.getMethodAbiFromContractAbi(this._contractWrappers.forwarder.abi, 'marketBuyOrdersWithEth') as MethodAbi; const methodAbi = utils.getMethodAbiFromContractAbi(
this._contractWrappers.forwarder.abi,
'marketBuyOrdersWithEth',
) as MethodAbi;
return { return {
params, params,
to: this._contractWrappers.forwarder.address, to: this._contractWrappers.forwarder.address,
@ -110,7 +115,10 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
}; };
} }
public async executeSwapQuoteOrThrowAsync(quote: SwapQuote, opts: Partial<ForwarderSwapQuoteExecutionOpts>): Promise<string> { public async executeSwapQuoteOrThrowAsync(
quote: SwapQuote,
opts: Partial<ForwarderSwapQuoteExecutionOpts>,
): Promise<string> {
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow()); assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
const { ethAmount, takerAddress, gasLimit, gasPrice, feeRecipient, feePercentage } = _.merge( const { ethAmount, takerAddress, gasLimit, gasPrice, feeRecipient, feePercentage } = _.merge(
@ -121,8 +129,9 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
assert.isNumber('feePercentage', feePercentage); assert.isNumber('feePercentage', feePercentage);
assert.isETHAddressHex('feeRecipient', feeRecipient); assert.isETHAddressHex('feeRecipient', feeRecipient);
assert.isBigNumber('ethAmount', ethAmount); if (ethAmount !== undefined) {
assert.isBigNumber('ethAmount', ethAmount);
}
if (takerAddress !== undefined) { if (takerAddress !== undefined) {
assert.isETHAddressHex('takerAddress', takerAddress); assert.isETHAddressHex('takerAddress', takerAddress);
} }

View File

@ -89,7 +89,11 @@ export class SwapQuoter {
* *
* @return An instance of SwapQuoter * @return An instance of SwapQuoter
*/ */
constructor(supportedProvider: SupportedProvider, orderProvider: OrderProvider, options: Partial<SwapQuoterOpts> = {}) { constructor(
supportedProvider: SupportedProvider,
orderProvider: OrderProvider,
options: Partial<SwapQuoterOpts> = {},
) {
const { networkId, orderRefreshIntervalMs, expiryBufferMs } = _.merge( const { networkId, orderRefreshIntervalMs, expiryBufferMs } = _.merge(
{}, {},
constants.DEFAULT_SWAP_QUOTER_OPTS, constants.DEFAULT_SWAP_QUOTER_OPTS,
@ -202,7 +206,7 @@ export class SwapQuoter {
): Promise<LiquidityForAssetData> { ): Promise<LiquidityForAssetData> {
const shouldForceOrderRefresh = const shouldForceOrderRefresh =
options.shouldForceOrderRefresh !== undefined ? options.shouldForceOrderRefresh : false; options.shouldForceOrderRefresh !== undefined ? options.shouldForceOrderRefresh : false;
assert.isString('makerAssetDataa', makerAssetData); assert.isString('makerAssetData', makerAssetData);
assert.isString('takerAssetData', takerAssetData); assert.isString('takerAssetData', takerAssetData);
assetDataUtils.decodeAssetDataOrThrow(makerAssetData); assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
assetDataUtils.decodeAssetDataOrThrow(takerAssetData); assetDataUtils.decodeAssetDataOrThrow(takerAssetData);

View File

@ -47,6 +47,7 @@ export interface OrderProvider {
*/ */
export interface CalldataInfo { export interface CalldataInfo {
calldataHexString: string; calldataHexString: string;
methodAbi: MethodAbi;
to: string; to: string;
ethAmount?: BigNumber; ethAmount?: BigNumber;
} }
@ -128,14 +129,13 @@ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOpts { export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOpts {
feePercentage: number; feePercentage: number;
feeRecipient: string; feeRecipient: string;
ethAmount: BigNumber; ethAmount?: BigNumber;
} }
/** /**
* Represents the options for executing a swap quote with ForwarderSwapQuoteConusmer * Represents the options for executing a swap quote with ForwarderSwapQuoteConusmer
*/ */
export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOpts { export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOpts {}
}
/** /**
* takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). * takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).

View File

@ -11,7 +11,7 @@ export const swapQuoteCalculator = {
calculate( calculate(
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
makerAssetBuyAmount: BigNumber, makerAssetFillAmount: BigNumber,
slippagePercentage: number, slippagePercentage: number,
isMakerAssetZrxToken: boolean, isMakerAssetZrxToken: boolean,
): SwapQuote { ): SwapQuote {
@ -19,20 +19,20 @@ export const swapQuoteCalculator = {
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts; const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const feeOrders = feeOrdersAndFillableAmounts.orders; const feeOrders = feeOrdersAndFillableAmounts.orders;
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts; const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const slippageBufferAmount = makerAssetBuyAmount.multipliedBy(slippagePercentage).integerValue(); const slippageBufferAmount = makerAssetFillAmount.multipliedBy(slippagePercentage).integerValue();
// find the orders that cover the desired assetBuyAmount (with slippage) // find the orders that cover the desired assetBuyAmount (with slippage)
const { const {
resultOrders, resultOrders,
remainingFillAmount, remainingFillAmount,
ordersRemainingFillableMakerAssetAmounts, ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, makerAssetBuyAmount, { } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, makerAssetFillAmount, {
remainingFillableMakerAssetAmounts, remainingFillableMakerAssetAmounts,
slippageBufferAmount, slippageBufferAmount,
}); });
// if we do not have enough orders to cover the desired assetBuyAmount, throw // if we do not have enough orders to cover the desired assetBuyAmount, throw
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
// We needed the amount they requested to buy, plus the amount for slippage // We needed the amount they requested to buy, plus the amount for slippage
const totalAmountRequested = makerAssetBuyAmount.plus(slippageBufferAmount); const totalAmountRequested = makerAssetFillAmount.plus(slippageBufferAmount);
const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount); const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount);
// multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by // multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by
// in order to get the total amount needed considering slippage // in order to get the total amount needed considering slippage
@ -87,21 +87,21 @@ export const swapQuoteCalculator = {
const bestCaseQuoteInfo = calculateQuoteInfo( const bestCaseQuoteInfo = calculateQuoteInfo(
trimmedOrdersAndFillableAmounts, trimmedOrdersAndFillableAmounts,
trimmedFeeOrdersAndFillableAmounts, trimmedFeeOrdersAndFillableAmounts,
makerAssetBuyAmount, makerAssetFillAmount,
isMakerAssetZrxToken, isMakerAssetZrxToken,
); );
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = calculateQuoteInfo( const worstCaseQuoteInfo = calculateQuoteInfo(
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts), reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
makerAssetBuyAmount, makerAssetFillAmount,
isMakerAssetZrxToken, isMakerAssetZrxToken,
); );
return { return {
takerAssetData, takerAssetData,
makerAssetData, makerAssetData,
makerAssetBuyAmount, makerAssetFillAmount,
orders: resultOrders, orders: resultOrders,
feeOrders: resultFeeOrders, feeOrders: resultFeeOrders,
bestCaseQuoteInfo, bestCaseQuoteInfo,
@ -191,30 +191,30 @@ function findTakerTokenAmountNeededToBuyZrx(
function findTakerTokenAndZrxAmountNeededToBuyAsset( function findTakerTokenAndZrxAmountNeededToBuyAsset(
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
makerAssetBuyAmount: BigNumber, makerAssetFillAmount: BigNumber,
): [BigNumber, BigNumber] { ): [BigNumber, BigNumber] {
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts; const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
const result = _.reduce( const result = _.reduce(
orders, orders,
(acc, order, index) => { (acc, order, index) => {
const { totalTakerTokenAmount, totalZrxAmount, remainingMakerAssetBuyAmount } = acc; const { totalTakerTokenAmount, totalZrxAmount, remainingmakerAssetFillAmount } = acc;
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
const makerFillAmount = BigNumber.min(acc.remainingMakerAssetBuyAmount, remainingFillableMakerAssetAmount); const makerFillAmount = BigNumber.min(acc.remainingmakerAssetFillAmount, remainingFillableMakerAssetAmount);
const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount); const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount);
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount); const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
return { return {
totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount), totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount),
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount), totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),
remainingMakerAssetBuyAmount: BigNumber.max( remainingmakerAssetFillAmount: BigNumber.max(
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
remainingMakerAssetBuyAmount.minus(makerFillAmount), remainingmakerAssetFillAmount.minus(makerFillAmount),
), ),
}; };
}, },
{ {
totalTakerTokenAmount: constants.ZERO_AMOUNT, totalTakerTokenAmount: constants.ZERO_AMOUNT,
totalZrxAmount: constants.ZERO_AMOUNT, totalZrxAmount: constants.ZERO_AMOUNT,
remainingMakerAssetBuyAmount: makerAssetBuyAmount, remainingmakerAssetFillAmount: makerAssetFillAmount,
}, },
); );
return [result.totalTakerTokenAmount, result.totalZrxAmount]; return [result.totalTakerTokenAmount, result.totalZrxAmount];

View File

@ -15,7 +15,7 @@ export const utils = {
return _.find( return _.find(
abi, abi,
(def: AbiDefinition): boolean => { (def: AbiDefinition): boolean => {
if (def.type === `'function'`) { if (def.type === 'function') {
const methodDef = def as MethodAbi; const methodDef = def as MethodAbi;
return methodDef.name === name; return methodDef.name === name;
} else { } else {

View File

@ -0,0 +1,142 @@
import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils';
import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils';
import { FillScenarios } from '@0x/fill-scenarios';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { Web3ProviderEngine } from '@0x/subproviders';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import 'mocha';
import * as TypeMoq from 'typemoq';
import { ForwarderSwapQuoteConsumer, SwapQuote } from '../src';
import { chaiSetup } from './utils/chai_setup';
import { migrateOnceAsync } from './utils/migrate';
import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFees } from './utils/swap_quote';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const FILLABLE_AMOUNTS = [new BigNumber(5), new BigNumber(10)];
const TESTRPC_NETWORK_ID = 50;
describe('ForwarderSwapQuoteConsumer', () => {
let userAddresses: string[];
let makerAddress: string;
let takerAddress: string;
let fillScenarios: FillScenarios;
let feeRecipient: string;
let makerAssetData: string;
let takerAssetData: string;
let wethAssetData: string;
const networkId = TESTRPC_NETWORK_ID;
before(async () => {
const contractAddresses = await migrateOnceAsync();
await blockchainLifecycle.startAsync();
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
fillScenarios = new FillScenarios(
provider,
userAddresses,
contractAddresses.zrxToken,
contractAddresses.exchange,
contractAddresses.erc20Proxy,
contractAddresses.erc721Proxy,
);
[makerAddress, takerAddress, feeRecipient] = userAddresses;
const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
[makerAssetData, takerAssetData, wethAssetData] = [
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken),
];
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
// This constructor has incorrect types
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('getSmartContractParamsOrThrow', () => {
describe('validation', () => {
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
const invalidSignedOrders = getSignedOrdersWithNoFees(
makerAssetData,
takerAssetData,
makerAddress,
takerAddress,
FILLABLE_AMOUNTS,
);
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(makerAssetData, takerAssetData, invalidSignedOrders);
const swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, {});
// TODO(dave4506) finish up testing/coverage
// expect(
// swapQuoteConsumer.getSmartContractParamsOrThrow(invalidSwapQuote, {}),
// ).to.throws();
});
});
describe('valid swap quote', async () => {
it('provide correct smart contract params with default options', async () => {
const signedOrders = getSignedOrdersWithNoFees(
makerAssetData,
wethAssetData,
makerAddress,
takerAddress,
FILLABLE_AMOUNTS,
);
const swapQuote = getFullyFillableSwapQuoteWithNoFees(makerAssetData, takerAssetData, signedOrders);
const swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, { networkId });
const smartContractParamsInfo = swapQuoteConsumer.getSmartContractParamsOrThrow(swapQuote, {});
// console.log(smartContractParamsInfo);
// TODO(dave4506): Add elaborate testing
});
});
});
describe('getCalldataOrThrow', () => {
describe('validation', () => {
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
const invalidSignedOrders = getSignedOrdersWithNoFees(
makerAssetData,
takerAssetData,
makerAddress,
takerAddress,
FILLABLE_AMOUNTS,
);
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(makerAssetData, takerAssetData, invalidSignedOrders);
const swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, {});
// TODO(dave4506) finish up testing/coverage
// expect(
// swapQuoteConsumer.getSmartContractParamsOrThrow(invalidSwapQuote, {}),
// ).to.throws();
});
});
describe('valid swap quote', async () => {
it('provide correct calldata hex with default options', async () => {
const signedOrders = getSignedOrdersWithNoFees(
makerAssetData,
wethAssetData,
makerAddress,
takerAddress,
FILLABLE_AMOUNTS,
);
const swapQuote = getFullyFillableSwapQuoteWithNoFees(makerAssetData, takerAssetData, signedOrders);
const swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, { networkId });
const callDataInfo = swapQuoteConsumer.getCalldataOrThrow(swapQuote, {});
// console.log(callDataInfo);
// TODO(dave4506): Add elaborate testing
});
});
});
});

View File

@ -0,0 +1,6 @@
before('set up mocha', async function(): Promise<void> {
// HACK: Since the migrations take longer then our global mocha timeout limit
// we manually increase it for this before hook.
const mochaTestTimeoutMs = 25000;
this.timeout(mochaTestTimeoutMs); // tslint:disable-line:no-invalid-this
});

View File

@ -5,8 +5,8 @@ import * as chai from 'chai';
import * as _ from 'lodash'; import * as _ from 'lodash';
import 'mocha'; import 'mocha';
import { AssetBuyerError, OrdersAndFillableAmounts } from '../src/types'; import { OrdersAndFillableAmounts, SwapQuoterError } from '../src/types';
import { buyQuoteCalculator } from '../src/utils/buy_quote_calculator'; import { swapQuoteCalculator } from '../src/utils/swap_quote_calculator';
import { chaiSetup } from './utils/chai_setup'; import { chaiSetup } from './utils/chai_setup';
import { testHelpers } from './utils/test_helpers'; import { testHelpers } from './utils/test_helpers';
@ -15,7 +15,7 @@ chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
// tslint:disable:custom-no-magic-numbers // tslint:disable:custom-no-magic-numbers
describe('buyQuoteCalculator', () => { describe('swapQuoteCalculator', () => {
describe('#calculate', () => { describe('#calculate', () => {
let firstOrder: SignedOrder; let firstOrder: SignedOrder;
let firstRemainingFillAmount: BigNumber; let firstRemainingFillAmount: BigNumber;
@ -71,12 +71,11 @@ describe('buyQuoteCalculator', () => {
it('should throw if not enough maker asset liquidity (multiple orders)', () => { it('should throw if not enough maker asset liquidity (multiple orders)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(500), new BigNumber(500),
0, 0,
0,
false, false,
); );
}; };
@ -85,11 +84,10 @@ describe('buyQuoteCalculator', () => {
it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => { it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(500), new BigNumber(500),
0,
0.2, 0.2,
false, false,
); );
@ -99,11 +97,10 @@ describe('buyQuoteCalculator', () => {
it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => { it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(600), new BigNumber(600),
0,
0.05, 0.05,
false, false,
); );
@ -117,12 +114,11 @@ describe('buyQuoteCalculator', () => {
}; };
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
firstOrderAndFillableAmount, firstOrderAndFillableAmount,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(201), new BigNumber(201),
0, 0,
0,
false, false,
); );
}; };
@ -139,12 +135,11 @@ describe('buyQuoteCalculator', () => {
remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount], remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount],
}; };
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
completelyFillableOrdersAndFillableAmount, completelyFillableOrdersAndFillableAmount,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(124), new BigNumber(124),
0, 0,
0,
false, false,
); );
}; };
@ -157,12 +152,11 @@ describe('buyQuoteCalculator', () => {
takerFee: new BigNumber(0), takerFee: new BigNumber(0),
}); });
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
{ orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] }, { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(600), new BigNumber(600),
0, 0,
0,
false, false,
); );
}; };
@ -175,11 +169,10 @@ describe('buyQuoteCalculator', () => {
takerFee: new BigNumber(0), takerFee: new BigNumber(0),
}); });
const errorFunction = () => { const errorFunction = () => {
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
{ orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] }, { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(600), new BigNumber(600),
0,
0.2, 0.2,
false, false,
); );
@ -189,12 +182,11 @@ describe('buyQuoteCalculator', () => {
}); });
it('should not throw if order is fillable', () => { it('should not throw if order is fillable', () => {
expect(() => expect(() =>
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
allFeeOrdersAndFillableAmounts, allFeeOrdersAndFillableAmounts,
new BigNumber(300), new BigNumber(300),
0, 0,
0,
false, false,
), ),
).to.not.throw(); ).to.not.throw();
@ -202,94 +194,102 @@ describe('buyQuoteCalculator', () => {
it('should throw if not enough ZRX liquidity', () => { it('should throw if not enough ZRX liquidity', () => {
// we request 300 makerAsset units but the ZRX order is only enough to fill the first order, which only has 200 makerAssetUnits available // we request 300 makerAsset units but the ZRX order is only enough to fill the first order, which only has 200 makerAssetUnits available
expect(() => expect(() =>
buyQuoteCalculator.calculate( swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
new BigNumber(300), new BigNumber(300),
0, 0,
0,
false, false,
), ),
).to.throw(AssetBuyerError.InsufficientZrxLiquidity); ).to.throw(SwapQuoterError.InsufficientZrxLiquidity);
}); });
it('calculates a correct buyQuote with no slippage', () => { it('calculates a correct swapQuote with no slippage', () => {
// we request 200 makerAsset units which can be filled using the first order // we request 200 makerAsset units which can be filled using the first order
// the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
const assetBuyAmount = new BigNumber(200); const assetBuyAmount = new BigNumber(200);
const feePercentage = 0.02;
const slippagePercentage = 0; const slippagePercentage = 0;
const buyQuote = buyQuoteCalculator.calculate( const swapQuote = swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount, smallFeeOrderAndFillableAmount,
assetBuyAmount, assetBuyAmount,
feePercentage,
slippagePercentage, slippagePercentage,
false, false,
); );
// test if orders are correct // test if orders are correct
expect(buyQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]); expect(swapQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
expect(buyQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]); expect(swapQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]);
// test if rates are correct // test if rates are correct
// 50 eth to fill the first order + 100 eth for fees // 50 eth to fill the first order + 100 eth for fees
const expectedEthAmountForAsset = new BigNumber(50); const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100); const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset; const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset.plus(
const expectedAffiliateFeeEthAmount = expectedEthAmountForAsset.multipliedBy(feePercentage); expectedTakerAssetAmountForZrxFees,
const expectedFeeEthAmount = expectedAffiliateFeeEthAmount.plus(expectedEthAmountForZrxFees); );
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount); expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
expect(buyQuote.bestCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount); expectedTakerAssetAmountForMakerAsset,
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); );
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
expectedTakerAssetAmountForZrxFees,
);
expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
// because we have no slippage protection, minRate is equal to maxRate // because we have no slippage protection, minRate is equal to maxRate
expect(buyQuote.worstCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount); expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); expectedTakerAssetAmountForMakerAsset,
expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); );
// test if feePercentage gets passed through expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
expect(buyQuote.feePercentage).to.equal(feePercentage); expectedTakerAssetAmountForZrxFees,
);
expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
expectedTotalTakerAssetAmount,
);
}); });
it('calculates a correct buyQuote with with slippage', () => { it('calculates a correct swapQuote with with slippage', () => {
// we request 200 makerAsset units which can be filled using the first order // we request 200 makerAsset units which can be filled using the first order
// however with 50% slippage we are protecting the buy with 100 extra makerAssetUnits // however with 50% slippage we are protecting the buy with 100 extra makerAssetUnits
// so we need enough orders to fill 300 makerAssetUnits // so we need enough orders to fill 300 makerAssetUnits
// 300 makerAssetUnits can only be filled using both orders // 300 makerAssetUnits can only be filled using both orders
// the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder // the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
const assetBuyAmount = new BigNumber(200); const assetBuyAmount = new BigNumber(200);
const feePercentage = 0.02;
const slippagePercentage = 0.5; const slippagePercentage = 0.5;
const buyQuote = buyQuoteCalculator.calculate( const swapQuote = swapQuoteCalculator.calculate(
ordersAndFillableAmounts, ordersAndFillableAmounts,
allFeeOrdersAndFillableAmounts, allFeeOrdersAndFillableAmounts,
assetBuyAmount, assetBuyAmount,
feePercentage,
slippagePercentage, slippagePercentage,
false, false,
); );
// test if orders are correct // test if orders are correct
expect(buyQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders); expect(swapQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
expect(buyQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders); expect(swapQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders);
// test if rates are correct // test if rates are correct
// 50 eth to fill the first order + 100 eth for fees // 50 eth to fill the first order + 100 eth for fees
const expectedEthAmountForAsset = new BigNumber(50); const expectedTakerAssetAmountForMakerAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100); const expectedTakerAssetAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset; const expectedTotalTakerAssetAmount = expectedTakerAssetAmountForMakerAsset.plus(
const expectedAffiliateFeeEthAmount = expectedEthAmountForAsset.multipliedBy(feePercentage); expectedTakerAssetAmountForZrxFees,
const expectedFeeEthAmount = expectedAffiliateFeeEthAmount.plus(expectedEthAmountForZrxFees); );
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount); expect(swapQuote.bestCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
expect(buyQuote.bestCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount); expectedTakerAssetAmountForMakerAsset,
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); );
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); expect(swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
expectedTakerAssetAmountForZrxFees,
);
expect(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(expectedTotalTakerAssetAmount);
// 100 eth to fill the first order + 208 eth for fees // 100 eth to fill the first order + 208 eth for fees
const expectedWorstEthAmountForAsset = new BigNumber(100); const expectedWorstTakerAssetAmountForMakerAsset = new BigNumber(100);
const expectedWorstEthAmountForZrxFees = new BigNumber(208); const expectedWorstTakerAssetAmountForZrxFees = new BigNumber(208);
const expectedWorstFillEthAmount = expectedWorstEthAmountForAsset; const expectedWorstTotalTakerAssetAmount = expectedWorstTakerAssetAmountForMakerAsset.plus(
const expectedWorstAffiliateFeeEthAmount = expectedWorstEthAmountForAsset.multipliedBy(feePercentage); expectedWorstTakerAssetAmountForZrxFees,
const expectedWorstFeeEthAmount = expectedWorstAffiliateFeeEthAmount.plus(expectedWorstEthAmountForZrxFees); );
const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.plus(expectedWorstFeeEthAmount); expect(swapQuote.worstCaseQuoteInfo.takerTokenAmount).to.bignumber.equal(
expect(buyQuote.worstCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedWorstFillEthAmount); expectedWorstTakerAssetAmountForMakerAsset,
expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedWorstFeeEthAmount); );
expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedWorstTotalEthAmount); expect(swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.bignumber.equal(
// test if feePercentage gets passed through expectedWorstTakerAssetAmountForZrxFees,
expect(buyQuote.feePercentage).to.equal(feePercentage); );
expect(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.bignumber.equal(
expectedWorstTotalTakerAssetAmount,
);
}); });
}); });
}); });

View File

@ -7,14 +7,15 @@ import * as chai from 'chai';
import 'mocha'; import 'mocha';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { AssetBuyer } from '../src'; import { SwapQuoter } from '../src';
import { constants } from '../src/constants'; import { constants } from '../src/constants';
import { LiquidityForAssetData, OrderProvider, OrdersAndFillableAmounts } from '../src/types'; import { LiquidityForAssetData, OrderProvider, OrdersAndFillableAmounts } from '../src/types';
import { chaiSetup } from './utils/chai_setup'; import { chaiSetup } from './utils/chai_setup';
import { import {
mockAvailableAssetDatas, mockAvailableMakerAssetDatas,
mockedAssetBuyerWithOrdersAndFillableAmounts, mockAvailableTakerAssetDatas,
mockedSwapQuoterWithOrdersAndFillableAmounts,
orderProviderMock, orderProviderMock,
} from './utils/mocks'; } from './utils/mocks';
@ -22,7 +23,8 @@ chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const FAKE_SRA_URL = 'https://fakeurl.com'; const FAKE_SRA_URL = 'https://fakeurl.com';
const FAKE_ASSET_DATA = '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48'; const FAKE_TAKER_ASSET_DATA = '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48';
const FAKE_MAKER_ASSET_DATA = '0xf47261b00000000000000000000000009f5B0C7e1623793bF0620569b9749e79DF6D0bC5';
const TOKEN_DECIMALS = 18; const TOKEN_DECIMALS = 18;
const DAI_ASSET_DATA = '0xf47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359"'; const DAI_ASSET_DATA = '0xf47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359"';
const WETH_ASSET_DATA = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const WETH_ASSET_DATA = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
@ -38,19 +40,23 @@ const expectLiquidityResult = async (
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
expectedLiquidityResult: LiquidityForAssetData, expectedLiquidityResult: LiquidityForAssetData,
) => { ) => {
const mockedAssetBuyer = mockedAssetBuyerWithOrdersAndFillableAmounts( const mockedSwapQuoter = mockedSwapQuoterWithOrdersAndFillableAmounts(
web3Provider, web3Provider,
orderProvider, orderProvider,
FAKE_ASSET_DATA, FAKE_MAKER_ASSET_DATA,
WETH_ASSET_DATA,
ordersAndFillableAmounts, ordersAndFillableAmounts,
); );
const liquidityResult = await mockedAssetBuyer.object.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA); const liquidityResult = await mockedSwapQuoter.object.getLiquidityForMakerTakerAssetDataPairAsync(
FAKE_MAKER_ASSET_DATA,
WETH_ASSET_DATA,
);
expect(liquidityResult).to.deep.equal(expectedLiquidityResult); expect(liquidityResult).to.deep.equal(expectedLiquidityResult);
}; };
// tslint:disable:custom-no-magic-numbers // tslint:disable:custom-no-magic-numbers
describe('AssetBuyer', () => { describe('SwapQuoter', () => {
describe('getLiquidityForAssetDataAsync', () => { describe('getLiquidityForMakerTakerAssetDataPairAsync', () => {
const mockWeb3Provider = TypeMoq.Mock.ofType(Web3ProviderEngine); const mockWeb3Provider = TypeMoq.Mock.ofType(Web3ProviderEngine);
const mockOrderProvider = orderProviderMock(); const mockOrderProvider = orderProviderMock();
@ -65,40 +71,51 @@ describe('AssetBuyer', () => {
}); });
describe('validation', () => { describe('validation', () => {
it('should ensure assetData is a string', async () => { it('should ensure takerAssetData is a string', async () => {
const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl( const swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(
mockWeb3Provider.object, mockWeb3Provider.object,
FAKE_SRA_URL, FAKE_SRA_URL,
); );
expect(assetBuyer.getLiquidityForAssetDataAsync(false as any)).to.be.rejectedWith( expect(
'Expected assetData to be of type string, encountered: false', swapQuoter.getLiquidityForMakerTakerAssetDataPairAsync(FAKE_MAKER_ASSET_DATA, false as any),
).to.be.rejectedWith('Expected takerAssetData to be of type string, encountered: false');
});
it('should ensure makerAssetData is a string', async () => {
const swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(
mockWeb3Provider.object,
FAKE_SRA_URL,
); );
expect(
swapQuoter.getLiquidityForMakerTakerAssetDataPairAsync(false as any, FAKE_TAKER_ASSET_DATA),
).to.be.rejectedWith('Expected makerAssetData to be of type string, encountered: false');
}); });
}); });
describe('asset pair not supported', () => { describe('asset pair not supported', () => {
it('should return 0s when no asset pair not supported', async () => { it('should return 0s when no asset pair are supported', async () => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, []); mockAvailableMakerAssetDatas(mockOrderProvider, FAKE_TAKER_ASSET_DATA, []);
const assetBuyer = new AssetBuyer(mockWeb3Provider.object, mockOrderProvider.object); const swapQuoter = new SwapQuoter(mockWeb3Provider.object, mockOrderProvider.object);
const liquidityResult = await assetBuyer.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA); const liquidityResult = await swapQuoter.getLiquidityForMakerTakerAssetDataPairAsync(FAKE_MAKER_ASSET_DATA, FAKE_TAKER_ASSET_DATA);
expect(liquidityResult).to.deep.equal({ expect(liquidityResult).to.deep.equal({
tokensAvailableInBaseUnits: new BigNumber(0), makerTokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0), takerTokensAvailableInBaseUnits: new BigNumber(0),
}); });
}); });
it('should return 0s when only other asset pair supported', async () => { it('should return 0s when only other asset pair supported', async () => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, [DAI_ASSET_DATA]); mockAvailableMakerAssetDatas(mockOrderProvider, FAKE_TAKER_ASSET_DATA, [DAI_ASSET_DATA]);
const assetBuyer = new AssetBuyer(mockWeb3Provider.object, mockOrderProvider.object); const swapQuoter = new SwapQuoter(mockWeb3Provider.object, mockOrderProvider.object);
const liquidityResult = await assetBuyer.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA); const liquidityResult = await swapQuoter.getLiquidityForMakerTakerAssetDataPairAsync(FAKE_MAKER_ASSET_DATA, FAKE_TAKER_ASSET_DATA);
expect(liquidityResult).to.deep.equal({ expect(liquidityResult).to.deep.equal({
tokensAvailableInBaseUnits: new BigNumber(0), makerTokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0), takerTokensAvailableInBaseUnits: new BigNumber(0),
}); });
}); });
}); });
describe('assetData is supported', () => { describe('assetData is supported', () => {
// orders // orders
@ -112,7 +129,7 @@ describe('AssetBuyer', () => {
}); });
beforeEach(() => { beforeEach(() => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, [WETH_ASSET_DATA]); mockAvailableMakerAssetDatas(mockOrderProvider, WETH_ASSET_DATA, [FAKE_MAKER_ASSET_DATA]);
}); });
it('should return 0s when no orders available', async () => { it('should return 0s when no orders available', async () => {
@ -121,8 +138,8 @@ describe('AssetBuyer', () => {
remainingFillableMakerAssetAmounts: [], remainingFillableMakerAssetAmounts: [],
}; };
const expectedResult = { const expectedResult = {
tokensAvailableInBaseUnits: new BigNumber(0), makerTokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0), takerTokensAvailableInBaseUnits: new BigNumber(0),
}; };
await expectLiquidityResult( await expectLiquidityResult(
mockWeb3Provider.object, mockWeb3Provider.object,
@ -139,11 +156,12 @@ describe('AssetBuyer', () => {
remainingFillableMakerAssetAmounts: orders.map(o => o.makerAssetAmount), remainingFillableMakerAssetAmounts: orders.map(o => o.makerAssetAmount),
}; };
const expectedTokensAvailable = orders[0].makerAssetAmount.plus(orders[1].makerAssetAmount); const expectedMakerTokensAvailable = orders[0].makerAssetAmount.plus(orders[1].makerAssetAmount);
const expectedEthValueAvailable = orders[0].takerAssetAmount.plus(orders[1].takerAssetAmount); const expectedTakerTokensAvailable = orders[0].takerAssetAmount.plus(orders[1].takerAssetAmount);
const expectedResult = { const expectedResult = {
tokensAvailableInBaseUnits: expectedTokensAvailable, makerTokensAvailableInBaseUnits: expectedMakerTokensAvailable,
ethValueAvailableInWei: expectedEthValueAvailable, takerTokensAvailableInBaseUnits: expectedTakerTokensAvailable,
}; };
await expectLiquidityResult( await expectLiquidityResult(
@ -159,9 +177,10 @@ describe('AssetBuyer', () => {
orders: [sellTwoTokensFor1Weth], orders: [sellTwoTokensFor1Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(1)], remainingFillableMakerAssetAmounts: [baseUnitAmount(1)],
}; };
const expectedResult = { const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(1), makerTokensAvailableInBaseUnits: baseUnitAmount(1),
ethValueAvailableInWei: baseUnitAmount(0.5, WETH_DECIMALS), takerTokensAvailableInBaseUnits: baseUnitAmount(0.5, WETH_DECIMALS),
}; };
await expectLiquidityResult( await expectLiquidityResult(
@ -177,9 +196,10 @@ describe('AssetBuyer', () => {
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth], orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(3)], remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(3)],
}; };
const expectedResult = { const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(4), makerTokensAvailableInBaseUnits: baseUnitAmount(4),
ethValueAvailableInWei: baseUnitAmount(3.5, WETH_DECIMALS), takerTokensAvailableInBaseUnits: baseUnitAmount(3.5, WETH_DECIMALS),
}; };
await expectLiquidityResult( await expectLiquidityResult(
@ -195,9 +215,10 @@ describe('AssetBuyer', () => {
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth], orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(0)], remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(0)],
}; };
const expectedResult = { const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(0), makerTokensAvailableInBaseUnits: baseUnitAmount(0),
ethValueAvailableInWei: baseUnitAmount(0, WETH_DECIMALS), takerTokensAvailableInBaseUnits: baseUnitAmount(0, WETH_DECIMALS),
}; };
await expectLiquidityResult( await expectLiquidityResult(

View File

@ -0,0 +1,18 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { devConstants } from '@0x/dev-utils';
import { runMigrationsOnceAsync } from '@0x/migrations';
import { provider } from './web3_wrapper';
/**
* Configures and runs the migrations exactly once. Any subsequent times this is
* called, it returns the cached addresses.
* @returns The addresses of contracts that were deployed during the migrations.
*/
export async function migrateOnceAsync(): Promise<ContractAddresses> {
const txDefaults = {
gas: devConstants.GAS_LIMIT,
from: devConstants.TESTRPC_FIRST_ADDRESS,
};
return runMigrationsOnceAsync(provider, txDefaults);
}

View File

@ -1,7 +1,7 @@
import { Web3ProviderEngine } from '@0x/subproviders'; import { Web3ProviderEngine } from '@0x/subproviders';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { SwapQuoter } from '../../src/asset_buyer'; import { SwapQuoter } from '../../src/swap_quoter';
import { OrderProvider, OrderProviderResponse, OrdersAndFillableAmounts } from '../../src/types'; import { OrderProvider, OrderProviderResponse, OrdersAndFillableAmounts } from '../../src/types';
// tslint:disable:promise-function-async // tslint:disable:promise-function-async
@ -26,7 +26,7 @@ export const orderProviderMock = () => {
return TypeMoq.Mock.ofType(OrderProviderClass, TypeMoq.MockBehavior.Strict); return TypeMoq.Mock.ofType(OrderProviderClass, TypeMoq.MockBehavior.Strict);
}; };
export const mockAvailableAssetDatas = ( export const mockAvailableMakerAssetDatas = (
mockOrderProvider: TypeMoq.IMock<OrderProviderClass>, mockOrderProvider: TypeMoq.IMock<OrderProviderClass>,
assetData: string, assetData: string,
availableAssetDatas: string[], availableAssetDatas: string[],
@ -39,34 +39,49 @@ export const mockAvailableAssetDatas = (
.verifiable(TypeMoq.Times.once()); .verifiable(TypeMoq.Times.once());
}; };
const partiallyMockedAssetBuyer = ( export const mockAvailableTakerAssetDatas = (
mockOrderProvider: TypeMoq.IMock<OrderProviderClass>,
assetData: string,
availableAssetDatas: string[],
) => {
mockOrderProvider
.setup(op => op.getAvailableTakerAssetDatasAsync(TypeMoq.It.isValue(assetData)))
.returns(() => {
return Promise.resolve(availableAssetDatas);
})
.verifiable(TypeMoq.Times.once());
};
const partiallyMockedSwapQuoter = (
provider: Web3ProviderEngine, provider: Web3ProviderEngine,
orderProvider: OrderProvider, orderProvider: OrderProvider,
): TypeMoq.IMock<SwapQuoter> => { ): TypeMoq.IMock<SwapQuoter> => {
const rawAssetBuyer = new SwapQuoter(provider, orderProvider); const rawSwapQuoter = new SwapQuoter(provider, orderProvider);
const mockedAssetBuyer = TypeMoq.Mock.ofInstance(rawAssetBuyer, TypeMoq.MockBehavior.Loose, false); const mockedSwapQuoter = TypeMoq.Mock.ofInstance(rawSwapQuoter, TypeMoq.MockBehavior.Loose, false);
mockedAssetBuyer.callBase = true; mockedSwapQuoter.callBase = true;
return mockedAssetBuyer; return mockedSwapQuoter;
}; };
const mockGetOrdersAndAvailableAmounts = ( const mockGetOrdersAndAvailableAmounts = (
mockedAssetBuyer: TypeMoq.IMock<SwapQuoter>, mockedSwapQuoter: TypeMoq.IMock<SwapQuoter>,
assetData: string, makerAssetData: string,
takerAssetData: string,
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
): void => { ): void => {
mockedAssetBuyer mockedSwapQuoter
.setup(a => a.getOrdersAndFillableAmountsAsync(assetData, false)) .setup(a => a.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData, false))
.returns(() => Promise.resolve(ordersAndFillableAmounts)) .returns(() => Promise.resolve(ordersAndFillableAmounts))
.verifiable(TypeMoq.Times.once()); .verifiable(TypeMoq.Times.once());
}; };
export const mockedAssetBuyerWithOrdersAndFillableAmounts = ( export const mockedSwapQuoterWithOrdersAndFillableAmounts = (
provider: Web3ProviderEngine, provider: Web3ProviderEngine,
orderProvider: OrderProvider, orderProvider: OrderProvider,
assetData: string, makerAssetData: string,
takerAssetData: string,
ordersAndFillableAmounts: OrdersAndFillableAmounts, ordersAndFillableAmounts: OrdersAndFillableAmounts,
): TypeMoq.IMock<SwapQuoter> => { ): TypeMoq.IMock<SwapQuoter> => {
const mockedAssetBuyer = partiallyMockedAssetBuyer(provider, orderProvider); const mockedAssetQuoter = partiallyMockedSwapQuoter(provider, orderProvider);
mockGetOrdersAndAvailableAmounts(mockedAssetBuyer, assetData, ordersAndFillableAmounts); mockGetOrdersAndAvailableAmounts(mockedAssetQuoter, makerAssetData, takerAssetData, ordersAndFillableAmounts);
return mockedAssetBuyer; return mockedAssetQuoter;
}; };

View File

@ -0,0 +1,41 @@
import { FillScenarios } from '@0x/fill-scenarios';
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { SupportedProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { SwapQuote } from '../../src';
const ZERO_BIG_NUMBER = new BigNumber(0);
export const getSignedOrdersWithNoFees = (makerAssetData: string, takerAssetData: string, makerAddress: string, takerAddress: string, fillableAmounts: BigNumber[]): SignedOrder[] => {
return _.map(fillableAmounts, (fillableAmount: BigNumber) => orderFactory.createSignedOrderFromPartial(
{
makerAddress,
makerAssetAmount: fillableAmount,
makerAssetData,
takerAssetAmount: fillableAmount,
takerAssetData,
}));
};
export const getFullyFillableSwapQuoteWithNoFees = (makerAssetData: string, takerAssetData: string, orders: SignedOrder[]): SwapQuote => {
const makerAssetFillAmount = _.reduce(orders, (a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount), ZERO_BIG_NUMBER);
const totalTakerTokenAmount = _.reduce(orders, (a: BigNumber, c: SignedOrder) => a.plus(c.takerAssetAmount), ZERO_BIG_NUMBER);
const quoteInfo = {
takerTokenAmount: totalTakerTokenAmount,
feeTakerTokenAmount: ZERO_BIG_NUMBER,
totalTakerTokenAmount,
};
return {
makerAssetData,
takerAssetData,
orders,
feeOrders: [],
makerAssetFillAmount,
bestCaseQuoteInfo: quoteInfo,
worstCaseQuoteInfo: quoteInfo,
};
};

View File

@ -0,0 +1,8 @@
import { web3Factory } from '@0x/dev-utils';
import { Web3ProviderEngine } from '@0x/subproviders';
import { Web3Wrapper } from '@0x/web3-wrapper';
const provider: Web3ProviderEngine = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
const web3Wrapper = new Web3Wrapper(provider);
export { provider, web3Wrapper };