807 lines
44 KiB
TypeScript
807 lines
44 KiB
TypeScript
// tslint:disable: max-file-line-count
|
|
import { encodeERC20AssetData } 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,
|
|
orderHashUtils,
|
|
randomAddress,
|
|
transactionHashUtils,
|
|
verifyEventsFromLogs,
|
|
} from '@0x/contracts-test-utils';
|
|
import { FillResults, OrderStatus, SignatureType, SignedOrder } from '@0x/types';
|
|
import { BigNumber, hexUtils } 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('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 [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: encodeERC20AssetData(makerToken.address),
|
|
takerAssetData: encodeERC20AssetData(takerToken.address),
|
|
makerFeeAssetData: encodeERC20AssetData(makerFeeToken.address),
|
|
takerFeeAssetData: encodeERC20AssetData(takerFeeToken.address),
|
|
},
|
|
});
|
|
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);
|
|
});
|
|
|
|
after(async () => {
|
|
Actor.reset();
|
|
});
|
|
|
|
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('executeTransaction', () => {
|
|
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 = hexUtils.concat(hexUtils.random(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);
|
|
});
|
|
});
|
|
});
|