import { blockchainTests, constants, describe, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils'; import { AnyRevertError, BigNumber } from '@0x/utils'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { LimitOrder, LimitOrderFields, OrderInfo, OrderStatus, RfqOrder, RfqOrderFields } from '../../src/orders'; import * as RevertErrors from '../../src/revert_errors'; import { IZeroExContract, IZeroExEvents } from '../../src/wrappers'; import { artifacts } from '../artifacts'; import { fullMigrateAsync } from '../utils/migration'; import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders'; import { TestMintableERC20TokenContract, TestRfqOriginRegistrationContract } from '../wrappers'; blockchainTests.resets('NativeOrdersFeature', env => { const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT } = constants; const GAS_PRICE = new BigNumber('123e9'); const PROTOCOL_FEE_MULTIPLIER = 1337e3; const SINGLE_PROTOCOL_FEE = GAS_PRICE.times(PROTOCOL_FEE_MULTIPLIER); let maker: string; let taker: string; let notMaker: string; let notTaker: string; let zeroEx: IZeroExContract; let verifyingContract: string; let makerToken: TestMintableERC20TokenContract; let takerToken: TestMintableERC20TokenContract; let wethToken: TestMintableERC20TokenContract; let testRfqOriginRegistration: TestRfqOriginRegistrationContract; before(async () => { let owner; [owner, maker, taker, notMaker, notTaker] = await env.getAccountAddressesAsync(); [makerToken, takerToken, wethToken] = await Promise.all( [...new Array(3)].map(async () => TestMintableERC20TokenContract.deployFrom0xArtifactAsync( artifacts.TestMintableERC20Token, env.provider, env.txDefaults, artifacts, ), ), ); zeroEx = await fullMigrateAsync( owner, env.provider, { ...env.txDefaults, gasPrice: GAS_PRICE }, {}, { wethAddress: wethToken.address, protocolFeeMultiplier: PROTOCOL_FEE_MULTIPLIER }, { nativeOrders: artifacts.TestNativeOrdersFeature }, ); verifyingContract = zeroEx.address; await Promise.all( [maker, notMaker].map(a => makerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: a }), ), ); await Promise.all( [taker, notTaker].map(a => takerToken.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({ from: a }), ), ); testRfqOriginRegistration = await TestRfqOriginRegistrationContract.deployFrom0xArtifactAsync( artifacts.TestRfqOriginRegistration, env.provider, env.txDefaults, artifacts, ); }); function getTestLimitOrder(fields: Partial = {}): LimitOrder { return getRandomLimitOrder({ maker, verifyingContract, takerToken: takerToken.address, makerToken: makerToken.address, taker: NULL_ADDRESS, sender: NULL_ADDRESS, ...fields, }); } function getTestRfqOrder(fields: Partial = {}): RfqOrder { return getRandomRfqOrder({ maker, verifyingContract, takerToken: takerToken.address, makerToken: makerToken.address, txOrigin: taker, ...fields, }); } async function prepareBalancesForOrderAsync(order: LimitOrder | RfqOrder, _taker: string = taker): Promise { await makerToken.mint(maker, order.makerAmount).awaitTransactionSuccessAsync(); if ('takerTokenFeeAmount' in order) { await takerToken .mint(_taker, order.takerAmount.plus(order.takerTokenFeeAmount)) .awaitTransactionSuccessAsync(); } else { await takerToken.mint(_taker, order.takerAmount).awaitTransactionSuccessAsync(); } } function assertOrderInfoEquals(actual: OrderInfo, expected: OrderInfo): void { expect(actual.status).to.eq(expected.status); expect(actual.orderHash).to.eq(expected.orderHash); expect(actual.takerTokenFilledAmount).to.bignumber.eq(expected.takerTokenFilledAmount); } function createExpiry(deltaSeconds: number = 60): BigNumber { return new BigNumber(Math.floor(Date.now() / 1000) + deltaSeconds); } describe('getProtocolFeeMultiplier()', () => { it('returns the protocol fee multiplier', async () => { const r = await zeroEx.getProtocolFeeMultiplier().callAsync(); expect(r).to.bignumber.eq(PROTOCOL_FEE_MULTIPLIER); }); }); describe('getLimitOrderHash()', () => { it('returns the correct hash', async () => { const order = getTestLimitOrder(); const hash = await zeroEx.getLimitOrderHash(order).callAsync(); expect(hash).to.eq(order.getHash()); }); }); describe('getRfqOrderHash()', () => { it('returns the correct hash', async () => { const order = getTestRfqOrder(); const hash = await zeroEx.getRfqOrderHash(order).callAsync(); expect(hash).to.eq(order.getHash()); }); }); async function fillLimitOrderAsync( order: LimitOrder, opts: Partial<{ fillAmount: BigNumber | number; taker: string; protocolFee?: BigNumber | number; }> = {}, ): Promise { const { fillAmount, taker: _taker, protocolFee } = { taker, fillAmount: order.takerAmount, ...opts, }; await prepareBalancesForOrderAsync(order, _taker); const _protocolFee = protocolFee === undefined ? SINGLE_PROTOCOL_FEE : protocolFee; return zeroEx .fillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount)) .awaitTransactionSuccessAsync({ from: _taker, value: _protocolFee }); } describe('getLimitOrderInfo()', () => { it('unfilled order', async () => { const order = getTestLimitOrder(); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Fillable, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('unfilled cancelled order', async () => { const order = getTestLimitOrder(); await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Cancelled, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('unfilled expired order', async () => { const order = getTestLimitOrder({ expiry: createExpiry(-60) }); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Expired, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('filled then expired order', async () => { const expiry = createExpiry(60); const order = getTestLimitOrder({ expiry }); // Fill the order first. await fillLimitOrderAsync(order); // Advance time to expire the order. await env.web3Wrapper.increaseTimeAsync(61); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, // Still reports filled. orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('filled order', async () => { const order = getTestLimitOrder(); // Fill the order first. await fillLimitOrderAsync(order); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('partially filled order', async () => { const order = getTestLimitOrder(); const fillAmount = order.takerAmount.minus(1); // Fill the order first. await fillLimitOrderAsync(order, { fillAmount }); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Fillable, orderHash: order.getHash(), takerTokenFilledAmount: fillAmount, }); }); it('filled then cancelled order', async () => { const order = getTestLimitOrder(); // Fill the order first. await fillLimitOrderAsync(order); await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, // Still reports filled. orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('partially filled then cancelled order', async () => { const order = getTestLimitOrder(); const fillAmount = order.takerAmount.minus(1); // Fill the order first. await fillLimitOrderAsync(order, { fillAmount }); await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getLimitOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Cancelled, orderHash: order.getHash(), takerTokenFilledAmount: fillAmount, }); }); }); async function fillRfqOrderAsync( order: RfqOrder, fillAmount: BigNumber | number = order.takerAmount, _taker: string = taker, ): Promise { await prepareBalancesForOrderAsync(order, _taker); return zeroEx .fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount)) .awaitTransactionSuccessAsync({ from: _taker }); } describe('getRfqOrderInfo()', () => { it('unfilled order', async () => { const order = getTestRfqOrder(); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Fillable, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('unfilled cancelled order', async () => { const order = getTestRfqOrder(); await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Cancelled, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('unfilled expired order', async () => { const expiry = createExpiry(-60); const order = getTestRfqOrder({ expiry }); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Expired, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); it('filled then expired order', async () => { const expiry = createExpiry(60); const order = getTestRfqOrder({ expiry }); await prepareBalancesForOrderAsync(order); const sig = await order.getSignatureWithProviderAsync(env.provider); // Fill the order first. await zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker }); // Advance time to expire the order. await env.web3Wrapper.increaseTimeAsync(61); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, // Still reports filled. orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('filled order', async () => { const order = getTestRfqOrder(); // Fill the order first. await fillRfqOrderAsync(order, order.takerAmount, taker); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('partially filled order', async () => { const order = getTestRfqOrder(); const fillAmount = order.takerAmount.minus(1); // Fill the order first. await fillRfqOrderAsync(order, fillAmount); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Fillable, orderHash: order.getHash(), takerTokenFilledAmount: fillAmount, }); }); it('filled then cancelled order', async () => { const order = getTestRfqOrder(); // Fill the order first. await fillRfqOrderAsync(order); await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Filled, // Still reports filled. orderHash: order.getHash(), takerTokenFilledAmount: order.takerAmount, }); }); it('partially filled then cancelled order', async () => { const order = getTestRfqOrder(); const fillAmount = order.takerAmount.minus(1); // Fill the order first. await fillRfqOrderAsync(order, fillAmount); await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Cancelled, orderHash: order.getHash(), takerTokenFilledAmount: fillAmount, }); }); it('invalid origin', async () => { const order = getTestRfqOrder({ txOrigin: NULL_ADDRESS }); const info = await zeroEx.getRfqOrderInfo(order).callAsync(); assertOrderInfoEquals(info, { status: OrderStatus.Invalid, orderHash: order.getHash(), takerTokenFilledAmount: ZERO_AMOUNT, }); }); }); describe('cancelLimitOrder()', async () => { it('can cancel an unfilled order', async () => { const order = getTestLimitOrder(); const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it('can cancel a fully filled order', async () => { const order = getTestLimitOrder(); await fillLimitOrderAsync(order); const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Filled); // Still reports filled. }); it('can cancel a partially filled order', async () => { const order = getTestLimitOrder(); await fillLimitOrderAsync(order, { fillAmount: order.takerAmount.minus(1) }); const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it('can cancel an expired order', async () => { const expiry = createExpiry(-60); const order = getTestLimitOrder({ expiry }); const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it('can cancel a cancelled order', async () => { const order = getTestLimitOrder(); await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it("cannot cancel someone else's order", async () => { const order = getTestLimitOrder(); const tx = zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: notMaker }); return expect(tx).to.revertWith( new RevertErrors.OnlyOrderMakerAllowed(order.getHash(), notMaker, order.maker), ); }); }); describe('cancelRfqOrder()', async () => { it('can cancel an unfilled order', async () => { const order = getTestRfqOrder(); const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it('can cancel a fully filled order', async () => { const order = getTestRfqOrder(); await fillRfqOrderAsync(order); const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Filled); // Still reports filled. }); it('can cancel a partially filled order', async () => { const order = getTestRfqOrder(); await fillRfqOrderAsync(order, order.takerAmount.minus(1)); const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); // Still reports filled. }); it('can cancel an expired order', async () => { const expiry = createExpiry(-60); const order = getTestRfqOrder({ expiry }); const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it('can cancel a cancelled order', async () => { const order = getTestRfqOrder(); await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [{ maker: order.maker, orderHash: order.getHash() }], IZeroExEvents.OrderCancelled, ); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Cancelled); }); it("cannot cancel someone else's order", async () => { const order = getTestRfqOrder(); const tx = zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: notMaker }); return expect(tx).to.revertWith( new RevertErrors.OnlyOrderMakerAllowed(order.getHash(), notMaker, order.maker), ); }); }); describe('batchCancelLimitOrders()', async () => { it('can cancel multiple orders', async () => { const orders = [...new Array(3)].map(() => getTestLimitOrder()); const receipt = await zeroEx.batchCancelLimitOrders(orders).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, orders.map(o => ({ maker: o.maker, orderHash: o.getHash() })), IZeroExEvents.OrderCancelled, ); const infos = await Promise.all(orders.map(o => zeroEx.getLimitOrderInfo(o).callAsync())); expect(infos.map(i => i.status)).to.deep.eq(infos.map(() => OrderStatus.Cancelled)); }); it("cannot cancel someone else's orders", async () => { const orders = [...new Array(3)].map(() => getTestLimitOrder()); const tx = zeroEx.batchCancelLimitOrders(orders).awaitTransactionSuccessAsync({ from: notMaker }); return expect(tx).to.revertWith( new RevertErrors.OnlyOrderMakerAllowed(orders[0].getHash(), notMaker, orders[0].maker), ); }); }); describe('batchCancelRfqOrders()', async () => { it('can cancel multiple orders', async () => { const orders = [...new Array(3)].map(() => getTestRfqOrder()); const receipt = await zeroEx.batchCancelRfqOrders(orders).awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, orders.map(o => ({ maker: o.maker, orderHash: o.getHash() })), IZeroExEvents.OrderCancelled, ); const infos = await Promise.all(orders.map(o => zeroEx.getRfqOrderInfo(o).callAsync())); expect(infos.map(i => i.status)).to.deep.eq(infos.map(() => OrderStatus.Cancelled)); }); it("cannot cancel someone else's orders", async () => { const orders = [...new Array(3)].map(() => getTestRfqOrder()); const tx = zeroEx.batchCancelRfqOrders(orders).awaitTransactionSuccessAsync({ from: notMaker }); return expect(tx).to.revertWith( new RevertErrors.OnlyOrderMakerAllowed(orders[0].getHash(), notMaker, orders[0].maker), ); }); }); describe('cancelPairOrders()', async () => { it('can cancel multiple limit orders of the same pair with salt < minValidSalt', async () => { const orders = [...new Array(3)].map((_v, i) => getTestLimitOrder().clone({ salt: new BigNumber(i) })); // Cancel the first two orders. const minValidSalt = orders[2].salt; const receipt = await zeroEx .cancelPairLimitOrders(makerToken.address, takerToken.address, minValidSalt) .awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [ { maker, makerToken: makerToken.address, takerToken: takerToken.address, minValidSalt, }, ], IZeroExEvents.PairOrdersCancelled, ); const statuses = (await Promise.all(orders.map(o => zeroEx.getLimitOrderInfo(o).callAsync()))).map( oi => oi.status, ); expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled, OrderStatus.Fillable]); }); it('does not cancel limit orders of a different pair', async () => { const order = getRandomLimitOrder({ salt: new BigNumber(1) }); // Cancel salts <= the order's, but flip the tokens to be a different // pair. const minValidSalt = order.salt.plus(1); await zeroEx .cancelPairLimitOrders(takerToken.address, makerToken.address, minValidSalt) .awaitTransactionSuccessAsync({ from: maker }); const { status } = await zeroEx.getLimitOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Fillable); }); it('can cancel multiple RFQ orders of the same pair with salt < minValidSalt', async () => { const orders = [...new Array(3)].map((_v, i) => getTestRfqOrder().clone({ salt: new BigNumber(i) })); // Cancel the first two orders. const minValidSalt = orders[2].salt; const receipt = await zeroEx .cancelPairRfqOrders(makerToken.address, takerToken.address, minValidSalt) .awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [ { maker, makerToken: makerToken.address, takerToken: takerToken.address, minValidSalt, }, ], IZeroExEvents.PairOrdersCancelled, ); const statuses = (await Promise.all(orders.map(o => zeroEx.getRfqOrderInfo(o).callAsync()))).map( oi => oi.status, ); expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled, OrderStatus.Fillable]); }); it('does not cancel RFQ orders of a different pair', async () => { const order = getRandomRfqOrder({ salt: new BigNumber(1) }); // Cancel salts <= the order's, but flip the tokens to be a different // pair. const minValidSalt = order.salt.plus(1); await zeroEx .cancelPairRfqOrders(takerToken.address, makerToken.address, minValidSalt) .awaitTransactionSuccessAsync({ from: maker }); const { status } = await zeroEx.getRfqOrderInfo(order).callAsync(); expect(status).to.eq(OrderStatus.Fillable); }); }); describe('batchCancelPairOrders()', async () => { it('can cancel multiple limit order pairs', async () => { const orders = [ getTestLimitOrder({ salt: new BigNumber(1) }), // Flip the tokens for the other order. getTestLimitOrder({ makerToken: takerToken.address, takerToken: makerToken.address, salt: new BigNumber(1), }), ]; const minValidSalt = new BigNumber(2); const receipt = await zeroEx .batchCancelPairLimitOrders( [makerToken.address, takerToken.address], [takerToken.address, makerToken.address], [minValidSalt, minValidSalt], ) .awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [ { maker, makerToken: makerToken.address, takerToken: takerToken.address, minValidSalt, }, { maker, makerToken: takerToken.address, takerToken: makerToken.address, minValidSalt, }, ], IZeroExEvents.PairOrdersCancelled, ); const statuses = (await Promise.all(orders.map(o => zeroEx.getLimitOrderInfo(o).callAsync()))).map( oi => oi.status, ); expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled]); }); it('can cancel multiple RFQ order pairs', async () => { const orders = [ getTestRfqOrder({ salt: new BigNumber(1) }), // Flip the tokens for the other order. getTestRfqOrder({ makerToken: takerToken.address, takerToken: makerToken.address, salt: new BigNumber(1), }), ]; const minValidSalt = new BigNumber(2); const receipt = await zeroEx .batchCancelPairRfqOrders( [makerToken.address, takerToken.address], [takerToken.address, makerToken.address], [minValidSalt, minValidSalt], ) .awaitTransactionSuccessAsync({ from: maker }); verifyEventsFromLogs( receipt.logs, [ { maker, makerToken: makerToken.address, takerToken: takerToken.address, minValidSalt, }, { maker, makerToken: takerToken.address, takerToken: makerToken.address, minValidSalt, }, ], IZeroExEvents.PairOrdersCancelled, ); const statuses = (await Promise.all(orders.map(o => zeroEx.getRfqOrderInfo(o).callAsync()))).map( oi => oi.status, ); expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled]); }); }); interface LimitOrderFilledAmounts { makerTokenFilledAmount: BigNumber; takerTokenFilledAmount: BigNumber; takerTokenFeeFilledAmount: BigNumber; } function computeLimitOrderFilledAmounts( order: LimitOrder, takerTokenFillAmount: BigNumber = order.takerAmount, takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, ): LimitOrderFilledAmounts { const fillAmount = BigNumber.min( order.takerAmount, takerTokenFillAmount, order.takerAmount.minus(takerTokenAlreadyFilledAmount), ); const makerTokenFilledAmount = fillAmount .times(order.makerAmount) .div(order.takerAmount) .integerValue(BigNumber.ROUND_DOWN); const takerTokenFeeFilledAmount = fillAmount .times(order.takerTokenFeeAmount) .div(order.takerAmount) .integerValue(BigNumber.ROUND_DOWN); return { makerTokenFilledAmount, takerTokenFilledAmount: fillAmount, takerTokenFeeFilledAmount, }; } function createLimitOrderFilledEventArgs( order: LimitOrder, takerTokenFillAmount: BigNumber = order.takerAmount, takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, ): object { const { makerTokenFilledAmount, takerTokenFilledAmount, takerTokenFeeFilledAmount, } = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount); const protocolFee = order.taker !== NULL_ADDRESS ? ZERO_AMOUNT : SINGLE_PROTOCOL_FEE; return { taker, takerTokenFilledAmount, makerTokenFilledAmount, takerTokenFeeFilledAmount, orderHash: order.getHash(), maker: order.maker, feeRecipient: order.feeRecipient, makerToken: order.makerToken, takerToken: order.takerToken, protocolFeePaid: protocolFee, pool: order.pool, }; } async function assertExpectedFinalBalancesFromLimitOrderFillAsync( order: LimitOrder, opts: Partial<{ takerTokenFillAmount: BigNumber; takerTokenAlreadyFilledAmount: BigNumber; receipt: TransactionReceiptWithDecodedLogs; }> = {}, ): Promise { const { takerTokenFillAmount, takerTokenAlreadyFilledAmount, receipt } = { takerTokenFillAmount: order.takerAmount, takerTokenAlreadyFilledAmount: ZERO_AMOUNT, receipt: undefined, ...opts, }; const { makerTokenFilledAmount, takerTokenFilledAmount, takerTokenFeeFilledAmount, } = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount); const makerBalance = await takerToken.balanceOf(maker).callAsync(); const takerBalance = await makerToken.balanceOf(taker).callAsync(); const feeRecipientBalance = await takerToken.balanceOf(order.feeRecipient).callAsync(); expect(makerBalance).to.bignumber.eq(takerTokenFilledAmount); expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount); expect(feeRecipientBalance).to.bignumber.eq(takerTokenFeeFilledAmount); if (receipt) { const balanceOfTakerNow = await env.web3Wrapper.getBalanceInWeiAsync(taker); const balanceOfTakerBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker, receipt.blockNumber - 1); const protocolFee = order.taker === NULL_ADDRESS ? SINGLE_PROTOCOL_FEE : 0; const totalCost = GAS_PRICE.times(receipt.gasUsed).plus(protocolFee); expect(balanceOfTakerBefore.minus(totalCost)).to.bignumber.eq(balanceOfTakerNow); } } describe('fillLimitOrder()', () => { it('can fully fill an order', async () => { const order = getTestLimitOrder(); const receipt = await fillLimitOrderAsync(order); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order)], IZeroExEvents.LimitOrderFilled, ); assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { receipt }); }); it('can partially fill an order', async () => { const order = getTestLimitOrder(); const fillAmount = order.takerAmount.minus(1); const receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.LimitOrderFilled, ); assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Fillable, takerTokenFilledAmount: fillAmount, }); await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount }); }); it('can fully fill an order in two steps', async () => { const order = getTestLimitOrder(); let fillAmount = order.takerAmount.dividedToIntegerBy(2); let receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.LimitOrderFilled, ); const alreadyFilledAmount = fillAmount; fillAmount = order.takerAmount.minus(fillAmount); receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], IZeroExEvents.LimitOrderFilled, ); assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); }); it('clamps fill amount to remaining available', async () => { const order = getTestLimitOrder(); const fillAmount = order.takerAmount.plus(1); const receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.LimitOrderFilled, ); assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount }); }); it('clamps fill amount to remaining available in partial filled order', async () => { const order = getTestLimitOrder(); let fillAmount = order.takerAmount.dividedToIntegerBy(2); let receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.LimitOrderFilled, ); const alreadyFilledAmount = fillAmount; fillAmount = order.takerAmount.minus(fillAmount).plus(1); receipt = await fillLimitOrderAsync(order, { fillAmount }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], IZeroExEvents.LimitOrderFilled, ); assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); }); it('cannot fill an expired order', async () => { const order = getTestLimitOrder({ expiry: createExpiry(-60) }); const tx = fillLimitOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Expired), ); }); it('cannot fill a cancelled order', async () => { const order = getTestLimitOrder(); await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker }); const tx = fillLimitOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), ); }); it('cannot fill a salt/pair cancelled order', async () => { const order = getTestLimitOrder(); await zeroEx .cancelPairLimitOrders(makerToken.address, takerToken.address, order.salt.plus(1)) .awaitTransactionSuccessAsync({ from: maker }); const tx = fillLimitOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), ); }); it('non-taker cannot fill order', async () => { const order = getTestLimitOrder({ taker }); const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker), ); }); it('non-sender cannot fill order', async () => { const order = getTestLimitOrder({ sender: taker }); const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker }); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableBySenderError(order.getHash(), notTaker, order.sender), ); }); it('cannot fill order with bad signature', async () => { const order = getTestLimitOrder(); // Overwrite chainId to result in a different hash and therefore different // signature. const tx = fillLimitOrderAsync(order.clone({ chainId: 1234 })); return expect(tx).to.revertWith( new RevertErrors.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker), ); }); it('fails if no protocol fee attached', async () => { const order = getTestLimitOrder(); await prepareBalancesForOrderAsync(order); const tx = zeroEx .fillLimitOrder( order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(order.takerAmount), ) .awaitTransactionSuccessAsync({ from: taker, value: ZERO_AMOUNT }); // The exact revert error depends on whether we are still doing a // token spender fallthroigh, so we won't get too specific. return expect(tx).to.revertWith(new AnyRevertError()); }); it('refunds excess protocol fee', async () => { const order = getTestLimitOrder(); const receipt = await fillLimitOrderAsync(order, { protocolFee: SINGLE_PROTOCOL_FEE.plus(1) }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order)], IZeroExEvents.LimitOrderFilled, ); await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { receipt }); }); }); interface RfqOrderFilledAmounts { makerTokenFilledAmount: BigNumber; takerTokenFilledAmount: BigNumber; } function computeRfqOrderFilledAmounts( order: RfqOrder, takerTokenFillAmount: BigNumber = order.takerAmount, takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, ): RfqOrderFilledAmounts { const fillAmount = BigNumber.min( order.takerAmount, takerTokenFillAmount, order.takerAmount.minus(takerTokenAlreadyFilledAmount), ); const makerTokenFilledAmount = fillAmount .times(order.makerAmount) .div(order.takerAmount) .integerValue(BigNumber.ROUND_DOWN); return { makerTokenFilledAmount, takerTokenFilledAmount: fillAmount, }; } function createRfqOrderFilledEventArgs( order: RfqOrder, takerTokenFillAmount: BigNumber = order.takerAmount, takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, ): object { const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts( order, takerTokenFillAmount, takerTokenAlreadyFilledAmount, ); return { taker, takerTokenFilledAmount, makerTokenFilledAmount, orderHash: order.getHash(), maker: order.maker, makerToken: order.makerToken, takerToken: order.takerToken, pool: order.pool, }; } async function assertExpectedFinalBalancesFromRfqOrderFillAsync( order: RfqOrder, takerTokenFillAmount: BigNumber = order.takerAmount, takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT, ): Promise { const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts( order, takerTokenFillAmount, takerTokenAlreadyFilledAmount, ); const makerBalance = await takerToken.balanceOf(maker).callAsync(); const takerBalance = await makerToken.balanceOf(taker).callAsync(); expect(makerBalance).to.bignumber.eq(takerTokenFilledAmount); expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount); } describe('registerAllowedRfqOrigins()', () => { it('cannot register through a contract', async () => { const tx = testRfqOriginRegistration .registerAllowedRfqOrigins(zeroEx.address, [], true) .awaitTransactionSuccessAsync(); expect(tx).to.revertWith('NativeOrdersFeature/NO_CONTRACT_ORIGINS'); }); }); describe('fillRfqOrder()', () => { it('can fully fill an order', async () => { const order = getTestRfqOrder(); const receipt = await fillRfqOrderAsync(order); verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled); assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); await assertExpectedFinalBalancesFromRfqOrderFillAsync(order); }); it('can partially fill an order', async () => { const order = getTestRfqOrder(); const fillAmount = order.takerAmount.minus(1); const receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.RfqOrderFilled, ); assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Fillable, takerTokenFilledAmount: fillAmount, }); await assertExpectedFinalBalancesFromRfqOrderFillAsync(order, fillAmount); }); it('can fully fill an order in two steps', async () => { const order = getTestRfqOrder(); let fillAmount = order.takerAmount.dividedToIntegerBy(2); let receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.RfqOrderFilled, ); const alreadyFilledAmount = fillAmount; fillAmount = order.takerAmount.minus(fillAmount); receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], IZeroExEvents.RfqOrderFilled, ); assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); }); it('clamps fill amount to remaining available', async () => { const order = getTestRfqOrder(); const fillAmount = order.takerAmount.plus(1); const receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.RfqOrderFilled, ); assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); await assertExpectedFinalBalancesFromRfqOrderFillAsync(order, fillAmount); }); it('clamps fill amount to remaining available in partial filled order', async () => { const order = getTestRfqOrder(); let fillAmount = order.takerAmount.dividedToIntegerBy(2); let receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount)], IZeroExEvents.RfqOrderFilled, ); const alreadyFilledAmount = fillAmount; fillAmount = order.takerAmount.minus(fillAmount).plus(1); receipt = await fillRfqOrderAsync(order, fillAmount); verifyEventsFromLogs( receipt.logs, [createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)], IZeroExEvents.RfqOrderFilled, ); assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), { orderHash: order.getHash(), status: OrderStatus.Filled, takerTokenFilledAmount: order.takerAmount, }); }); it('cannot fill an order with wrong tx.origin', async () => { const order = getTestRfqOrder(); const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableByOriginError(order.getHash(), notTaker, taker), ); }); it('can fill an order from a different tx.origin if registered', async () => { const order = getTestRfqOrder(); const receipt = await zeroEx .registerAllowedRfqOrigins([notTaker], true) .awaitTransactionSuccessAsync({ from: taker }); verifyEventsFromLogs( receipt.logs, [ { origin: taker, addrs: [notTaker], allowed: true, }, ], IZeroExEvents.RfqOrderOriginsAllowed, ); return fillRfqOrderAsync(order, order.takerAmount, notTaker); }); it('cannot fill an order with registered then unregistered tx.origin', async () => { const order = getTestRfqOrder(); await zeroEx.registerAllowedRfqOrigins([notTaker], true).awaitTransactionSuccessAsync({ from: taker }); const receipt = await zeroEx .registerAllowedRfqOrigins([notTaker], false) .awaitTransactionSuccessAsync({ from: taker }); verifyEventsFromLogs( receipt.logs, [ { origin: taker, addrs: [notTaker], allowed: false, }, ], IZeroExEvents.RfqOrderOriginsAllowed, ); const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableByOriginError(order.getHash(), notTaker, taker), ); }); it('cannot fill an order with a zero tx.origin', async () => { const order = getTestRfqOrder({ txOrigin: NULL_ADDRESS }); const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Invalid), ); }); it('non-taker cannot fill order', async () => { const order = getTestRfqOrder({ taker, txOrigin: notTaker }); const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker), ); }); it('cannot fill an expired order', async () => { const order = getTestRfqOrder({ expiry: createExpiry(-60) }); const tx = fillRfqOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Expired), ); }); it('cannot fill a cancelled order', async () => { const order = getTestRfqOrder(); await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker }); const tx = fillRfqOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), ); }); it('cannot fill a salt/pair cancelled order', async () => { const order = getTestRfqOrder(); await zeroEx .cancelPairRfqOrders(makerToken.address, takerToken.address, order.salt.plus(1)) .awaitTransactionSuccessAsync({ from: maker }); const tx = fillRfqOrderAsync(order); return expect(tx).to.revertWith( new RevertErrors.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled), ); }); it('cannot fill order with bad signature', async () => { const order = getTestRfqOrder(); // Overwrite chainId to result in a different hash and therefore different // signature. const tx = fillRfqOrderAsync(order.clone({ chainId: 1234 })); return expect(tx).to.revertWith( new RevertErrors.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker), ); }); it('fails if ETH is attached', async () => { const order = getTestRfqOrder(); await prepareBalancesForOrderAsync(order, taker); const tx = zeroEx .fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) .awaitTransactionSuccessAsync({ from: taker, value: 1 }); // This will revert at the language level because the fill function is not payable. return expect(tx).to.be.rejectedWith('revert'); }); }); describe('fillOrKillLimitOrder()', () => { it('can fully fill an order', async () => { const order = getTestLimitOrder(); await prepareBalancesForOrderAsync(order); const receipt = await zeroEx .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) .awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE }); verifyEventsFromLogs( receipt.logs, [createLimitOrderFilledEventArgs(order)], IZeroExEvents.LimitOrderFilled, ); }); it('reverts if cannot fill the exact amount', async () => { const order = getTestLimitOrder(); await prepareBalancesForOrderAsync(order); const fillAmount = order.takerAmount.plus(1); const tx = zeroEx .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount) .awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE }); return expect(tx).to.revertWith( new RevertErrors.FillOrKillFailedError(order.getHash(), order.takerAmount, fillAmount), ); }); it('refunds excess protocol fee', async () => { const order = getTestLimitOrder(); await prepareBalancesForOrderAsync(order); const takerBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker); const receipt = await zeroEx .fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) .awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE.plus(1) }); const takerBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker); const totalCost = GAS_PRICE.times(receipt.gasUsed).plus(SINGLE_PROTOCOL_FEE); expect(takerBalanceBefore.minus(totalCost)).to.bignumber.eq(takerBalanceAfter); }); }); describe('fillOrKillRfqOrder()', () => { it('can fully fill an order', async () => { const order = getTestRfqOrder(); await prepareBalancesForOrderAsync(order); const receipt = await zeroEx .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) .awaitTransactionSuccessAsync({ from: taker }); verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled); }); it('reverts if cannot fill the exact amount', async () => { const order = getTestRfqOrder(); await prepareBalancesForOrderAsync(order); const fillAmount = order.takerAmount.plus(1); const tx = zeroEx .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount) .awaitTransactionSuccessAsync({ from: taker }); return expect(tx).to.revertWith( new RevertErrors.FillOrKillFailedError(order.getHash(), order.takerAmount, fillAmount), ); }); it('fails if ETH is attached', async () => { const order = getTestRfqOrder(); await prepareBalancesForOrderAsync(order); const tx = zeroEx .fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount) .awaitTransactionSuccessAsync({ from: taker, value: 1 }); // This will revert at the language level because the fill function is not payable. return expect(tx).to.be.rejectedWith('revert'); }); }); it.skip('RFQ gas benchmark', async () => { const orders = [...new Array(2)].map(() => getTestRfqOrder({ pool: '0x0000000000000000000000000000000000000000000000000000000000000000' }), ); // Fill one to warm up the fee pool. await fillRfqOrderAsync(orders[0]); const receipt = await fillRfqOrderAsync(orders[1]); // tslint:disable-next-line: no-console console.log(receipt.gasUsed); }); });