protocol/contracts/exchange/test/wrapper_unit_tests.ts
Xianny f0d7d10fe7
update abi-gen with new method interfaces (#2325)
* update abi-gen with new method interfaces

* wip: get all packages to build

* wip: get all packages to build

* Fix two contract wrapper calls

* Export necessary types part of the contract wrapper public interfaces

* Revive and fix wrapper_unit_tests

* Remove duplicate type

* Fix lib_exchange_rich_error_decoder tests

* Fix remaining test failures in contracts-* packages

* Prettier fixes

* remove transactionHelper

* lint and update changelogs

* Fix prettier

* Revert changes to reference docs

* Add back changelog already published and add revert changelog entry

* Add missing CHANGELOG entries

* Add missing comma

* Update mesh-rpc-client dep

* Update Mesh RPC logic in @0x/orderbook to v6.0.1-beta

* Align package versions
2019-11-14 11:22:29 -05:00

1197 lines
63 KiB
TypeScript

import { ContractTxFunctionObj } from '@0x/base-contract';
import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils';
import { ReferenceFunctions as UtilReferenceFunctions } from '@0x/contracts-utils';
import { ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
import { FillResults, Order } from '@0x/types';
import { AnyRevertError, BigNumber, SafeMathRevertErrors, StringRevertError } from '@0x/utils';
import { LogEntry, LogWithDecodedArgs } from 'ethereum-types';
import * as ethjs from 'ethereumjs-util';
import * as _ from 'lodash';
import { artifacts } from './artifacts';
import {
TestWrapperFunctionsCancelOrderCalledEventArgs as CancelOrderCalledEventArgs,
TestWrapperFunctionsContract,
TestWrapperFunctionsFillOrderCalledEventArgs as FillOrderCalledEventArgs,
} from './wrappers';
blockchainTests('Exchange wrapper functions unit tests.', env => {
const CHAIN_ID = 0x74657374;
const { ONE_ETHER, MAX_UINT256 } = constants;
const { addFillResults, getPartialAmountFloor } = LibReferenceFunctions;
const { safeSub } = UtilReferenceFunctions;
const protocolFeeMultiplier = new BigNumber(150000);
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomAssetData = () => hexRandom(34);
const randomAmount = (maxAmount: BigNumber = ONE_ETHER) => maxAmount.times(_.random(0, 100, true).toFixed(12));
const randomTimestamp = () => new BigNumber(Math.floor(_.now() / 1000) + _.random(0, 34560));
const randomSalt = () => new BigNumber(hexRandom(constants.WORD_LENGTH).substr(2), 16);
const ALWAYS_FAILING_SALT = constants.MAX_UINT256;
const ALWAYS_FAILING_SALT_REVERT_ERROR = new StringRevertError('ALWAYS_FAILING_SALT');
const EMPTY_FILL_RESULTS = {
makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
};
let testContract: TestWrapperFunctionsContract;
let owner: string;
let senderAddress: string;
before(async () => {
[owner, senderAddress] = await env.getAccountAddressesAsync();
testContract = await TestWrapperFunctionsContract.deployFrom0xArtifactAsync(
artifacts.TestWrapperFunctions,
env.provider,
{
...env.txDefaults,
from: owner,
},
{},
);
// Set the protocol fee multiplier.
await testContract.setProtocolFeeMultiplier(protocolFeeMultiplier).awaitTransactionSuccessAsync({
from: owner,
});
});
function randomOrder(fields?: Partial<Order>): Order {
return {
makerAddress: randomAddress(),
takerAddress: randomAddress(),
feeRecipientAddress: randomAddress(),
senderAddress: randomAddress(),
takerAssetAmount: randomAmount(),
makerAssetAmount: randomAmount(),
makerFee: randomAmount(),
takerFee: randomAmount(),
expirationTimeSeconds: randomTimestamp(),
salt: randomSalt(),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
exchangeAddress: constants.NULL_ADDRESS,
chainId: CHAIN_ID,
...(fields || {}),
};
}
// Computes the expected (fake) fill results from `TestWrapperFunctions` `_fillOrder` implementation.
function getExpectedFillResults(order: Order, signature: string): FillResults {
if (order.salt === ALWAYS_FAILING_SALT) {
return EMPTY_FILL_RESULTS;
}
return {
makerAssetFilledAmount: order.makerAssetAmount,
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee,
protocolFeePaid: protocolFeeMultiplier,
};
}
// Creates a deterministic order signature, even though no signature validation
// actually occurs in the test contract.
function createOrderSignature(order: Order): string {
return ethjs.bufferToHex(ethjs.sha3(ethjs.toBuffer(orderHashUtils.getOrderHashHex(order))));
}
// Asserts that `_fillOrder()` was called in the same order and with the same
// arguments as given by examining receipt logs.
function assertFillOrderCallsFromLogs(logs: LogEntry[], calls: Array<[Order, BigNumber, string]>): void {
expect(logs.length).to.eq(calls.length);
for (const i of _.times(calls.length)) {
const log = (logs[i] as any) as LogWithDecodedArgs<FillOrderCalledEventArgs>;
const [expectedOrder, expectedTakerAssetFillAmount, expectedSignature] = calls[i];
expect(log.event).to.eq('FillOrderCalled');
assertSameOrderFromEvent(log.args.order as any, expectedOrder);
expect(log.args.takerAssetFillAmount).to.bignumber.eq(expectedTakerAssetFillAmount);
expect(log.args.signature).to.eq(expectedSignature);
}
}
function assertSameOrderFromEvent(actual: any[], expected: Order): void {
expect(actual.length === 14);
expect(actual[0].toLowerCase()).to.be.eq(expected.makerAddress);
expect(actual[1].toLowerCase()).to.be.eq(expected.takerAddress);
expect(actual[2].toLowerCase()).to.be.eq(expected.feeRecipientAddress);
expect(actual[3].toLowerCase()).to.be.eq(expected.senderAddress);
expect(actual[4]).to.be.bignumber.eq(expected.makerAssetAmount);
expect(actual[5]).to.be.bignumber.eq(expected.takerAssetAmount);
expect(actual[6]).to.be.bignumber.eq(expected.makerFee);
expect(actual[7]).to.be.bignumber.eq(expected.takerFee);
expect(actual[8]).to.be.bignumber.eq(expected.expirationTimeSeconds);
expect(actual[9]).to.be.bignumber.eq(expected.salt);
expect(actual[10]).to.be.eq(expected.makerAssetData);
expect(actual[11]).to.be.eq(expected.takerAssetData);
expect(actual[12]).to.be.eq(expected.makerFeeAssetData);
expect(actual[13]).to.be.eq(expected.takerFeeAssetData);
}
describe('fillOrKillOrder', () => {
it('works if the order is filled by exactly `takerAssetFillAmount`', async () => {
const fillAmount = randomAmount();
const order = randomOrder({
// `_fillOrder()` is overridden to always return `order.takerAssetAmount` as
// the `takerAssetFilledAmount`.
takerAssetAmount: fillAmount,
});
const signature = createOrderSignature(order);
const expectedResult = getExpectedFillResults(order, signature);
const expectedCalls = [[order, fillAmount, signature]];
const contractFn = testContract.fillOrKillOrder(order, fillAmount, signature);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('reverts if the order is filled by less than `takerAssetFillAmount`', async () => {
const fillAmount = randomAmount();
const order = randomOrder({
// `_fillOrder()` is overridden to always return `order.takerAssetAmount` as
// the `takerAssetFilledAmount`.
takerAssetAmount: fillAmount.minus(1),
});
const signature = createOrderSignature(order);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteFillOrder,
fillAmount,
fillAmount.minus(1),
);
const tx = testContract.fillOrKillOrder(order, fillAmount, signature).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if the order is filled by greater than `takerAssetFillAmount`', async () => {
const fillAmount = randomAmount();
const order = randomOrder({
// `_fillOrder()` is overridden to always return `order.takerAssetAmount` as
// the `takerAssetFilledAmount`.
takerAssetAmount: fillAmount.plus(1),
});
const signature = createOrderSignature(order);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteFillOrder,
fillAmount,
fillAmount.plus(1),
);
const tx = testContract.fillOrKillOrder(order, fillAmount, signature).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if `_fillOrder()` reverts', async () => {
const fillAmount = randomAmount();
const order = randomOrder({
salt: ALWAYS_FAILING_SALT,
});
const signature = createOrderSignature(order);
const expectedError = ALWAYS_FAILING_SALT_REVERT_ERROR;
const tx = testContract.fillOrKillOrder(order, fillAmount, signature).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
describe('fillOrderNoThrow', () => {
it('calls `fillOrder()` and returns its result', async () => {
const fillAmount = randomAmount();
const order = randomOrder();
const signature = createOrderSignature(order);
const expectedResult = getExpectedFillResults(order, signature);
const expectedCalls = [[order, fillAmount, signature]];
const contractFn = testContract.fillOrderNoThrow(order, fillAmount, signature);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('does not revert if `fillOrder()` reverts', async () => {
const fillAmount = randomAmount();
const order = randomOrder({
salt: ALWAYS_FAILING_SALT,
});
const signature = createOrderSignature(order);
const expectedResult = EMPTY_FILL_RESULTS;
const contractFn = testContract.fillOrderNoThrow(order, fillAmount, signature);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, []);
});
});
describe('batchFillOrders', () => {
it('works with no fills', async () => {
const contractFn = testContract.batchFillOrders([], [], []);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq([]);
assertFillOrderCallsFromLogs(receipt.logs, []);
});
it('works with one fill', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with many fills', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount.dividedToIntegerBy(COUNT));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('reverts if there are more orders than fill amounts', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT - 1, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract.batchFillOrders(orders, fillAmounts, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if there are more orders than signatures', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT - 1, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract.batchFillOrders(orders, fillAmounts, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
describe('batchFillOrKillOrders', () => {
it('works with no fills', async () => {
const contractFn = testContract.batchFillOrKillOrders([], [], []);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq([]);
assertFillOrderCallsFromLogs(receipt.logs, []);
});
it('works with one fill', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrKillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with many fills', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrKillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrKillOrders(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('reverts if any fill sells less than its takerAssetFillAmount', async () => {
const COUNT = 8;
const FAILING_ORDER_INDEX = 6;
const orders = _.times(COUNT, () => randomOrder());
const failingOrder = orders[FAILING_ORDER_INDEX];
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const failingAmount = fillAmounts[FAILING_ORDER_INDEX];
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
// `_fillOrder()` is overridden to always return `order.takerAssetAmount` as
// the `takerAssetFilledAmount`.
failingOrder.takerAssetAmount = failingOrder.takerAssetAmount.minus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteFillOrder,
failingAmount,
failingAmount.minus(1),
);
const tx = testContract
.batchFillOrKillOrders(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if any fill sells more than its takerAssetFillAmount', async () => {
const COUNT = 8;
const FAILING_ORDER_INDEX = 6;
const orders = _.times(COUNT, () => randomOrder());
const failingOrder = orders[FAILING_ORDER_INDEX];
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const failingAmount = fillAmounts[FAILING_ORDER_INDEX];
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
// `_fillOrder()` is overridden to always return `order.takerAssetAmount` as
// the `takerAssetFilledAmount`.
failingOrder.takerAssetAmount = failingOrder.takerAssetAmount.plus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteFillOrder,
failingAmount,
failingAmount.plus(1),
);
const tx = testContract
.batchFillOrKillOrders(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if there are more orders than fill amounts', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT - 1, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract
.batchFillOrKillOrders(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if there are more orders than signatures', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT - 1, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract
.batchFillOrKillOrders(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
describe('batchFillOrdersNoThrow', () => {
it('works with no fills', async () => {
const contractFn = testContract.batchFillOrdersNoThrow([], [], []);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq([]);
assertFillOrderCallsFromLogs(receipt.logs, []);
});
it('works with one fill', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrdersNoThrow(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with many fills', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrdersNoThrow(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount.dividedToIntegerBy(COUNT));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const contractFn = testContract.batchFillOrdersNoThrow(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works if a fill fails', async () => {
const COUNT = 8;
const FAILING_ORDER_INDEX = 6;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const failingOrder = orders[FAILING_ORDER_INDEX];
failingOrder.salt = ALWAYS_FAILING_SALT;
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
expectedCalls.splice(FAILING_ORDER_INDEX, 1);
const contractFn = testContract.batchFillOrdersNoThrow(orders, fillAmounts, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('reverts if there are more orders than fill amounts', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT - 1, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract
.batchFillOrdersNoThrow(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts if there are more orders than signatures', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT - 1, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = testContract
.batchFillOrdersNoThrow(orders, fillAmounts, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
type ExpectedFillOrderCallArgs = [Order, BigNumber, string];
type MarketSellBuyArgs = [Order[], BigNumber, string[], ...any[]];
type MarketSellBuyContractFn = (...args: MarketSellBuyArgs) => ContractTxFunctionObj<FillResults>;
type MarketSellBuySimulator = (...args: MarketSellBuyArgs) => [FillResults, ExpectedFillOrderCallArgs[]];
describe('marketSell*', () => {
function defineCommonMarketSellOrdersTests(
getContractFn: () => MarketSellBuyContractFn,
simulator: MarketSellBuySimulator,
): void {
it('works with one order', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, takerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, takerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works with many orders', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, takerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, takerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, takerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, takerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('stops when filled == `takerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
// Skip the last order in our `takerAssetFillAmount` calculation.
const takerAssetFillAmount = _.reduce(
orders.slice(0, COUNT - 1),
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, takerAssetFillAmount, signatures);
// It should stop filling after the penultimate order.
expect(expectedCalls.length).to.eq(COUNT - 1);
const fnWithArgs = getContractFn()(orders, takerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('stops when filled > `takerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
// Because `TestWrapperFunctions` always fills `takerAssetAmount`
// setting the first order's `takerAssetAmount` to larger than
// `takerAssetFillAmount` will cause an overfill.
orders[0].takerAssetAmount = takerAssetFillAmount.plus(1);
const [expectedResult, expectedCalls] = simulator(orders, takerAssetFillAmount, signatures);
// It should stop filling after first order.
expect(expectedCalls.length).to.eq(1);
const fnWithArgs = getContractFn()(orders, takerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('reverts when an overflow occurs when summing fill results', async () => {
const COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
orders[1].takerAssetAmount = MAX_UINT256;
const takerAssetFillAmount = MAX_UINT256;
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow,
orders[0].takerAssetAmount,
orders[1].takerAssetAmount,
);
const tx = getContractFn()(orders, takerAssetFillAmount, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('returns empty fill results with no orders', async () => {
const [expectedResult, expectedCalls] = simulator([], constants.ZERO_AMOUNT, []);
expect(expectedCalls.length).to.eq(0);
const fnWithArgs = getContractFn()([], constants.ZERO_AMOUNT, []);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
}
function simulateMarketSellOrders(
orders: Order[],
takerAssetFillAmount: BigNumber,
signatures: string[],
): [FillResults, ExpectedFillOrderCallArgs[]] {
const fillOrderCalls = [];
let fillResults = _.cloneDeep(EMPTY_FILL_RESULTS);
for (const [order, signature] of _.zip(orders, signatures) as [[Order, string]]) {
const remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, fillResults.takerAssetFilledAmount);
if (order.salt !== ALWAYS_FAILING_SALT) {
fillOrderCalls.push([order, remainingTakerAssetFillAmount, signature]);
}
fillResults = addFillResults(fillResults, getExpectedFillResults(order, signature));
if (fillResults.takerAssetFilledAmount.gte(takerAssetFillAmount)) {
break;
}
}
return [fillResults, fillOrderCalls as any];
}
describe('marketSellOrdersNoThrow', () => {
defineCommonMarketSellOrdersTests(
() => testContract.marketSellOrdersNoThrow.bind(testContract),
simulateMarketSellOrders,
);
it('works when any fills revert', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulateMarketSellOrders(
orders,
takerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(COUNT - BAD_ORDERS_COUNT);
const contractFn = testContract.marketSellOrdersNoThrow(orders, takerAssetFillAmount, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works when all fills revert', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder({ salt: ALWAYS_FAILING_SALT }));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulateMarketSellOrders(
orders,
takerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(0);
const contractFn = testContract.marketSellOrdersNoThrow(orders, takerAssetFillAmount, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
});
describe('marketSellOrdersFillOrKill', () => {
defineCommonMarketSellOrdersTests(
() => testContract.marketSellOrdersNoThrow.bind(testContract),
simulateMarketSellOrders,
);
it('reverts when filled < `takerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
).plus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteMarketSellOrders,
takerAssetFillAmount,
takerAssetFillAmount.minus(1),
);
const tx = testContract
.marketSellOrdersFillOrKill(orders, takerAssetFillAmount, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('works when fills fail but can still sell `takerAssetFillAmount`', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
).minus(_.reduce(badOrders, (total, o) => o.takerAssetAmount.plus(total), constants.ZERO_AMOUNT));
const [expectedResult, expectedCalls] = simulateMarketSellOrders(
orders,
takerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(COUNT - BAD_ORDERS_COUNT);
const contractFn = testContract.marketSellOrdersFillOrKill(orders, takerAssetFillAmount, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('reverts when a failed fill results in selling less than `takerAssetFillAmount`', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const badOrdersAmount = _.reduce(
badOrders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const takerAssetFillAmount = _.reduce(
orders,
(total, o) => o.takerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
)
.minus(badOrdersAmount)
.plus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteMarketSellOrders,
takerAssetFillAmount,
takerAssetFillAmount.minus(1),
);
const tx = testContract
.marketSellOrdersFillOrKill(orders, takerAssetFillAmount, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
});
describe('marketBuy*', () => {
function defineCommonMarketBuyOrdersTests(
getContractFn: () => MarketSellBuyContractFn,
simulator: MarketSellBuySimulator,
): void {
it('works with one order', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, makerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works with many orders', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, makerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, makerAssetFillAmount, signatures);
expect(expectedCalls.length).to.eq(COUNT);
const fnWithArgs = getContractFn()(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('stops when filled == `makerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
// Skip the last order in our `makerAssetFillAmount` calculation.
const makerAssetFillAmount = _.reduce(
orders.slice(0, COUNT - 1),
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulator(orders, makerAssetFillAmount, signatures);
// It should stop filling after the penultimate order.
expect(expectedCalls.length).to.eq(COUNT - 1);
const fnWithArgs = getContractFn()(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('stops when filled > `makerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
// Because `TestWrapperFunctions` always fills `makerAssetAmount`
// setting the first order's `makerAssetAmount` to larger than
// `makerAssetFillAmount` will cause an overfill.
orders[0].makerAssetAmount = makerAssetFillAmount.plus(1);
const [expectedResult, expectedCalls] = simulator(orders, makerAssetFillAmount, signatures);
// It should stop filling after first order.
expect(expectedCalls.length).to.eq(1);
const fnWithArgs = getContractFn()(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('reverts when an overflow occurs when computing `remainingTakerAssetFillAmount`', async () => {
const orders = [randomOrder({ takerAssetAmount: MAX_UINT256 })];
const signatures = _.times(orders.length, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = new BigNumber(2);
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow,
orders[0].takerAssetAmount,
makerAssetFillAmount,
);
const tx = getContractFn()(orders, makerAssetFillAmount, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it("reverts when an order's `makerAssetAmount` is zero", async () => {
const orders = [randomOrder({ makerAssetAmount: constants.ZERO_AMOUNT })];
const signatures = _.times(orders.length, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = ONE_ETHER;
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.DivisionByZero,
orders[0].takerAssetAmount.times(makerAssetFillAmount),
orders[0].makerAssetAmount,
);
const tx = getContractFn()(orders, makerAssetFillAmount, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('reverts when an overflow occurs when summing fill results', async () => {
const orders = [
randomOrder({
takerAssetAmount: new BigNumber(1),
makerAssetAmount: new BigNumber(1),
}),
randomOrder({
takerAssetAmount: new BigNumber(1),
makerAssetAmount: MAX_UINT256,
}),
];
const signatures = _.times(orders.length, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = new BigNumber(2);
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow,
orders[0].makerAssetAmount,
orders[1].makerAssetAmount,
);
const tx = getContractFn()(orders, makerAssetFillAmount, signatures).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('returns empty fill results with no orders', async () => {
const [expectedResult, expectedCalls] = simulator([], constants.ZERO_AMOUNT, []);
expect(expectedCalls.length).to.eq(0);
const fnWithArgs = getContractFn()([], constants.ZERO_AMOUNT, []);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
}
function simulateMarketBuyOrdersNoThrow(
orders: Order[],
makerAssetFillAmount: BigNumber,
signatures: string[],
): [FillResults, ExpectedFillOrderCallArgs[]] {
const fillOrderCalls = [];
let fillResults = _.cloneDeep(EMPTY_FILL_RESULTS);
for (const [order, signature] of _.zip(orders, signatures) as [[Order, string]]) {
const remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, fillResults.makerAssetFilledAmount);
const remainingTakerAssetFillAmount = getPartialAmountFloor(
order.takerAssetAmount,
order.makerAssetAmount,
remainingMakerAssetFillAmount,
);
if (order.salt !== ALWAYS_FAILING_SALT) {
fillOrderCalls.push([order, remainingTakerAssetFillAmount, signature]);
}
fillResults = addFillResults(fillResults, getExpectedFillResults(order, signature));
if (fillResults.makerAssetFilledAmount.gte(makerAssetFillAmount)) {
break;
}
}
return [fillResults, fillOrderCalls as any];
}
describe('marketBuyOrdersNoThrow', () => {
defineCommonMarketBuyOrdersTests(
() => testContract.marketBuyOrdersNoThrow.bind(testContract),
simulateMarketBuyOrdersNoThrow,
);
it('works when any fills revert', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulateMarketBuyOrdersNoThrow(
orders,
makerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(COUNT - BAD_ORDERS_COUNT);
const contractFn = testContract.marketBuyOrdersNoThrow(orders, makerAssetFillAmount, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('works when all fills revert', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder({ salt: ALWAYS_FAILING_SALT }));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const [expectedResult, expectedCalls] = simulateMarketBuyOrdersNoThrow(
orders,
makerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(0);
const contractFn = testContract.marketBuyOrdersNoThrow(orders, makerAssetFillAmount, signatures);
const actualResult = await contractFn.callAsync();
const receipt = await contractFn.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
});
describe('marketBuyOrdersFillOrKill', () => {
defineCommonMarketBuyOrdersTests(
() => testContract.marketBuyOrdersFillOrKill.bind(testContract),
simulateMarketBuyOrdersNoThrow,
);
it('reverts when filled < `makerAssetFillAmount`', async () => {
const COUNT = 4;
const orders = _.times(COUNT, () => randomOrder());
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
).plus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteMarketBuyOrders,
makerAssetFillAmount,
makerAssetFillAmount.minus(1),
);
const tx = testContract
.marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
it('works when fills fail but can still buy `makerAssetFillAmount`', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
).minus(_.reduce(badOrders, (total, o) => o.makerAssetAmount.plus(total), constants.ZERO_AMOUNT));
const [expectedResult, expectedCalls] = simulateMarketBuyOrdersNoThrow(
orders,
makerAssetFillAmount,
signatures,
);
expect(expectedCalls.length).to.eq(COUNT - BAD_ORDERS_COUNT);
const fnWithArgs = testContract.marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, signatures);
const actualResult = await fnWithArgs.callAsync();
const receipt = await fnWithArgs.awaitTransactionSuccessAsync();
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls);
});
it('reverts when a failed fill results in buying less than `makerAssetFillAmount`', async () => {
const COUNT = 4;
const BAD_ORDERS_COUNT = 2;
const orders = _.times(COUNT, () => randomOrder());
const badOrders = _.sampleSize(orders, BAD_ORDERS_COUNT);
for (const order of badOrders) {
order.salt = ALWAYS_FAILING_SALT;
}
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const badOrdersAmount = _.reduce(
badOrders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
);
const makerAssetFillAmount = _.reduce(
orders,
(total, o) => o.makerAssetAmount.plus(total),
constants.ZERO_AMOUNT,
)
.minus(badOrdersAmount)
.plus(1);
const expectedError = new ExchangeRevertErrors.IncompleteFillError(
ExchangeRevertErrors.IncompleteFillErrorCode.IncompleteMarketBuyOrders,
makerAssetFillAmount,
makerAssetFillAmount.minus(1),
);
const tx = testContract
.marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, signatures)
.awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
});
describe('batchCancelOrders', () => {
// Asserts that `_cancelOrder()` was called in the same order and with the same
// arguments as given by examining receipt logs.
function assertCancelOrderCallsFromLogs(logs: LogEntry[], calls: Order[]): void {
expect(logs.length).to.eq(calls.length);
for (const i of _.times(calls.length)) {
const log = (logs[i] as any) as LogWithDecodedArgs<CancelOrderCalledEventArgs>;
const expectedOrder = calls[i];
expect(log.event).to.eq('CancelOrderCalled');
assertSameOrderFromEvent(log.args.order as any, expectedOrder);
}
}
it('works with no orders', async () => {
const { logs } = await testContract.batchCancelOrders([]).awaitTransactionSuccessAsync();
assertCancelOrderCallsFromLogs(logs, []);
});
it('works with many orders', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder({ makerAddress: senderAddress }));
const { logs } = await testContract.batchCancelOrders(orders).awaitTransactionSuccessAsync();
assertCancelOrderCallsFromLogs(logs, orders);
});
it('works with duplicate orders', async () => {
const UNIQUE_ORDERS = 2;
const COUNT = 6;
const uniqueOrders = _.times(UNIQUE_ORDERS, () => randomOrder({ makerAddress: senderAddress }));
const orders = _.shuffle(_.flatten(_.times(COUNT / UNIQUE_ORDERS, () => uniqueOrders)));
const { logs } = await testContract.batchCancelOrders(orders).awaitTransactionSuccessAsync();
assertCancelOrderCallsFromLogs(logs, orders);
});
it('reverts if one `_cancelOrder()` reverts', async () => {
const COUNT = 8;
const FAILING_ORDER_INDEX = 4;
const orders = _.times(COUNT, () => randomOrder({ makerAddress: senderAddress }));
const failingOrder = orders[FAILING_ORDER_INDEX];
failingOrder.salt = ALWAYS_FAILING_SALT;
const expectedError = ALWAYS_FAILING_SALT_REVERT_ERROR;
const tx = testContract.batchCancelOrders(orders).awaitTransactionSuccessAsync();
return expect(tx).to.revertWith(expectedError);
});
});
});
// tslint:disable-next-line: max-file-line-count