refactor transaction integration tests to use new framework

This commit is contained in:
Michael Zhu
2019-11-07 15:49:49 -08:00
parent 761d0a0f18
commit 251d30d47f
5 changed files with 831 additions and 1011 deletions

View File

@@ -1,996 +0,0 @@
// tslint:disable: max-file-line-count
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import {
blockchainTests,
constants,
describe,
ExchangeFunctionName,
expect,
getLatestBlockTimestampAsync,
OrderFactory,
orderHashUtils,
TransactionFactory,
transactionHashUtils,
} from '@0x/contracts-test-utils';
import { FillResults, OrderStatus } from '@0x/types';
import { AbiEncoder, BigNumber, ExchangeRevertErrors } from '@0x/utils';
import { LogWithDecodedArgs, MethodAbi } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { exchangeDataEncoder } from '../src/exchange_data_encoder';
import { artifacts as localArtifacts } from './artifacts';
import { ExchangeWrapper } from './utils/exchange_wrapper';
import {
ExchangeCancelEventArgs,
ExchangeCancelUpToEventArgs,
ExchangeContract,
ExchangeFillEventArgs,
ExchangeSignatureValidatorApprovalEventArgs,
ExchangeTransactionExecutionEventArgs,
} from './wrappers';
const artifacts = { ...erc20Artifacts, ...localArtifacts };
// tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets('Exchange transactions', env => {
let chainId: number;
let senderAddress: string;
let owner: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipientAddress: string;
let validatorAddress: string;
let taker2Address: string;
let erc20TokenA: DummyERC20TokenContract;
let erc20TokenB: DummyERC20TokenContract;
let takerFeeToken: DummyERC20TokenContract;
let makerFeeToken: DummyERC20TokenContract;
let exchangeInstance: ExchangeContract;
let erc20Proxy: ERC20ProxyContract;
let orderFactory: OrderFactory;
let makerTransactionFactory: TransactionFactory;
let takerTransactionFactory: TransactionFactory;
let taker2TransactionFactory: TransactionFactory;
let exchangeWrapper: ExchangeWrapper;
let erc20Wrapper: ERC20Wrapper;
let defaultMakerTokenAddress: string;
let defaultTakerTokenAddress: string;
let defaultMakerFeeTokenAddress: string;
let defaultTakerFeeTokenAddress: string;
let makerPrivateKey: Buffer;
let takerPrivateKey: Buffer;
let taker2PrivateKey: Buffer;
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, env.provider, env.txDefaults);
before(async () => {
chainId = await env.getChainIdAsync();
const accounts = await env.getAccountAddressesAsync();
const usedAddresses = ([
owner,
senderAddress,
makerAddress,
takerAddress,
feeRecipientAddress,
validatorAddress,
taker2Address,
] = _.slice(accounts, 0, 7));
erc20Wrapper = new ERC20Wrapper(env.provider, usedAddresses, owner);
const numDummyErc20ToDeploy = 4;
[erc20TokenA, erc20TokenB, takerFeeToken, makerFeeToken] = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
env.provider,
env.txDefaults,
{},
new BigNumber(chainId),
);
exchangeWrapper = new ExchangeWrapper(exchangeInstance);
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
await erc20Proxy.addAuthorizedAddress(exchangeInstance.address).awaitTransactionSuccessAsync({ from: owner });
defaultMakerTokenAddress = erc20TokenA.address;
defaultTakerTokenAddress = erc20TokenB.address;
defaultMakerFeeTokenAddress = makerFeeToken.address;
defaultTakerFeeTokenAddress = takerFeeToken.address;
const defaultOrderParams = {
...constants.STATIC_ORDER_PARAMS,
makerAddress,
feeRecipientAddress,
makerAssetData: await devUtils.encodeERC20AssetData(defaultMakerTokenAddress).callAsync(),
takerAssetData: await devUtils.encodeERC20AssetData(defaultTakerTokenAddress).callAsync(),
makerFeeAssetData: await devUtils.encodeERC20AssetData(defaultMakerFeeTokenAddress).callAsync(),
takerFeeAssetData: await devUtils.encodeERC20AssetData(defaultTakerFeeTokenAddress).callAsync(),
exchangeAddress: exchangeInstance.address,
chainId,
};
makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
taker2PrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(taker2Address)];
orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams);
makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address, chainId);
takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address, chainId);
taker2TransactionFactory = new TransactionFactory(taker2PrivateKey, exchangeInstance.address, chainId);
});
describe('executeTransaction', () => {
describe('general functionality', () => {
it('should log the correct transactionHash if successfully executed', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress);
const transactionExecutionLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeTransactionExecutionEventArgs>).event ===
'TransactionExecution',
);
expect(transactionExecutionLogs.length).to.eq(1);
const executionLogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(executionLogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction),
);
});
it('should revert if the transaction is expired', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
const currentTimestamp = await getLatestBlockTimestampAsync();
const transaction = await takerTransactionFactory.newSignedTransactionAsync({
data,
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.Expired,
transactionHashHex,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if the actual gasPrice is greater than expected', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({
data,
});
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const actualGasPrice = transaction.gasPrice.plus(1);
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
transactionHashHex,
actualGasPrice,
transaction.gasPrice,
);
const tx = exchangeInstance
.executeTransaction(transaction, transaction.signature)
.sendTransactionAsync({ gasPrice: actualGasPrice, from: senderAddress });
return expect(tx).to.revertWith(expectedError);
});
it('should revert if the actual gasPrice is less than expected', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({
data,
});
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const actualGasPrice = transaction.gasPrice.minus(1);
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
transactionHashHex,
actualGasPrice,
transaction.gasPrice,
);
const tx = exchangeInstance
.executeTransaction(transaction, transaction.signature)
.sendTransactionAsync({ gasPrice: actualGasPrice, from: senderAddress });
return expect(tx).to.revertWith(expectedError);
});
});
describe('fill methods', () => {
for (const fnName of [
...constants.SINGLE_FILL_FN_NAMES,
...constants.BATCH_FILL_FN_NAMES,
...constants.MARKET_FILL_FN_NAMES,
]) {
it(`${fnName} should revert if signature is invalid and not called by signer`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const v = ethUtil.toBuffer(transaction.signature.slice(0, 4));
const invalidR = ethUtil.sha3('invalidR');
const invalidS = ethUtil.sha3('invalidS');
const signatureType = ethUtil.toBuffer(`0x${transaction.signature.slice(-2)}`);
const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]);
const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
transaction.signature = invalidSigHex;
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.BadTransactionSignature,
transactionHashHex,
transaction.signerAddress,
transaction.signature,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should be successful if signed by taker and called by sender`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(
transaction,
senderAddress,
);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(1);
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
expect(fillLogArgs.senderAddress).to.eq(senderAddress);
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount);
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount);
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee);
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee);
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
});
it(`${fnName} should be successful if called by taker without a transaction signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(1);
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
expect(fillLogArgs.senderAddress).to.eq(takerAddress);
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount);
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount);
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee);
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee);
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
});
it(`${fnName} should return the correct data if successful`, async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const returnData = await exchangeInstance
.executeTransaction(transaction, transaction.signature)
.callAsync({
from: senderAddress,
});
const abi = artifacts.Exchange.compilerOutput.abi;
const methodAbi = abi.filter(abiItem => (abiItem as MethodAbi).name === fnName)[0] as MethodAbi;
const abiEncoder = new AbiEncoder.Method(methodAbi);
const decodedReturnData = abiEncoder.decodeReturnValues(returnData);
const fillResults =
constants.BATCH_FILL_FN_NAMES.indexOf(fnName) !== -1
? decodedReturnData.fillResults[0]
: decodedReturnData.fillResults;
expect(fillResults.makerAssetFilledAmount).to.be.bignumber.eq(order.makerAssetAmount);
expect(fillResults.takerAssetFilledAmount).to.be.bignumber.eq(order.takerAssetAmount);
expect(fillResults.makerFeePaid).to.be.bignumber.eq(order.makerFee);
expect(fillResults.takerFeePaid).to.be.bignumber.eq(order.takerFee);
});
it(`${fnName} should revert if transaction has already been executed`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted,
transactionHashHex,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should revert and rethrow error if executeTransaction is called recursively with a signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const recursiveData = exchangeInstance
.executeTransaction(transaction, transaction.signature)
.getABIEncodedTransactionData();
const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({
data: recursiveData,
});
const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex(
recursiveTransaction,
);
const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError(
transactionHashHex,
transaction.signerAddress,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
recursiveTransactionHashHex,
noReentrancyError,
);
const tx = exchangeWrapper.executeTransactionAsync(recursiveTransaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should be successful if executeTransaction is called recursively by taker without a signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const recursiveData = exchangeInstance
.executeTransaction(transaction, constants.NULL_BYTES)
.getABIEncodedTransactionData();
const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({
data: recursiveData,
});
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(
recursiveTransaction,
takerAddress,
);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(1);
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
expect(fillLogArgs.senderAddress).to.eq(takerAddress);
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount);
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount);
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee);
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee);
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
});
if (
[
ExchangeFunctionName.FillOrderNoThrow,
ExchangeFunctionName.BatchFillOrdersNoThrow,
ExchangeFunctionName.MarketBuyOrdersNoThrow,
ExchangeFunctionName.MarketSellOrdersNoThrow,
ExchangeFunctionName.MarketBuyOrdersFillOrKill,
ExchangeFunctionName.MarketSellOrdersFillOrKill,
].indexOf(fnName) === -1
) {
it(`${fnName} should revert and rethrow error if the underlying function reverts`, async () => {
const order = await orderFactory.newSignedOrderAsync();
order.signature = constants.NULL_BYTES;
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
orderHashUtils.getOrderHashHex(order),
order.makerAddress,
order.signature,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
}
}
});
describe('cancelOrder', () => {
it('should revert if not signed by or called by maker', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(order),
takerAddress,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it('should be successful if signed by maker and called by sender', async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
);
expect(cancelLogs.length).to.eq(1);
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.senderAddress).to.eq(senderAddress);
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(cancelLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
expect(cancelLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
});
it('should be successful if called by maker without a signature', async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
);
expect(cancelLogs.length).to.eq(1);
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.senderAddress).to.eq(makerAddress);
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(cancelLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
expect(cancelLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
});
});
describe('batchCancelOrders', () => {
it('should revert if not signed by or called by maker', async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(orders[0]),
takerAddress,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
return expect(tx).to.revertWith(expectedError);
});
it('should be successful if signed by maker and called by sender', async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
);
expect(cancelLogs.length).to.eq(orders.length);
orders.forEach((order, index) => {
const cancelLogArgs = (cancelLogs[index] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.senderAddress).to.eq(senderAddress);
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(cancelLogArgs.makerAssetData).to.eq(order.makerAssetData);
expect(cancelLogArgs.takerAssetData).to.eq(order.takerAssetData);
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order));
});
});
it('should be successful if called by maker without a signature', async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
);
expect(cancelLogs.length).to.eq(orders.length);
orders.forEach((order, index) => {
const cancelLogArgs = (cancelLogs[index] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.senderAddress).to.eq(makerAddress);
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(cancelLogArgs.makerAssetData).to.eq(order.makerAssetData);
expect(cancelLogArgs.takerAssetData).to.eq(order.takerAssetData);
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order));
});
});
});
describe('cancelOrdersUpTo', () => {
it('should be successful if signed by maker and called by sender', async () => {
const targetEpoch = constants.ZERO_AMOUNT;
const data = exchangeInstance.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData();
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).event === 'CancelUpTo',
);
expect(cancelLogs.length).to.eq(1);
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.orderSenderAddress).to.eq(senderAddress);
expect(cancelLogArgs.orderEpoch).to.bignumber.eq(targetEpoch.plus(1));
});
it('should be successful if called by maker without a signature', async () => {
const targetEpoch = constants.ZERO_AMOUNT;
const data = exchangeInstance.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData();
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress);
const cancelLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).event === 'CancelUpTo',
);
expect(cancelLogs.length).to.eq(1);
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.orderSenderAddress).to.eq(constants.NULL_ADDRESS);
expect(cancelLogArgs.orderEpoch).to.bignumber.eq(targetEpoch.plus(1));
});
});
describe('preSign', () => {
it('should preSign a hash for the signer', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orderHash = orderHashUtils.getOrderHashHex(order);
const data = exchangeInstance.preSign(orderHash).getABIEncodedTransactionData();
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
let isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync();
expect(isPreSigned).to.be.eq(false);
await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync();
expect(isPreSigned).to.be.eq(true);
});
it('should preSign a hash for the caller if called without a signature', async () => {
const order = await orderFactory.newSignedOrderAsync();
const orderHash = orderHashUtils.getOrderHashHex(order);
const data = exchangeInstance.preSign(orderHash).getABIEncodedTransactionData();
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
let isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync();
expect(isPreSigned).to.be.eq(false);
await exchangeWrapper.executeTransactionAsync(transaction, takerAddress);
isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync();
expect(isPreSigned).to.be.eq(true);
});
});
describe('setSignatureValidatorApproval', () => {
it('should approve a validator for the signer', async () => {
const shouldApprove = true;
const data = exchangeInstance
.setSignatureValidatorApproval(validatorAddress, shouldApprove)
.getABIEncodedTransactionData();
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress);
const validatorApprovalLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeSignatureValidatorApprovalEventArgs>).event ===
'SignatureValidatorApproval',
);
expect(validatorApprovalLogs.length).to.eq(1);
const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs<
ExchangeSignatureValidatorApprovalEventArgs
>).args;
expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress);
expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress);
expect(validatorApprovalLogArgs.isApproved).to.eq(shouldApprove);
});
it('should approve a validator for the caller if called with no signature', async () => {
const shouldApprove = true;
const data = exchangeInstance
.setSignatureValidatorApproval(validatorAddress, shouldApprove)
.getABIEncodedTransactionData();
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress);
const validatorApprovalLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeSignatureValidatorApprovalEventArgs>).event ===
'SignatureValidatorApproval',
);
expect(validatorApprovalLogs.length).to.eq(1);
const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs<
ExchangeSignatureValidatorApprovalEventArgs
>).args;
expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress);
expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress);
expect(validatorApprovalLogArgs.isApproved).to.eq(shouldApprove);
});
});
describe('batchExecuteTransactions', () => {
it('should successfully call fillOrder via 2 transactions with different taker signatures', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 });
const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync(
[transaction1, transaction2],
senderAddress,
);
const transactionExecutionLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeTransactionExecutionEventArgs>).event ===
'TransactionExecution',
);
expect(transactionExecutionLogs.length).to.eq(2);
const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution1LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction1),
);
const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution2LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction2),
);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(2);
const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill1LogArgs.makerAddress).to.eq(makerAddress);
expect(fill1LogArgs.takerAddress).to.eq(takerAddress);
expect(fill1LogArgs.senderAddress).to.eq(senderAddress);
expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData);
expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData);
expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount);
expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount);
expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee);
expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee);
expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1));
const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill2LogArgs.makerAddress).to.eq(makerAddress);
expect(fill2LogArgs.takerAddress).to.eq(taker2Address);
expect(fill2LogArgs.senderAddress).to.eq(senderAddress);
expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData);
expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData);
expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount);
expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount);
expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee);
expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee);
expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2));
});
it('should successfully call fillOrder via 2 transactions when called by taker with no signatures', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 });
transaction1.signature = constants.NULL_BYTES;
transaction2.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync(
[transaction1, transaction2],
takerAddress,
);
const transactionExecutionLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeTransactionExecutionEventArgs>).event ===
'TransactionExecution',
);
expect(transactionExecutionLogs.length).to.eq(2);
const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution1LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction1),
);
const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution2LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction2),
);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(2);
const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill1LogArgs.makerAddress).to.eq(makerAddress);
expect(fill1LogArgs.takerAddress).to.eq(takerAddress);
expect(fill1LogArgs.senderAddress).to.eq(takerAddress);
expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData);
expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData);
expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount);
expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount);
expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee);
expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee);
expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1));
const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill2LogArgs.makerAddress).to.eq(makerAddress);
expect(fill2LogArgs.takerAddress).to.eq(takerAddress);
expect(fill2LogArgs.senderAddress).to.eq(takerAddress);
expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData);
expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData);
expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount);
expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount);
expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee);
expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee);
expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2));
});
it('should successfully call fillOrder via 2 transactions when one is signed by taker1 and executeTransaction is called by taker2', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 });
transaction2.signature = constants.NULL_BYTES;
const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync(
[transaction1, transaction2],
taker2Address,
);
const transactionExecutionLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeTransactionExecutionEventArgs>).event ===
'TransactionExecution',
);
expect(transactionExecutionLogs.length).to.eq(2);
const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution1LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction1),
);
const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution2LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction2),
);
const fillLogs = transactionReceipt.logs.filter(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
expect(fillLogs.length).to.eq(2);
const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill1LogArgs.makerAddress).to.eq(makerAddress);
expect(fill1LogArgs.takerAddress).to.eq(takerAddress);
expect(fill1LogArgs.senderAddress).to.eq(taker2Address);
expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData);
expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData);
expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount);
expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount);
expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee);
expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee);
expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1));
const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fill2LogArgs.makerAddress).to.eq(makerAddress);
expect(fill2LogArgs.takerAddress).to.eq(taker2Address);
expect(fill2LogArgs.senderAddress).to.eq(taker2Address);
expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData);
expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData);
expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount);
expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount);
expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee);
expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee);
expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2));
});
it('should return the correct data for 2 different fillOrder calls', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 });
const returnData = await exchangeInstance
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.callAsync({ from: senderAddress });
const abi = artifacts.Exchange.compilerOutput.abi;
const methodAbi = abi.filter(
abiItem => (abiItem as MethodAbi).name === ExchangeFunctionName.FillOrder,
)[0] as MethodAbi;
const abiEncoder = new AbiEncoder.Method(methodAbi);
const fillResults1: FillResults = abiEncoder.decodeReturnValues(returnData[0]).fillResults;
const fillResults2: FillResults = abiEncoder.decodeReturnValues(returnData[1]).fillResults;
expect(fillResults1.makerAssetFilledAmount).to.be.bignumber.eq(order1.makerAssetAmount);
expect(fillResults1.takerAssetFilledAmount).to.be.bignumber.eq(order1.takerAssetAmount);
expect(fillResults1.makerFeePaid).to.be.bignumber.eq(order1.makerFee);
expect(fillResults1.takerFeePaid).to.be.bignumber.eq(order1.takerFee);
expect(fillResults2.makerAssetFilledAmount).to.be.bignumber.eq(order2.makerAssetAmount);
expect(fillResults2.takerAssetFilledAmount).to.be.bignumber.eq(order2.takerAssetAmount);
expect(fillResults2.makerFeePaid).to.be.bignumber.eq(order2.makerFee);
expect(fillResults2.takerFeePaid).to.be.bignumber.eq(order2.takerFee);
});
it('should successfully call fillOrder and cancelOrder via 2 transactions', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [
order2,
]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 });
const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync(
[transaction1, transaction2],
senderAddress,
);
const transactionExecutionLogs = transactionReceipt.logs.filter(
log =>
(log as LogWithDecodedArgs<ExchangeTransactionExecutionEventArgs>).event ===
'TransactionExecution',
);
expect(transactionExecutionLogs.length).to.eq(2);
const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution1LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction1),
);
const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs<
ExchangeTransactionExecutionEventArgs
>).args;
expect(execution2LogArgs.transactionHash).to.equal(
transactionHashUtils.getTransactionHashHex(transaction2),
);
let fillLogIndex: number = 0;
let cancelLogIndex: number = 0;
const fillLogs = transactionReceipt.logs.filter((log, index) => {
if ((log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill') {
fillLogIndex = index;
return true;
}
return false;
});
const cancelLogs = transactionReceipt.logs.filter((log, index) => {
if ((log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel') {
cancelLogIndex = index;
return true;
}
return false;
});
expect(fillLogs.length).to.eq(1);
expect(cancelLogs.length).to.eq(1);
expect(cancelLogIndex).to.greaterThan(fillLogIndex);
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
expect(fillLogArgs.senderAddress).to.eq(senderAddress);
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(fillLogArgs.makerAssetData).to.eq(order1.makerAssetData);
expect(fillLogArgs.takerAssetData).to.eq(order1.takerAssetData);
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount);
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount);
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee);
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee);
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1));
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
expect(cancelLogArgs.senderAddress).to.eq(senderAddress);
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
expect(cancelLogArgs.makerAssetData).to.eq(order2.makerAssetData);
expect(cancelLogArgs.takerAssetData).to.eq(order2.takerAssetData);
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2));
});
it('should return the correct data for a fillOrder and cancelOrder call', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [
order2,
]);
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 });
const returnData = await exchangeInstance
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.callAsync({ from: senderAddress });
const abi = artifacts.Exchange.compilerOutput.abi;
const methodAbi = abi.filter(
abiItem => (abiItem as MethodAbi).name === ExchangeFunctionName.FillOrder,
)[0] as MethodAbi;
const abiEncoder = new AbiEncoder.Method(methodAbi);
const fillResults: FillResults = abiEncoder.decodeReturnValues(returnData[0]).fillResults;
expect(fillResults.makerAssetFilledAmount).to.be.bignumber.eq(order1.makerAssetAmount);
expect(fillResults.takerAssetFilledAmount).to.be.bignumber.eq(order1.takerAssetAmount);
expect(fillResults.makerFeePaid).to.be.bignumber.eq(order1.makerFee);
expect(fillResults.takerFeePaid).to.be.bignumber.eq(order1.takerFee);
expect(returnData[1]).to.eq(constants.NULL_BYTES);
});
it('should revert if a single transaction reverts', async () => {
const order = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const transaction1 = await makerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 });
const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress);
const nestedError = new ExchangeRevertErrors.OrderStatusError(
orderHashUtils.getOrderHashHex(order),
OrderStatus.Cancelled,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashUtils.getTransactionHashHex(transaction2),
nestedError,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if a single transaction is expired', async () => {
const order1 = await orderFactory.newSignedOrderAsync();
const order2 = await orderFactory.newSignedOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const currentTimestamp = await getLatestBlockTimestampAsync();
const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 });
const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({
data: data2,
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress);
const expiredTransactionHash = transactionHashUtils.getTransactionHashHex(transaction2);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.Expired,
expiredTransactionHash,
);
return expect(tx).to.revertWith(expectedError);
});
});
});
});

View File

@@ -191,10 +191,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
before(async () => {
order = await maker.signOrderAsync();
data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
transaction = await taker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
transaction = await taker.signTransactionAsync({ data });
approval = await feeRecipient.signCoordinatorApprovalAsync(transaction, taker.address);
});
@@ -309,10 +306,7 @@ blockchainTests.resets('Coordinator integration tests', env => {
before(async () => {
orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
transaction = await taker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
transaction = await taker.signTransactionAsync({ data });
approval = await feeRecipient.signCoordinatorApprovalAsync(transaction, taker.address);
});
@@ -400,7 +394,6 @@ blockchainTests.resets('Coordinator integration tests', env => {
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const transaction = await maker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
const txReceipt = await coordinator
.executeTransaction(transaction, maker.address, transaction.signature, [])
@@ -413,7 +406,6 @@ blockchainTests.resets('Coordinator integration tests', env => {
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders);
const transaction = await maker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
const txReceipt = await coordinator
.executeTransaction(transaction, maker.address, transaction.signature, [])
@@ -425,7 +417,6 @@ blockchainTests.resets('Coordinator integration tests', env => {
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo, []);
const transaction = await maker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
const txReceipt = await coordinator
.executeTransaction(transaction, maker.address, transaction.signature, [])

View File

@@ -0,0 +1,820 @@
// tslint:disable: max-file-line-count
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
import {
ExchangeCancelEventArgs,
ExchangeCancelUpToEventArgs,
exchangeDataEncoder,
ExchangeEvents,
ExchangeFillEventArgs,
ExchangeRevertErrors,
ExchangeSignatureValidatorApprovalEventArgs,
ExchangeTransactionExecutionEventArgs,
} from '@0x/contracts-exchange';
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
import {
blockchainTests,
constants,
describe,
ExchangeFunctionName,
expect,
getLatestBlockTimestampAsync,
hexConcat,
hexRandom,
orderHashUtils,
randomAddress,
transactionHashUtils,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { FillResults, OrderStatus, SignatureType, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import { Actor } from '../framework/actors/base';
import { FeeRecipient } from '../framework/actors/fee_recipient';
import { Maker } from '../framework/actors/maker';
import { Taker } from '../framework/actors/taker';
import { DeploymentManager } from '../framework/deployment_manager';
// tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets.only('Transaction integration tests', env => {
let deployment: DeploymentManager;
let maker: Maker;
let takers: [Taker, Taker];
let feeRecipient: FeeRecipient;
let sender: Actor;
before(async () => {
deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 4,
numErc721TokensToDeploy: 0,
numErc1155TokensToDeploy: 0,
});
const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20;
takers = [new Taker({ name: 'Taker 1', deployment }), new Taker({ name: 'Taker 2', deployment })];
feeRecipient = new FeeRecipient({
name: 'Fee recipient',
deployment,
});
maker = new Maker({
name: 'Maker',
deployment,
orderConfig: {
feeRecipientAddress: feeRecipient.address,
makerAssetData: assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(),
takerAssetData: assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(),
makerFeeAssetData: assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(),
takerFeeAssetData: assetDataEncoder.ERC20Token(takerFeeToken.address).getABIEncodedTransactionData(),
},
});
sender = new Actor({ name: 'Transaction sender', deployment });
for (const taker of takers) {
await taker.configureERC20TokenAsync(takerToken);
await taker.configureERC20TokenAsync(takerFeeToken);
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
}
await maker.configureERC20TokenAsync(makerToken);
await maker.configureERC20TokenAsync(makerFeeToken);
});
describe('executeTransaction', () => {
function defaultFillEvent(order: SignedOrder): ExchangeFillEventArgs {
return {
makerAddress: maker.address,
feeRecipientAddress: feeRecipient.address,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
makerFeeAssetData: order.makerFeeAssetData,
takerFeeAssetData: order.takerFeeAssetData,
orderHash: orderHashUtils.getOrderHashHex(order),
takerAddress: takers[0].address,
senderAddress: sender.address,
makerAssetFilledAmount: order.makerAssetAmount,
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee,
protocolFeePaid: DeploymentManager.protocolFee,
};
}
function defaultCancelEvent(order: SignedOrder): ExchangeCancelEventArgs {
return {
makerAddress: maker.address,
feeRecipientAddress: feeRecipient.address,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
senderAddress: sender.address,
orderHash: orderHashUtils.getOrderHashHex(order),
};
}
describe('general functionality', () => {
it('should log the correct transactionHash if successfully executed', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs(
transactionReceipt.logs,
[{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction) }],
ExchangeEvents.TransactionExecution,
);
});
it('should revert if the transaction is expired', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const currentTimestamp = await getLatestBlockTimestampAsync();
const transaction = await takers[0].signTransactionAsync({
data,
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.Expired,
transactionHashHex,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it('should revert if the actual gasPrice is greater than expected', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const actualGasPrice = transaction.gasPrice.plus(1);
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
transactionHashHex,
actualGasPrice,
transaction.gasPrice,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ gasPrice: actualGasPrice, from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it('should revert if the actual gasPrice is less than expected', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const actualGasPrice = transaction.gasPrice.minus(1);
const expectedError = new ExchangeRevertErrors.TransactionGasPriceError(
transactionHashHex,
actualGasPrice,
transaction.gasPrice,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ gasPrice: actualGasPrice, from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
});
describe('fill methods', () => {
for (const fnName of [
...constants.SINGLE_FILL_FN_NAMES,
...constants.BATCH_FILL_FN_NAMES,
...constants.MARKET_FILL_FN_NAMES,
]) {
it(`${fnName} should revert if signature is invalid and not called by signer`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
transaction.signature = hexConcat(hexRandom(65), SignatureType.EthSign);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.BadTransactionSignature,
transactionHashHex,
transaction.signerAddress,
transaction.signature,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should be successful if signed by taker and called by sender`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[defaultFillEvent(order)],
ExchangeEvents.Fill,
);
});
it(`${fnName} should be successful if called by taker without a transaction signature`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.awaitTransactionSuccessAsync({ from: takers[0].address });
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[{ ...defaultFillEvent(order), senderAddress: takers[0].address }],
ExchangeEvents.Fill,
);
});
it(`${fnName} should return the correct data if successful`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const returnData = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.callAsync({ from: sender.address });
const decodedReturnData = deployment.exchange.getABIDecodedReturnData(fnName, returnData);
const fillResults = Array.isArray(decodedReturnData) ? decodedReturnData[0] : decodedReturnData;
expect(fillResults).to.deep.equal(
ReferenceFunctions.calculateFillResults(
order,
order.takerAssetAmount,
DeploymentManager.protocolFeeMultiplier,
DeploymentManager.gasPrice,
),
);
});
it(`${fnName} should revert if transaction has already been executed`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted,
transactionHashHex,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should revert and rethrow error if executeTransaction is called recursively with a signature`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const recursiveData = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.getABIEncodedTransactionData();
const recursiveTransaction = await takers[0].signTransactionAsync({
data: recursiveData,
});
const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex(
recursiveTransaction,
);
const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError(
transactionHashHex,
transaction.signerAddress,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
recursiveTransactionHashHex,
noReentrancyError,
);
const tx = deployment.exchange
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it(`${fnName} should be successful if executeTransaction is called recursively by taker without a signature`, async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const recursiveData = deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.getABIEncodedTransactionData();
const recursiveTransaction = await takers[0].signTransactionAsync({
data: recursiveData,
});
const transactionReceipt = await deployment.exchange
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
.awaitTransactionSuccessAsync({ from: takers[0].address });
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[{ ...defaultFillEvent(order), senderAddress: takers[0].address }],
ExchangeEvents.Fill,
);
});
if (
[
ExchangeFunctionName.FillOrderNoThrow,
ExchangeFunctionName.BatchFillOrdersNoThrow,
ExchangeFunctionName.MarketBuyOrdersNoThrow,
ExchangeFunctionName.MarketSellOrdersNoThrow,
ExchangeFunctionName.MarketBuyOrdersFillOrKill,
ExchangeFunctionName.MarketSellOrdersFillOrKill,
].indexOf(fnName) === -1
) {
it(`${fnName} should revert and rethrow error if the underlying function reverts`, async () => {
const order = await maker.signOrderAsync();
order.signature = constants.NULL_BYTES;
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
orderHashUtils.getOrderHashHex(order),
order.makerAddress,
order.signature,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
}
}
});
describe('cancelOrder', () => {
it('should revert if not signed by or called by maker', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(order),
takers[0].address,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it('should be successful if signed by maker and called by sender', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const transaction = await maker.signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeCancelEventArgs>(
transactionReceipt.logs,
[defaultCancelEvent(order)],
ExchangeEvents.Cancel,
);
});
it('should be successful if called by maker without a signature', async () => {
const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const transaction = await maker.signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.awaitTransactionSuccessAsync({ from: maker.address });
verifyEventsFromLogs<ExchangeCancelEventArgs>(
transactionReceipt.logs,
[{ ...defaultCancelEvent(order), senderAddress: maker.address }],
ExchangeEvents.Cancel,
);
});
});
describe('batchCancelOrders', () => {
it('should revert if not signed by or called by maker', async () => {
const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await takers[0].signTransactionAsync({ data });
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError(
ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker,
orderHashUtils.getOrderHashHex(orders[0]),
takers[0].address,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashHex,
nestedError,
);
const tx = deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
return expect(tx).to.revertWith(expectedError);
});
it('should be successful if signed by maker and called by sender', async () => {
const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await maker.signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeCancelEventArgs>(
transactionReceipt.logs,
[defaultCancelEvent(orders[0]), defaultCancelEvent(orders[1])],
ExchangeEvents.Cancel,
);
});
it('should be successful if called by maker without a signature', async () => {
const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
ExchangeFunctionName.BatchCancelOrders,
orders,
);
const transaction = await maker.signTransactionAsync({ data });
transaction.signature = constants.NULL_BYTES;
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: maker.address });
verifyEventsFromLogs<ExchangeCancelEventArgs>(
transactionReceipt.logs,
[
{ ...defaultCancelEvent(orders[0]), senderAddress: maker.address },
{ ...defaultCancelEvent(orders[1]), senderAddress: maker.address },
],
ExchangeEvents.Cancel,
);
});
});
describe('cancelOrdersUpTo', () => {
it('should be successful if signed by maker and called by sender', async () => {
const targetEpoch = constants.ZERO_AMOUNT;
const data = deployment.exchange.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData();
const transaction = await maker.signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeCancelUpToEventArgs>(
transactionReceipt.logs,
[
{
makerAddress: maker.address,
orderSenderAddress: sender.address,
orderEpoch: targetEpoch.plus(1),
},
],
ExchangeEvents.CancelUpTo,
);
});
it('should be successful if called by maker without a signature', async () => {
const targetEpoch = constants.ZERO_AMOUNT;
const data = deployment.exchange.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData();
const transaction = await maker.signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.awaitTransactionSuccessAsync({ from: maker.address });
verifyEventsFromLogs<ExchangeCancelUpToEventArgs>(
transactionReceipt.logs,
[
{
makerAddress: maker.address,
orderSenderAddress: constants.NULL_ADDRESS,
orderEpoch: targetEpoch.plus(1),
},
],
ExchangeEvents.CancelUpTo,
);
});
describe('preSign', () => {
it('should preSign a hash for the signer', async () => {
const order = await maker.signOrderAsync();
const orderHash = orderHashUtils.getOrderHashHex(order);
const data = deployment.exchange.preSign(orderHash).getABIEncodedTransactionData();
const transaction = await takers[0].signTransactionAsync({ data });
let isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync();
expect(isPreSigned).to.be.eq(false);
await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync();
expect(isPreSigned).to.be.eq(true);
});
it('should preSign a hash for the caller if called without a signature', async () => {
const order = await maker.signOrderAsync();
const orderHash = orderHashUtils.getOrderHashHex(order);
const data = deployment.exchange.preSign(orderHash).getABIEncodedTransactionData();
const transaction = await takers[0].signTransactionAsync({ data });
let isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync();
expect(isPreSigned).to.be.eq(false);
await deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.awaitTransactionSuccessAsync({ from: takers[0].address });
isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync();
expect(isPreSigned).to.be.eq(true);
});
});
describe('setSignatureValidatorApproval', () => {
it('should approve a validator for the signer', async () => {
const validatorAddress = randomAddress();
const shouldApprove = true;
const data = deployment.exchange
.setSignatureValidatorApproval(validatorAddress, shouldApprove)
.getABIEncodedTransactionData();
const transaction = await takers[0].signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, transaction.signature)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeSignatureValidatorApprovalEventArgs>(
transactionReceipt.logs,
[
{
signerAddress: takers[0].address,
validatorAddress,
isApproved: shouldApprove,
},
],
ExchangeEvents.SignatureValidatorApproval,
);
});
it('should approve a validator for the caller if called with no signature', async () => {
const validatorAddress = randomAddress();
const shouldApprove = true;
const data = deployment.exchange
.setSignatureValidatorApproval(validatorAddress, shouldApprove)
.getABIEncodedTransactionData();
const transaction = await takers[0].signTransactionAsync({ data });
const transactionReceipt = await deployment.exchange
.executeTransaction(transaction, constants.NULL_BYTES)
.awaitTransactionSuccessAsync({ from: takers[0].address });
verifyEventsFromLogs<ExchangeSignatureValidatorApprovalEventArgs>(
transactionReceipt.logs,
[
{
signerAddress: takers[0].address,
validatorAddress,
isApproved: shouldApprove,
},
],
ExchangeEvents.SignatureValidatorApproval,
);
});
});
});
describe('batchExecuteTransactions', () => {
it('should successfully call fillOrder via 2 transactions with different taker signatures', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await takers[1].signTransactionAsync({ data: data2 });
const transactionReceipt = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeTransactionExecutionEventArgs>(
transactionReceipt.logs,
[
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) },
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) },
],
ExchangeEvents.TransactionExecution,
);
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[defaultFillEvent(order1), { ...defaultFillEvent(order2), takerAddress: takers[1].address }],
ExchangeEvents.Fill,
);
});
it('should successfully call fillOrder via 2 transactions when called by taker with no signatures', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await takers[0].signTransactionAsync({ data: data2 });
transaction1.signature = constants.NULL_BYTES;
transaction2.signature = constants.NULL_BYTES;
const transactionReceipt = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.awaitTransactionSuccessAsync({ from: takers[0].address });
verifyEventsFromLogs<ExchangeTransactionExecutionEventArgs>(
transactionReceipt.logs,
[
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) },
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) },
],
ExchangeEvents.TransactionExecution,
);
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[
{ ...defaultFillEvent(order1), senderAddress: takers[0].address },
{ ...defaultFillEvent(order2), senderAddress: takers[0].address },
],
ExchangeEvents.Fill,
);
});
it('should successfully call fillOrder via 2 transactions when one is signed by taker1 and executeTransaction is called by taker2', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await takers[1].signTransactionAsync({ data: data2 });
const transactionReceipt = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, constants.NULL_BYTES],
)
.awaitTransactionSuccessAsync({ from: takers[1].address });
verifyEventsFromLogs<ExchangeTransactionExecutionEventArgs>(
transactionReceipt.logs,
[
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) },
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) },
],
ExchangeEvents.TransactionExecution,
);
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[
{ ...defaultFillEvent(order1), senderAddress: takers[1].address },
{
...defaultFillEvent(order2),
takerAddress: takers[1].address,
senderAddress: takers[1].address,
},
],
ExchangeEvents.Fill,
);
});
it('should return the correct data for 2 different fillOrder calls', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await takers[1].signTransactionAsync({ data: data2 });
const returnData = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.callAsync({ from: sender.address });
const fillResults1: FillResults = deployment.exchange.getABIDecodedReturnData(
'fillOrder',
returnData[0],
);
const fillResults2: FillResults = deployment.exchange.getABIDecodedReturnData(
'fillOrder',
returnData[1],
);
expect(fillResults1).to.deep.equal(
ReferenceFunctions.calculateFillResults(
order1,
order1.takerAssetAmount,
DeploymentManager.protocolFeeMultiplier,
DeploymentManager.gasPrice,
),
);
expect(fillResults2).to.deep.equal(
ReferenceFunctions.calculateFillResults(
order2,
order2.takerAssetAmount,
DeploymentManager.protocolFeeMultiplier,
DeploymentManager.gasPrice,
),
);
});
it('should successfully call fillOrder and cancelOrder via 2 transactions', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [
order2,
]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await maker.signTransactionAsync({ data: data2 });
const transactionReceipt = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.awaitTransactionSuccessAsync({ from: sender.address });
verifyEventsFromLogs<ExchangeTransactionExecutionEventArgs>(
transactionReceipt.logs,
[
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) },
{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) },
],
ExchangeEvents.TransactionExecution,
);
const fillLogIndex = transactionReceipt.logs.findIndex(
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
);
const cancelLogIndex = transactionReceipt.logs.findIndex(
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
);
expect(cancelLogIndex).to.greaterThan(fillLogIndex);
verifyEventsFromLogs<ExchangeFillEventArgs>(
transactionReceipt.logs,
[defaultFillEvent(order1)],
ExchangeEvents.Fill,
);
verifyEventsFromLogs<ExchangeCancelEventArgs>(
transactionReceipt.logs,
[defaultCancelEvent(order2)],
ExchangeEvents.Cancel,
);
});
it('should return the correct data for a fillOrder and cancelOrder call', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [
order2,
]);
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await maker.signTransactionAsync({ data: data2 });
const returnData = await deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.callAsync({ from: sender.address });
const fillResults: FillResults = deployment.exchange.getABIDecodedReturnData(
'fillOrder',
returnData[0],
);
expect(fillResults).to.deep.equal(
ReferenceFunctions.calculateFillResults(
order1,
order1.takerAssetAmount,
DeploymentManager.protocolFeeMultiplier,
DeploymentManager.gasPrice,
),
);
expect(returnData[1]).to.eq(constants.NULL_BYTES);
});
it('should revert if a single transaction reverts', async () => {
const order = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
const transaction1 = await maker.signTransactionAsync({ data: data1 });
const transaction2 = await takers[0].signTransactionAsync({ data: data2 });
const tx = deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.awaitTransactionSuccessAsync({ from: sender.address });
const nestedError = new ExchangeRevertErrors.OrderStatusError(
orderHashUtils.getOrderHashHex(order),
OrderStatus.Cancelled,
).encode();
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
transactionHashUtils.getTransactionHashHex(transaction2),
nestedError,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if a single transaction is expired', async () => {
const order1 = await maker.signOrderAsync();
const order2 = await maker.signOrderAsync();
const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]);
const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]);
const currentTimestamp = await getLatestBlockTimestampAsync();
const transaction1 = await takers[0].signTransactionAsync({ data: data1 });
const transaction2 = await takers[1].signTransactionAsync({
data: data2,
expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
});
const tx = deployment.exchange
.batchExecuteTransactions(
[transaction1, transaction2],
[transaction1.signature, transaction2.signature],
)
.awaitTransactionSuccessAsync({ from: sender.address });
const expiredTransactionHash = transactionHashUtils.getTransactionHashHex(transaction2);
const expectedError = new ExchangeRevertErrors.TransactionError(
ExchangeRevertErrors.TransactionErrorCode.Expired,
expiredTransactionHash,
);
return expect(tx).to.revertWith(expectedError);
});
});
});
});

View File

@@ -142,6 +142,12 @@ export class Actor {
customTransactionParams: Partial<ZeroExTransaction>,
signatureType: SignatureType = SignatureType.EthSign,
): Promise<SignedZeroExTransaction> {
return this._transactionFactory.newSignedTransactionAsync(customTransactionParams, signatureType);
return this._transactionFactory.newSignedTransactionAsync(
{
gasPrice: DeploymentManager.gasPrice,
...customTransactionParams,
},
signatureType,
);
}
}

View File

@@ -1,4 +1,4 @@
import { constants, OrderFactory, orderUtils } from '@0x/contracts-test-utils';
import { constants, OrderFactory } from '@0x/contracts-test-utils';
import { Order, SignedOrder } from '@0x/types';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
@@ -58,8 +58,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
* Cancels one of the maker's orders.
*/
public async cancelOrderAsync(order: SignedOrder): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createCancel(order);
return this.actor.deployment.exchange.cancelOrder(params.order).awaitTransactionSuccessAsync({
return this.actor.deployment.exchange.cancelOrder(order).awaitTransactionSuccessAsync({
from: this.actor.address,
});
}