609 lines
27 KiB
TypeScript
609 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, ExchangeRevertErrors } from '@0x/utils';
|
|
import * as _ from 'lodash';
|
|
|
|
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
|