protocol/contracts/exchange/test/isolated_fill_order.ts
F. Eugene Aumson f11d8a5bd8
@0x/order-utils refactors for v3: orderParsingUtils, signatureUtils, orderHashUtils, RevertErrors, transactionHashUtils (#2321)
* move orderParsingUtils from order-utils to connect

* Remove many functions from signatureUtils

Removed from the exported object, that is.  All of them are used in
other existing code, so they were all moved to be as local to their
usage as possible.

* remove orderHashUtils.isValidOrderHash()

* Move all *RevertErrors from order-utils...

...into their respective @0x/contracts- packages.

* Refactor @0x/order-utils' orderHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed getOrderHash()
method in DevUtils.

* Move all *RevertErrors from @0x/utils...

...into their respective @0x/contracts- packages.

* rm transactionHashUtils.isValidTransactionHash()

* DevUtils.sol: Fail yarn test if too big to deploy

* Refactor @0x/order-utils transactionHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed
getTransactionHash() method in DevUtils.

* Consolidate `Removed export...` CHANGELOG entries

* Rm EthBalanceChecker from devutils wrapper exports

* Stop importing from '.' or '.../src'

* fix builds

* fix prettier; dangling promise

* increase max bundle size
2019-11-14 17:14:24 -05:00

611 lines
27 KiB
TypeScript

import { LibMathRevertErrors, ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
import { SafeMathRevertErrors } from '@0x/contracts-utils';
import { FillResults, OrderInfo, OrderStatus, SignatureType } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import ExchangeRevertErrors = require('../src/revert_errors');
import {
AssetBalances,
createBadAssetData,
createBadSignature,
createGoodAssetData,
createGoodSignature,
IsolatedExchangeWrapper,
Order,
} from './utils/isolated_exchange_wrapper';
blockchainTests('Isolated fillOrder() tests', env => {
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const getCurrentTime = () => Math.floor(_.now() / 1000);
const { ZERO_AMOUNT, ONE_ETHER, MAX_UINT256_ROOT } = constants;
const ONE_DAY = 60 * 60 * 24;
const TOMORROW = getCurrentTime() + ONE_DAY;
const DEFAULT_ORDER: Order = {
senderAddress: constants.NULL_ADDRESS,
makerAddress: randomAddress(),
takerAddress: constants.NULL_ADDRESS,
feeRecipientAddress: randomAddress(),
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(2),
makerFee: ONE_ETHER.times(0.0015),
takerFee: ONE_ETHER.times(0.0025),
salt: ZERO_AMOUNT,
expirationTimeSeconds: new BigNumber(TOMORROW),
makerAssetData: createGoodAssetData(),
takerAssetData: createGoodAssetData(),
makerFeeAssetData: createGoodAssetData(),
takerFeeAssetData: createGoodAssetData(),
exchangeAddress: constants.NULL_ADDRESS,
chainId: 1,
};
const DEFAULT_GAS_PRICE = new BigNumber(20000);
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(15000);
let takerAddress: string;
let notTakerAddress: string;
let exchange: IsolatedExchangeWrapper;
let nextSaltValue = 1;
before(async () => {
[takerAddress, notTakerAddress] = await env.getAccountAddressesAsync();
exchange = await IsolatedExchangeWrapper.deployAsync(env.web3Wrapper, {
...env.txDefaults,
from: takerAddress,
});
});
function createOrder(details: Partial<Order> = {}): Order {
return {
...DEFAULT_ORDER,
salt: new BigNumber(nextSaltValue++),
...details,
};
}
interface FillOrderAndAssertResultsResults {
fillResults: FillResults;
orderInfo: OrderInfo;
}
async function fillOrderAndAssertResultsAsync(
order: Order,
takerAssetFillAmount: BigNumber,
signature?: string,
): Promise<FillOrderAndAssertResultsResults> {
const orderInfo = await exchange.getOrderInfoAsync(order);
const efr = calculateExpectedFillResults(
order,
orderInfo,
takerAssetFillAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
);
const eoi = calculateExpectedOrderInfo(order, orderInfo, efr);
const efb = calculateExpectedFillBalances(order, efr);
// Fill the order.
const fillResults = await exchange.fillOrderAsync(order, takerAssetFillAmount, signature);
const newOrderInfo = await exchange.getOrderInfoAsync(order);
// Check returned fillResults.
expect(fillResults.makerAssetFilledAmount).to.bignumber.eq(efr.makerAssetFilledAmount);
expect(fillResults.takerAssetFilledAmount).to.bignumber.eq(efr.takerAssetFilledAmount);
expect(fillResults.makerFeePaid).to.bignumber.eq(efr.makerFeePaid);
expect(fillResults.takerFeePaid).to.bignumber.eq(efr.takerFeePaid);
// Check balances.
for (const assetData of Object.keys(efb)) {
for (const address of Object.keys(efb[assetData])) {
expect(
exchange.getBalanceChange(assetData, address),
`checking balance of assetData: ${assetData}, address: ${address}`,
).to.bignumber.eq(efb[assetData][address]);
}
}
// Check order info.
expect(newOrderInfo.orderStatus).to.eq(eoi.orderStatus);
expect(newOrderInfo.orderTakerAssetFilledAmount).to.bignumber.eq(eoi.orderTakerAssetFilledAmount);
// Check that there wasn't an overfill.
expect(newOrderInfo.orderTakerAssetFilledAmount.lte(order.takerAssetAmount), 'checking for overfill').to.be.ok(
'',
);
return {
fillResults,
orderInfo: newOrderInfo,
};
}
function calculateExpectedFillResults(
order: Order,
orderInfo: OrderInfo,
takerAssetFillAmount: BigNumber,
protocolFeeMultiplier: BigNumber,
gasPrice: BigNumber,
): FillResults {
const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount);
return LibReferenceFunctions.calculateFillResults(
order,
BigNumber.min(takerAssetFillAmount, remainingTakerAssetAmount),
protocolFeeMultiplier,
gasPrice,
);
}
function calculateExpectedOrderInfo(order: Order, orderInfo: OrderInfo, fillResults: FillResults): OrderInfo {
const orderTakerAssetFilledAmount = orderInfo.orderTakerAssetFilledAmount.plus(
fillResults.takerAssetFilledAmount,
);
const orderStatus = orderTakerAssetFilledAmount.gte(order.takerAssetAmount)
? OrderStatus.FullyFilled
: OrderStatus.Fillable;
return {
orderHash: exchange.getOrderHash(order),
orderStatus,
orderTakerAssetFilledAmount,
};
}
function calculateExpectedFillBalances(order: Order, fillResults: FillResults): AssetBalances {
const balances: AssetBalances = {};
const addBalance = (assetData: string, address: string, amount: BigNumber) => {
balances[assetData] = balances[assetData] || {};
const balance = balances[assetData][address] || ZERO_AMOUNT;
balances[assetData][address] = balance.plus(amount);
};
addBalance(order.makerAssetData, order.makerAddress, fillResults.makerAssetFilledAmount.negated());
addBalance(order.makerAssetData, takerAddress, fillResults.makerAssetFilledAmount);
addBalance(order.takerAssetData, order.makerAddress, fillResults.takerAssetFilledAmount);
addBalance(order.takerAssetData, takerAddress, fillResults.takerAssetFilledAmount.negated());
addBalance(order.makerFeeAssetData, order.makerAddress, fillResults.makerFeePaid.negated());
addBalance(order.makerFeeAssetData, order.feeRecipientAddress, fillResults.makerFeePaid);
addBalance(order.takerFeeAssetData, takerAddress, fillResults.takerFeePaid.negated());
addBalance(order.takerFeeAssetData, order.feeRecipientAddress, fillResults.takerFeePaid);
return balances;
}
function splitAmount(total: BigNumber, n: number = 2): BigNumber[] {
const splitSize = total.dividedToIntegerBy(n);
const splits = _.times(n - 1, () => splitSize);
splits.push(total.minus(splitSize.times(n - 1)));
return splits;
}
describe('full fills', () => {
it('can fully fill an order', async () => {
const order = createOrder();
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.FullyFilled);
});
it("can't overfill an order", async () => {
const order = createOrder();
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, order.takerAssetAmount.times(1.01));
expect(orderInfo.orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('no fees', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('only maker fees', async () => {
const order = createOrder({
takerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('only taker fees', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.FullyFilled);
});
});
describe('partial fills', () => {
const takerAssetFillAmount = DEFAULT_ORDER.takerAssetAmount.dividedToIntegerBy(2);
it('can partially fill an order', async () => {
const order = createOrder();
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, takerAssetFillAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.Fillable);
});
it('no fees', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, takerAssetFillAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.Fillable);
});
it('only maker fees', async () => {
const order = createOrder({
takerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, takerAssetFillAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.Fillable);
});
it('only taker fees', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
});
const { orderInfo } = await fillOrderAndAssertResultsAsync(order, takerAssetFillAmount);
expect(orderInfo.orderStatus).to.eq(OrderStatus.Fillable);
});
});
describe('multiple fills', () => {
it('can fully fill an order in two fills', async () => {
const order = createOrder();
const fillAmounts = splitAmount(order.takerAssetAmount);
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('can partially fill an order in two fills', async () => {
const order = createOrder();
const fillAmounts = splitAmount(order.takerAssetAmount.dividedToIntegerBy(2));
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.Fillable);
});
it("can't overfill an order in two fills", async () => {
const order = createOrder();
const fillAmounts = splitAmount(order.takerAssetAmount);
fillAmounts[0] = fillAmounts[0].times(1.01);
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('can fully fill an order with no fees in two fills', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
});
const fillAmounts = splitAmount(order.takerAssetAmount);
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('can fully fill an order with only maker fees in two fills', async () => {
const order = createOrder({
takerFee: ZERO_AMOUNT,
});
const fillAmounts = splitAmount(order.takerAssetAmount);
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.FullyFilled);
});
it('can fully fill an order with only taker fees in two fills', async () => {
const order = createOrder({
makerFee: ZERO_AMOUNT,
});
const fillAmounts = splitAmount(order.takerAssetAmount);
const orderInfos = [
(await fillOrderAndAssertResultsAsync(order, fillAmounts[0])).orderInfo,
(await fillOrderAndAssertResultsAsync(order, fillAmounts[1])).orderInfo,
];
expect(orderInfos[0].orderStatus).to.eq(OrderStatus.Fillable);
expect(orderInfos[1].orderStatus).to.eq(OrderStatus.FullyFilled);
});
});
describe('bad fills', () => {
it("can't fill an order with zero takerAssetAmount", async () => {
const order = createOrder({
takerAssetAmount: ZERO_AMOUNT,
});
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.InvalidTakerAssetAmount,
);
return expect(exchange.fillOrderAsync(order, ONE_ETHER)).to.revertWith(expectedError);
});
it("can't fill an order with zero makerAssetAmount", async () => {
const order = createOrder({
makerAssetAmount: ZERO_AMOUNT,
});
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.InvalidMakerAssetAmount,
);
return expect(exchange.fillOrderAsync(order, ONE_ETHER)).to.revertWith(expectedError);
});
it("can't fill an order that is fully filled", async () => {
const order = createOrder();
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.FullyFilled,
);
await exchange.fillOrderAsync(order, order.takerAssetAmount);
return expect(exchange.fillOrderAsync(order, 1)).to.revertWith(expectedError);
});
it("can't fill an order that is expired", async () => {
const order = createOrder({
expirationTimeSeconds: new BigNumber(getCurrentTime() - 60),
});
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.Expired,
);
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(expectedError);
});
it("can't fill an order that is cancelled by `cancelOrder()`", async () => {
const order = createOrder({
makerAddress: notTakerAddress,
});
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.Cancelled,
);
await exchange.cancelOrderAsync(order, { from: notTakerAddress });
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(expectedError);
});
it("can't fill an order that is cancelled by `cancelOrdersUpTo()`", async () => {
const order = createOrder({
makerAddress: notTakerAddress,
});
const expectedError = new ExchangeRevertErrors.OrderStatusError(
exchange.getOrderHash(order),
OrderStatus.Cancelled,
);
await exchange.cancelOrdersUpToAsync(order.salt, { from: notTakerAddress });
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(expectedError);
});
it("can't fill an order if taker is not `takerAddress`", async () => {
const order = createOrder({
takerAddress: randomAddress(),
});
const expectedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidTaker,
exchange.getOrderHash(order),
takerAddress,
);
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(expectedError);
});
it("can't fill an order if sender is not `senderAddress`", async () => {
const order = createOrder({
senderAddress: randomAddress(),
});
const expectedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidSender,
exchange.getOrderHash(order),
takerAddress,
);
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(expectedError);
});
it("can't fill an order with a taker amount that results in a maker asset rounding error", async () => {
const order = createOrder({
makerAssetAmount: new BigNumber(100),
takerAssetAmount: ONE_ETHER,
});
const takerAssetFillAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const expectedError = new LibMathRevertErrors.RoundingError(
takerAssetFillAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order with a taker amount that results in a maker fee rounding error", async () => {
const order = createOrder({
makerAssetAmount: ONE_ETHER.times(2),
takerAssetAmount: ONE_ETHER,
makerFee: new BigNumber(100),
});
const takerAssetFillAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const expectedError = new LibMathRevertErrors.RoundingError(
takerAssetFillAmount,
order.takerAssetAmount,
order.makerFee,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order with a taker amount that results in a taker fee rounding error", async () => {
const order = createOrder({
makerAssetAmount: ONE_ETHER.times(2),
takerAssetAmount: ONE_ETHER,
takerFee: new BigNumber(100),
});
const takerAssetFillAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const expectedError = new LibMathRevertErrors.RoundingError(
takerAssetFillAmount,
order.takerAssetAmount,
order.takerFee,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order that results in a `makerAssetFilledAmount` overflow.", async () => {
// All values need to be large to ensure we don't trigger a Rounding.
const order = createOrder({
makerAssetAmount: MAX_UINT256_ROOT.times(2),
takerAssetAmount: MAX_UINT256_ROOT,
});
const takerAssetFillAmount = MAX_UINT256_ROOT;
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow,
takerAssetFillAmount,
order.makerAssetAmount,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order that results in a `makerFeePaid` overflow.", async () => {
// All values need to be large to ensure we don't trigger a Rounding.
const order = createOrder({
makerAssetAmount: MAX_UINT256_ROOT,
takerAssetAmount: MAX_UINT256_ROOT,
makerFee: MAX_UINT256_ROOT.times(11),
});
const takerAssetFillAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10);
const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor(
takerAssetFillAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow,
makerAssetFilledAmount,
order.makerFee,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order that results in a `takerFeePaid` overflow.", async () => {
// All values need to be large to ensure we don't trigger a Rounding.
const order = createOrder({
makerAssetAmount: MAX_UINT256_ROOT,
takerAssetAmount: MAX_UINT256_ROOT,
takerFee: MAX_UINT256_ROOT.times(11),
});
const takerAssetFillAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10);
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow,
takerAssetFillAmount,
order.takerFee,
);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmount)).to.revertWith(expectedError);
});
it("can't fill an order with a bad signature", async () => {
const order = createOrder();
const signature = createBadSignature();
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.BadOrderSignature,
exchange.getOrderHash(order),
order.makerAddress,
signature,
);
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount, signature)).to.revertWith(
expectedError,
);
});
it("can't complementary fill an order with a bad signature that is always checked", async () => {
const order = createOrder();
const takerAssetFillAmounts = splitAmount(order.takerAssetAmount);
const goodSignature = createGoodSignature(SignatureType.Wallet);
const badSignature = createBadSignature(SignatureType.Wallet);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.BadOrderSignature,
exchange.getOrderHash(order),
order.makerAddress,
badSignature,
);
await exchange.fillOrderAsync(order, takerAssetFillAmounts[0], goodSignature);
return expect(exchange.fillOrderAsync(order, takerAssetFillAmounts[1], badSignature)).to.revertWith(
expectedError,
);
});
const TRANSFER_ERROR = 'TRANSFER_FAILED';
it("can't fill an order with a maker asset that fails to transfer", async () => {
const order = createOrder({
makerAssetData: createBadAssetData(),
});
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(TRANSFER_ERROR);
});
it("can't fill an order with a taker asset that fails to transfer", async () => {
const order = createOrder({
takerAssetData: createBadAssetData(),
});
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(TRANSFER_ERROR);
});
it("can't fill an order with a maker fee asset that fails to transfer", async () => {
const order = createOrder({
makerFeeAssetData: createBadAssetData(),
});
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(TRANSFER_ERROR);
});
it("can't fill an order with a taker fee asset that fails to transfer", async () => {
const order = createOrder({
takerFeeAssetData: createBadAssetData(),
});
return expect(exchange.fillOrderAsync(order, order.takerAssetAmount)).to.revertWith(TRANSFER_ERROR);
});
});
describe('permitted fills', () => {
it('should allow takerAssetFillAmount to be zero', async () => {
const order = createOrder();
return fillOrderAndAssertResultsAsync(order, constants.ZERO_AMOUNT);
});
it('can fill an order if taker is `takerAddress`', async () => {
const order = createOrder({
takerAddress,
});
return fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
});
it('can fill an order if sender is `senderAddress`', async () => {
const order = createOrder({
senderAddress: takerAddress,
});
return fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
});
it('cannot fill an order with a bad signature that has already been partially filled', async () => {
const order = createOrder();
const takerAssetFillAmounts = splitAmount(order.takerAssetAmount);
const goodSignature = createGoodSignature(SignatureType.EthSign);
const badSignature = createBadSignature(SignatureType.EthSign);
await fillOrderAndAssertResultsAsync(order, takerAssetFillAmounts[0], goodSignature);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.BadOrderSignature,
exchange.getOrderHash(order),
order.makerAddress,
badSignature,
);
return expect(fillOrderAndAssertResultsAsync(order, takerAssetFillAmounts[1], badSignature)).to.revertWith(
expectedError,
);
});
});
});
// tslint:disable: max-file-line-count