Merge pull request #2357 from 0xProject/refactor/integrations/transaction-tests
`@0x/contracts-integrations`: Transaction integration tests
This commit is contained in:
commit
8685cf9036
@ -198,7 +198,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
}
|
||||
|
||||
function getDeterministicOrderInfo(order: Order): OrderInfo {
|
||||
const hash = getPackedHash(toHex(order.salt, 32));
|
||||
const hash = getPackedHash(hexLeftPad(order.salt, 32));
|
||||
return {
|
||||
orderHash: hash,
|
||||
orderStatus: new BigNumber(hash).mod(255).toNumber(),
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -19,7 +19,6 @@ import {
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
|
||||
import { Actor } from '../framework/actors/base';
|
||||
import { FeeRecipient } from '../framework/actors/fee_recipient';
|
||||
@ -96,72 +95,9 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
async function simulateFillsAsync(
|
||||
orders: SignedOrder[],
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
msgValue?: BigNumber,
|
||||
): Promise<LocalBalanceStore> {
|
||||
let remainingValue = msgValue || constants.ZERO_AMOUNT;
|
||||
const localBalanceStore = LocalBalanceStore.create(balanceStore);
|
||||
// Transaction gas cost
|
||||
localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
for (const order of orders) {
|
||||
// Taker -> Maker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
order.takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
order.makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
// Taker -> Fee Recipient
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
feeRecipient.address,
|
||||
order.takerFee,
|
||||
order.takerFeeAssetData,
|
||||
);
|
||||
// Maker -> Fee Recipient
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
feeRecipient.address,
|
||||
order.makerFee,
|
||||
order.makerFeeAssetData,
|
||||
);
|
||||
|
||||
// Protocol fee
|
||||
if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) {
|
||||
localBalanceStore.sendEth(
|
||||
txReceipt.from,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
);
|
||||
remainingValue = remainingValue.minus(DeploymentManager.protocolFee);
|
||||
} else {
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
deployment.assetDataEncoder
|
||||
.ERC20Token(deployment.tokens.weth.address)
|
||||
.getABIEncodedTransactionData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return localBalanceStore;
|
||||
}
|
||||
|
||||
function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs {
|
||||
return {
|
||||
makerAddress: order.makerAddress,
|
||||
@ -191,10 +127,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);
|
||||
});
|
||||
|
||||
@ -204,7 +137,14 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, taker.address, transaction.signature, [approval.signature])
|
||||
.awaitTransactionSuccessAsync({ from: taker.address, value: DeploymentManager.protocolFee });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(
|
||||
[order],
|
||||
taker.address,
|
||||
txReceipt,
|
||||
deployment,
|
||||
DeploymentManager.protocolFee,
|
||||
);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@ -215,7 +155,14 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, feeRecipient.address, transaction.signature, [])
|
||||
.awaitTransactionSuccessAsync({ from: feeRecipient.address, value: DeploymentManager.protocolFee });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(
|
||||
[order],
|
||||
taker.address,
|
||||
txReceipt,
|
||||
deployment,
|
||||
DeploymentManager.protocolFee,
|
||||
);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@ -229,9 +176,12 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
value: DeploymentManager.protocolFee.plus(1),
|
||||
});
|
||||
|
||||
const expectedBalances = await simulateFillsAsync(
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(
|
||||
[order],
|
||||
taker.address,
|
||||
txReceipt,
|
||||
deployment,
|
||||
DeploymentManager.protocolFee.plus(1),
|
||||
);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
@ -244,7 +194,8 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, feeRecipient.address, transaction.signature, [])
|
||||
.awaitTransactionSuccessAsync({ from: feeRecipient.address });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills([order], taker.address, txReceipt, deployment);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@ -255,7 +206,8 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, feeRecipient.address, transaction.signature, [])
|
||||
.awaitTransactionSuccessAsync({ from: feeRecipient.address, value: new BigNumber(1) });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync([order], txReceipt, new BigNumber(1));
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills([order], taker.address, txReceipt, deployment, new BigNumber(1));
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
|
||||
@ -309,10 +261,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);
|
||||
});
|
||||
|
||||
@ -323,7 +272,8 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, taker.address, transaction.signature, [approval.signature])
|
||||
.awaitTransactionSuccessAsync({ from: taker.address, value });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
@ -335,7 +285,8 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, feeRecipient.address, transaction.signature, [])
|
||||
.awaitTransactionSuccessAsync({ from: feeRecipient.address, value });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
@ -347,7 +298,8 @@ blockchainTests.resets('Coordinator integration tests', env => {
|
||||
.executeTransaction(transaction, feeRecipient.address, transaction.signature, [])
|
||||
.awaitTransactionSuccessAsync({ from: feeRecipient.address, value });
|
||||
|
||||
const expectedBalances = await simulateFillsAsync(orders, txReceipt, value);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
|
||||
@ -400,7 +352,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 +364,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 +375,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, [])
|
||||
|
@ -146,7 +146,7 @@ blockchainTests.resets('matchOrders integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
describe('batchMatchOrders and batchMatchOrdersWithMaximalFill rich errors', async () => {
|
||||
|
@ -124,7 +124,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
interface SignedOrderWithValidity {
|
||||
@ -132,13 +132,9 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
async function simulateFillAsync(
|
||||
signedOrder: SignedOrder,
|
||||
expectedFillResults: FillResults,
|
||||
shouldUseWeth: boolean,
|
||||
): Promise<void> {
|
||||
function simulateFill(signedOrder: SignedOrder, expectedFillResults: FillResults, shouldUseWeth: boolean): void {
|
||||
// taker -> maker
|
||||
await localBalances.transferAssetAsync(
|
||||
localBalances.transferAsset(
|
||||
taker.address,
|
||||
maker.address,
|
||||
expectedFillResults.takerAssetFilledAmount,
|
||||
@ -146,7 +142,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// maker -> taker
|
||||
await localBalances.transferAssetAsync(
|
||||
localBalances.transferAsset(
|
||||
maker.address,
|
||||
taker.address,
|
||||
expectedFillResults.makerAssetFilledAmount,
|
||||
@ -154,7 +150,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// maker -> feeRecipient
|
||||
await localBalances.transferAssetAsync(
|
||||
localBalances.transferAsset(
|
||||
maker.address,
|
||||
feeRecipient,
|
||||
expectedFillResults.makerFeePaid,
|
||||
@ -162,7 +158,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
);
|
||||
|
||||
// taker -> feeRecipient
|
||||
await localBalances.transferAssetAsync(
|
||||
localBalances.transferAsset(
|
||||
taker.address,
|
||||
feeRecipient,
|
||||
expectedFillResults.takerFeePaid,
|
||||
@ -171,7 +167,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
|
||||
// taker -> protocol fees
|
||||
if (shouldUseWeth) {
|
||||
await localBalances.transferAssetAsync(
|
||||
localBalances.transferAsset(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
expectedFillResults.protocolFeePaid,
|
||||
@ -343,7 +339,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
const shouldPayWethFees = DeploymentManager.protocolFee.gt(value);
|
||||
|
||||
// Simulate filling the order
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
|
||||
// Ensure that the correct logs were emitted and that the balances are accurate.
|
||||
await assertResultsAsync(receipt, [{ signedOrder, expectedFillResults, shouldPayWethFees }]);
|
||||
@ -444,7 +440,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
}
|
||||
|
||||
const contractFn = deployment.exchange.batchFillOrders(
|
||||
@ -506,7 +502,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
}
|
||||
|
||||
const contractFn = deployment.exchange.batchFillOrKillOrders(
|
||||
@ -600,7 +596,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
}
|
||||
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
} else {
|
||||
totalFillResults.push(nullFillResults);
|
||||
}
|
||||
@ -714,7 +710,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
takerFillAmount,
|
||||
);
|
||||
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
|
||||
totalFillResults = addFillResults(totalFillResults, expectedFillResults);
|
||||
@ -912,7 +908,7 @@ blockchainTests.resets('Exchange wrappers', env => {
|
||||
makerAssetBought,
|
||||
);
|
||||
|
||||
await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
simulateFill(signedOrder, expectedFillResults, shouldPayWethFees);
|
||||
fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees });
|
||||
|
||||
totalFillResults = addFillResults(totalFillResults, expectedFillResults);
|
||||
|
@ -99,7 +99,7 @@ export class FillOrderWrapper {
|
||||
await this._assertOrderStateAsync(signedOrder, initTakerAssetFilledAmount);
|
||||
// Simulate and execute fill then assert outputs
|
||||
const [fillResults, fillEvent, txReceipt] = await this._fillOrderAsync(signedOrder, from, opts);
|
||||
const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = await simulateFillOrderAsync(
|
||||
const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = simulateFillOrder(
|
||||
txReceipt,
|
||||
signedOrder,
|
||||
from,
|
||||
@ -169,13 +169,13 @@ export class FillOrderWrapper {
|
||||
* @param initBalanceStore Account balances prior to the fill.
|
||||
* @return The expected account balances, fill results, and fill events.
|
||||
*/
|
||||
async function simulateFillOrderAsync(
|
||||
function simulateFillOrder(
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
signedOrder: SignedOrder,
|
||||
takerAddress: string,
|
||||
initBalanceStore: BalanceStore,
|
||||
opts: { takerAssetFillAmount?: BigNumber } = {},
|
||||
): Promise<[FillResults, FillEventArgs, BalanceStore]> {
|
||||
): [FillResults, FillEventArgs, BalanceStore] {
|
||||
const balanceStore = LocalBalanceStore.create(initBalanceStore);
|
||||
const takerAssetFillAmount =
|
||||
opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount;
|
||||
@ -188,28 +188,28 @@ async function simulateFillOrderAsync(
|
||||
);
|
||||
const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults);
|
||||
// Taker -> Maker
|
||||
await balanceStore.transferAssetAsync(
|
||||
balanceStore.transferAsset(
|
||||
takerAddress,
|
||||
signedOrder.makerAddress,
|
||||
fillResults.takerAssetFilledAmount,
|
||||
signedOrder.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
await balanceStore.transferAssetAsync(
|
||||
balanceStore.transferAsset(
|
||||
signedOrder.makerAddress,
|
||||
takerAddress,
|
||||
fillResults.makerAssetFilledAmount,
|
||||
signedOrder.makerAssetData,
|
||||
);
|
||||
// Taker -> Fee Recipient
|
||||
await balanceStore.transferAssetAsync(
|
||||
balanceStore.transferAsset(
|
||||
takerAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
fillResults.takerFeePaid,
|
||||
signedOrder.takerFeeAssetData,
|
||||
);
|
||||
// Maker -> Fee Recipient
|
||||
await balanceStore.transferAssetAsync(
|
||||
balanceStore.transferAsset(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
fillResults.makerFeePaid,
|
||||
|
@ -109,54 +109,9 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
async function simulateFillAsync(
|
||||
order: SignedOrder,
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
msgValue?: BigNumber,
|
||||
): Promise<LocalBalanceStore> {
|
||||
let remainingValue = msgValue !== undefined ? msgValue : DeploymentManager.protocolFee;
|
||||
const localBalanceStore = LocalBalanceStore.create(balanceStore);
|
||||
// Transaction gas cost
|
||||
localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
// Taker -> Maker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
order.takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
order.makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
|
||||
// Protocol fee
|
||||
if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) {
|
||||
localBalanceStore.sendEth(
|
||||
txReceipt.from,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
);
|
||||
remainingValue = remainingValue.minus(DeploymentManager.protocolFee);
|
||||
} else {
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
deployment.assetDataEncoder.ERC20Token(deployment.tokens.weth.address).getABIEncodedTransactionData(),
|
||||
);
|
||||
}
|
||||
|
||||
return localBalanceStore;
|
||||
}
|
||||
|
||||
function verifyFillEvents(order: SignedOrder, receipt: TransactionReceiptWithDecodedLogs): void {
|
||||
// Ensure that the fill event was correct.
|
||||
verifyEvents<ExchangeFillEventArgs>(
|
||||
@ -207,7 +162,8 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
|
||||
// Check balances
|
||||
const expectedBalances = await simulateFillAsync(order, receipt);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@ -233,7 +189,8 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount);
|
||||
|
||||
// Check balances
|
||||
const expectedBalances = await simulateFillAsync(order, receipt);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@ -310,7 +267,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);
|
||||
|
||||
// Check balances
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(
|
||||
deployment.staking.stakingProxy.address,
|
||||
operator.address,
|
||||
operatorReward,
|
||||
@ -371,7 +328,8 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const order = await maker.signOrderAsync();
|
||||
const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount, { value: constants.ZERO_AMOUNT });
|
||||
const rewardsAvailable = DeploymentManager.protocolFee;
|
||||
const expectedBalances = await simulateFillAsync(order, receipt, constants.ZERO_AMOUNT);
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.simulateFills([order], taker.address, receipt, deployment);
|
||||
|
||||
// End the epoch. This should wrap the staking proxy's ETH balance.
|
||||
const endEpochReceipt = await delegator.endEpochAsync();
|
||||
@ -392,7 +350,7 @@ blockchainTests.resets('fillOrder integration tests', env => {
|
||||
const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);
|
||||
|
||||
// Check balances
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(
|
||||
deployment.staking.stakingProxy.address,
|
||||
operator.address,
|
||||
operatorReward,
|
||||
|
@ -296,7 +296,7 @@ export class MatchOrderTester {
|
||||
localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed));
|
||||
|
||||
// Simulate the fill.
|
||||
const expectedMatchResults = await this._simulateMatchOrdersAsync(
|
||||
const expectedMatchResults = this._simulateMatchOrders(
|
||||
orders,
|
||||
takerAddress,
|
||||
toFullMatchTransferAmounts(expectedTransferAmounts),
|
||||
@ -319,12 +319,12 @@ export class MatchOrderTester {
|
||||
* @param localBalanceStore The balance store to use for the simulation.
|
||||
* @return The new account balances and fill events that occurred during the match.
|
||||
*/
|
||||
protected async _simulateMatchOrdersAsync(
|
||||
protected _simulateMatchOrders(
|
||||
orders: MatchedOrders,
|
||||
takerAddress: string,
|
||||
transferAmounts: MatchTransferAmounts,
|
||||
localBalanceStore: LocalBalanceStore,
|
||||
): Promise<MatchResults> {
|
||||
): MatchResults {
|
||||
// prettier-ignore
|
||||
const matchResults = {
|
||||
orders: {
|
||||
@ -343,7 +343,7 @@ export class MatchOrderTester {
|
||||
};
|
||||
|
||||
// Right maker asset -> left maker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.rightOrder.makerAddress,
|
||||
orders.leftOrder.makerAddress,
|
||||
transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
|
||||
@ -352,7 +352,7 @@ export class MatchOrderTester {
|
||||
|
||||
if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) {
|
||||
// Left maker fees
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.leftOrder.makerAddress,
|
||||
orders.leftOrder.feeRecipientAddress,
|
||||
transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
|
||||
@ -361,7 +361,7 @@ export class MatchOrderTester {
|
||||
}
|
||||
|
||||
// Left maker asset -> right maker
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.leftOrder.makerAddress,
|
||||
orders.rightOrder.makerAddress,
|
||||
transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
|
||||
@ -370,7 +370,7 @@ export class MatchOrderTester {
|
||||
|
||||
if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) {
|
||||
// Right maker fees
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.rightOrder.makerAddress,
|
||||
orders.rightOrder.feeRecipientAddress,
|
||||
transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
|
||||
@ -379,7 +379,7 @@ export class MatchOrderTester {
|
||||
}
|
||||
|
||||
// Left taker profit
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.leftOrder.makerAddress,
|
||||
takerAddress,
|
||||
transferAmounts.leftMakerAssetReceivedByTakerAmount,
|
||||
@ -387,7 +387,7 @@ export class MatchOrderTester {
|
||||
);
|
||||
|
||||
// Right taker profit
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
orders.rightOrder.makerAddress,
|
||||
takerAddress,
|
||||
transferAmounts.rightMakerAssetReceivedByTakerAmount,
|
||||
@ -395,7 +395,7 @@ export class MatchOrderTester {
|
||||
);
|
||||
|
||||
// Left taker fees
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
takerAddress,
|
||||
orders.leftOrder.feeRecipientAddress,
|
||||
transferAmounts.leftTakerFeeAssetPaidByTakerAmount,
|
||||
@ -403,7 +403,7 @@ export class MatchOrderTester {
|
||||
);
|
||||
|
||||
// Right taker fees
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
takerAddress,
|
||||
orders.rightOrder.feeRecipientAddress,
|
||||
transferAmounts.rightTakerFeeAssetPaidByTakerAmount,
|
||||
@ -424,13 +424,13 @@ export class MatchOrderTester {
|
||||
this._deployment.staking.stakingProxy.address,
|
||||
transferAmounts.rightProtocolFeePaidByTakerInEthAmount,
|
||||
);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
takerAddress,
|
||||
this._deployment.staking.stakingProxy.address,
|
||||
transferAmounts.leftProtocolFeePaidByTakerInWethAmount,
|
||||
wethAssetData,
|
||||
);
|
||||
await localBalanceStore.transferAssetAsync(
|
||||
localBalanceStore.transferAsset(
|
||||
takerAddress,
|
||||
this._deployment.staking.stakingProxy.address,
|
||||
transferAmounts.rightProtocolFeePaidByTakerInWethAmount,
|
||||
@ -513,7 +513,7 @@ export class MatchOrderTester {
|
||||
|
||||
// Add the latest match to the batch match results
|
||||
batchMatchResults.matches.push(
|
||||
await this._simulateMatchOrdersAsync(
|
||||
this._simulateMatchOrders(
|
||||
matchedOrders,
|
||||
takerAddress,
|
||||
toFullMatchTransferAmounts(transferAmounts[i]),
|
||||
|
@ -163,7 +163,7 @@ blockchainTests.resets('matchOrdersWithMaximalFill integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
describe('matchOrdersWithMaximalFill', () => {
|
||||
|
@ -163,7 +163,7 @@ blockchainTests.resets('matchOrders integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
describe('matchOrders', () => {
|
||||
|
@ -0,0 +1,500 @@
|
||||
// tslint:disable: max-file-line-count
|
||||
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
|
||||
import { exchangeDataEncoder, ExchangeRevertErrors } from '@0x/contracts-exchange';
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
describe,
|
||||
ExchangeFunctionName,
|
||||
expect,
|
||||
orderHashUtils,
|
||||
transactionHashUtils,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
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 { actorAddressesByName } from '../framework/actors/utils';
|
||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||
import { LocalBalanceStore } from '../framework/balances/local_balance_store';
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests.resets('Transaction <> protocol fee integration tests', env => {
|
||||
let deployment: DeploymentManager;
|
||||
let balanceStore: BlockchainBalanceStore;
|
||||
|
||||
let maker: Maker;
|
||||
let feeRecipient: FeeRecipient;
|
||||
let alice: Taker;
|
||||
let bob: Taker;
|
||||
let charlie: Taker;
|
||||
let wethless: Taker; // Used to test revert scenarios
|
||||
|
||||
let order: SignedOrder; // All orders will have the same fields, modulo salt and expiration time
|
||||
let transactionA: SignedZeroExTransaction; // fillOrder transaction signed by Alice
|
||||
let transactionB: SignedZeroExTransaction; // fillOrder transaction signed by Bob
|
||||
let transactionC: SignedZeroExTransaction; // fillOrder transaction signed by Charlie
|
||||
|
||||
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;
|
||||
|
||||
alice = new Taker({ name: 'Alice', deployment });
|
||||
bob = new Taker({ name: 'Bob', deployment });
|
||||
charlie = new Taker({ name: 'Charlie', deployment });
|
||||
wethless = new Taker({ name: 'wethless', 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(),
|
||||
},
|
||||
});
|
||||
|
||||
for (const taker of [alice, bob, charlie]) {
|
||||
await taker.configureERC20TokenAsync(takerToken);
|
||||
await taker.configureERC20TokenAsync(takerFeeToken);
|
||||
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
|
||||
}
|
||||
await wethless.configureERC20TokenAsync(takerToken);
|
||||
await wethless.configureERC20TokenAsync(takerFeeToken);
|
||||
await wethless.configureERC20TokenAsync(
|
||||
deployment.tokens.weth,
|
||||
deployment.staking.stakingProxy.address,
|
||||
constants.ZERO_AMOUNT, // wethless taker has approved the proxy, but has no weth
|
||||
);
|
||||
await maker.configureERC20TokenAsync(makerToken);
|
||||
await maker.configureERC20TokenAsync(makerFeeToken);
|
||||
|
||||
balanceStore = new BlockchainBalanceStore(
|
||||
{
|
||||
...actorAddressesByName([alice, bob, charlie, maker, feeRecipient]),
|
||||
StakingProxy: deployment.staking.stakingProxy.address,
|
||||
},
|
||||
{ erc20: { makerToken, takerToken, makerFeeToken, takerFeeToken, wETH: deployment.tokens.weth } },
|
||||
{},
|
||||
);
|
||||
await balanceStore.updateBalancesAsync();
|
||||
|
||||
order = await maker.signOrderAsync();
|
||||
let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
transactionA = await alice.signTransactionAsync({ data });
|
||||
|
||||
order = await maker.signOrderAsync();
|
||||
data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
transactionB = await bob.signTransactionAsync({ data });
|
||||
|
||||
order = await maker.signOrderAsync();
|
||||
data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
transactionC = await charlie.signTransactionAsync({ data });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
const REFUND_AMOUNT = new BigNumber(1);
|
||||
|
||||
function protocolFeeError(
|
||||
failedOrder: SignedOrder,
|
||||
failedTransaction: SignedZeroExTransaction,
|
||||
): ExchangeRevertErrors.TransactionExecutionError {
|
||||
const nestedError = new ExchangeRevertErrors.PayProtocolFeeError(
|
||||
orderHashUtils.getOrderHashHex(failedOrder),
|
||||
DeploymentManager.protocolFee,
|
||||
maker.address,
|
||||
wethless.address,
|
||||
'0x',
|
||||
).encode();
|
||||
return new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHashUtils.getTransactionHashHex(failedTransaction),
|
||||
nestedError,
|
||||
);
|
||||
}
|
||||
|
||||
describe('executeTransaction', () => {
|
||||
const ETH_FEE_WITH_REFUND = DeploymentManager.protocolFee.plus(REFUND_AMOUNT);
|
||||
|
||||
let expectedBalances: LocalBalanceStore;
|
||||
beforeEach(async () => {
|
||||
await balanceStore.updateBalancesAsync();
|
||||
expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
|
||||
describe('Simple', () => {
|
||||
it('Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => {
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(transactionA, transactionA.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
it('Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => {
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(transactionB, transactionB.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
it('Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => {
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(transactionA, transactionA.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT);
|
||||
});
|
||||
it('Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => {
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(transactionB, transactionB.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT);
|
||||
});
|
||||
it('Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
const transaction = await wethless.signTransactionAsync({ data });
|
||||
const tx = deployment.exchange
|
||||
.executeTransaction(transaction, transaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
return expect(tx).to.revertWith(protocolFeeError(order, transaction));
|
||||
});
|
||||
it('Alice executeTransaction => Alice batchFillOrders; mixed protocol fees', async () => {
|
||||
const orders = [order, await maker.signOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
|
||||
ExchangeFunctionName.BatchFillOrders,
|
||||
orders,
|
||||
);
|
||||
const batchFillTransaction = await alice.signTransactionAsync({ data });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(batchFillTransaction, batchFillTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills(orders, alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
it('Alice executeTransaction => Bob batchFillOrders; mixed protocol fees', async () => {
|
||||
const orders = [order, await maker.signOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(
|
||||
ExchangeFunctionName.BatchFillOrders,
|
||||
orders,
|
||||
);
|
||||
const batchFillTransaction = await bob.signTransactionAsync({ data });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(batchFillTransaction, batchFillTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills(orders, bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
});
|
||||
describe('Nested', () => {
|
||||
it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => {
|
||||
const recursiveData = deployment.exchange
|
||||
.executeTransaction(transactionA, transactionA.signature)
|
||||
.getABIEncodedTransactionData();
|
||||
const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => {
|
||||
const recursiveData = deployment.exchange
|
||||
.executeTransaction(transactionB, transactionB.signature)
|
||||
.getABIEncodedTransactionData();
|
||||
const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
|
||||
expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
|
||||
});
|
||||
it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => {
|
||||
const recursiveData = deployment.exchange
|
||||
.executeTransaction(transactionA, transactionA.signature)
|
||||
.getABIEncodedTransactionData();
|
||||
const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT);
|
||||
});
|
||||
it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => {
|
||||
const recursiveData = deployment.exchange
|
||||
.executeTransaction(transactionB, transactionB.signature)
|
||||
.getABIEncodedTransactionData();
|
||||
const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT);
|
||||
});
|
||||
it('Alice executeTransaction => Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
const transaction = await wethless.signTransactionAsync({ data });
|
||||
const recursiveData = deployment.exchange
|
||||
.executeTransaction(transaction, transaction.signature)
|
||||
.getABIEncodedTransactionData();
|
||||
const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
const tx = deployment.exchange
|
||||
.executeTransaction(recursiveTransaction, recursiveTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
|
||||
transactionHashUtils.getTransactionHashHex(recursiveTransaction),
|
||||
protocolFeeError(order, transaction).encode(),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('batchExecuteTransactions', () => {
|
||||
let expectedBalances: LocalBalanceStore;
|
||||
beforeEach(async () => {
|
||||
await balanceStore.updateBalancesAsync();
|
||||
expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await balanceStore.updateBalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
});
|
||||
|
||||
describe('Simple', () => {
|
||||
// All orders' protocol fees paid in ETH by sender
|
||||
const ETH_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(3).plus(REFUND_AMOUNT);
|
||||
// First order's protocol fee paid in ETH by sender, the other two paid in WETH by their respective takers
|
||||
const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(1).plus(REFUND_AMOUNT);
|
||||
|
||||
it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in ETH', async () => {
|
||||
const transactions = [transactionA, transactionB, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[alice.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
ETH_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in ETH', async () => {
|
||||
const transactions = [transactionB, transactionA, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, alice.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
ETH_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in ETH', async () => {
|
||||
const transactions = [transactionB, transactionC, transactionA];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, charlie.address, alice.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
ETH_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in wETH', async () => {
|
||||
const transactions = [transactionA, transactionB, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[alice.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
REFUND_AMOUNT,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in wETH', async () => {
|
||||
const transactions = [transactionB, transactionA, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, alice.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
REFUND_AMOUNT,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in wETH', async () => {
|
||||
const transactions = [transactionB, transactionC, transactionA];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, charlie.address, alice.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
REFUND_AMOUNT,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
|
||||
const transactions = [transactionA, transactionB, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[alice.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
|
||||
const transactions = [transactionB, transactionA, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, alice.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; mixed protocol fees', async () => {
|
||||
const transactions = [transactionB, transactionC, transactionA];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[bob.address, charlie.address, alice.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
|
||||
const failTransaction = await wethless.signTransactionAsync({ data });
|
||||
const transactions = [transactionA, transactionB, failTransaction];
|
||||
const signatures = transactions.map(transaction => transaction.signature);
|
||||
const tx = deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
return expect(tx).to.revertWith(protocolFeeError(order, failTransaction));
|
||||
});
|
||||
});
|
||||
describe('Nested', () => {
|
||||
// First two orders' protocol fees paid in ETH by sender, the others paid in WETH by their respective takers
|
||||
const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(2.5);
|
||||
|
||||
// Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder
|
||||
let nestedTransaction: SignedZeroExTransaction;
|
||||
// Second fillOrder transaction signed by Bob
|
||||
let transactionB2: SignedZeroExTransaction;
|
||||
// Second fillOrder transaction signed by Charlie
|
||||
let transactionC2: SignedZeroExTransaction;
|
||||
|
||||
before(async () => {
|
||||
let newOrder = await maker.signOrderAsync();
|
||||
let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]);
|
||||
transactionB2 = await bob.signTransactionAsync({ data });
|
||||
|
||||
newOrder = await maker.signOrderAsync();
|
||||
data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]);
|
||||
transactionC2 = await charlie.signTransactionAsync({ data });
|
||||
|
||||
const transactions = [transactionA, transactionB, transactionC];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const recursiveData = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.getABIEncodedTransactionData();
|
||||
nestedTransaction = await alice.signTransactionAsync({ data: recursiveData });
|
||||
});
|
||||
|
||||
it('Alice executeTransaction => nested batchExecuteTransactions; mixed protocol fees', async () => {
|
||||
const txReceipt = await deployment.exchange
|
||||
.executeTransaction(nestedTransaction, nestedTransaction.signature)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order],
|
||||
[alice.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => nested batchExecuteTransactions, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
|
||||
const transactions = [nestedTransaction, transactionB2, transactionC2];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order, order, order],
|
||||
[alice.address, bob.address, charlie.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, nested batchExecuteTransactions, Charlie fillOrder; mixed protocol fees', async () => {
|
||||
const transactions = [transactionB2, nestedTransaction, transactionC2];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order, order, order],
|
||||
[bob.address, alice.address, bob.address, charlie.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, nested batchExecuteTransactions; mixed protocol fees', async () => {
|
||||
const transactions = [transactionB2, transactionC2, nestedTransaction];
|
||||
const signatures = transactions.map(tx => tx.signature);
|
||||
const txReceipt = await deployment.exchange
|
||||
.batchExecuteTransactions(transactions, signatures)
|
||||
.awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
|
||||
expectedBalances.simulateFills(
|
||||
[order, order, order, order, order],
|
||||
[bob.address, charlie.address, alice.address, bob.address, charlie.address],
|
||||
txReceipt,
|
||||
deployment,
|
||||
MIXED_FEES_WITH_REFUND,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
809
contracts/integrations/test/exchange/transaction_test.ts
Normal file
809
contracts/integrations/test/exchange/transaction_test.ts
Normal file
@ -0,0 +1,809 @@
|
||||
// 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('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);
|
||||
});
|
||||
|
||||
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 = 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -156,7 +156,7 @@ blockchainTests.resets('Forwarder <> ERC20Bridge integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
describe('marketSellOrdersWithEth', () => {
|
||||
|
@ -106,7 +106,7 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
blockchainTests.resets('constructor', () => {
|
||||
@ -511,20 +511,10 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
|
||||
// Compute expected balances
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
makerAssetFillAmount,
|
||||
makerAssetData,
|
||||
);
|
||||
expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData);
|
||||
expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, ethValue);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
primaryTakerAssetFillAmount,
|
||||
wethAssetData,
|
||||
);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(taker.address, maker.address, primaryTakerAssetFillAmount, wethAssetData);
|
||||
expectedBalances.transferAsset(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
@ -568,24 +558,14 @@ blockchainTests('Forwarder integration tests', env => {
|
||||
|
||||
// Compute expected balances
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
maker.address,
|
||||
taker.address,
|
||||
makerAssetFillAmount,
|
||||
makerAssetData,
|
||||
);
|
||||
expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData);
|
||||
expectedBalances.wrapEth(
|
||||
taker.address,
|
||||
deployment.tokens.weth.address,
|
||||
takerAssetFillAmount.plus(DeploymentManager.protocolFee),
|
||||
);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
taker.address,
|
||||
maker.address,
|
||||
takerAssetFillAmount,
|
||||
wethAssetData,
|
||||
);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(taker.address, maker.address, takerAssetFillAmount, wethAssetData);
|
||||
expectedBalances.transferAsset(
|
||||
taker.address,
|
||||
deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
|
@ -207,7 +207,7 @@ export class ForwarderTestFactory {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { wethSpentAmount, makerAssetAcquiredAmount } = await this._simulateSingleFillAsync(
|
||||
const { wethSpentAmount, makerAssetAcquiredAmount } = this._simulateSingleFill(
|
||||
balances,
|
||||
order,
|
||||
ordersInfoBefore[i].orderTakerAssetFilledAmount,
|
||||
@ -232,13 +232,13 @@ export class ForwarderTestFactory {
|
||||
return { ...currentTotal, balances };
|
||||
}
|
||||
|
||||
private async _simulateSingleFillAsync(
|
||||
private _simulateSingleFill(
|
||||
balances: LocalBalanceStore,
|
||||
order: SignedOrder,
|
||||
takerAssetFilled: BigNumber,
|
||||
fillFraction: number,
|
||||
bridgeExcessBuyAmount: BigNumber,
|
||||
): Promise<ForwarderFillState> {
|
||||
): ForwarderFillState {
|
||||
let { makerAssetAmount, takerAssetAmount, makerFee, takerFee } = order;
|
||||
makerAssetAmount = makerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL);
|
||||
takerAssetAmount = takerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL);
|
||||
@ -272,43 +272,23 @@ export class ForwarderTestFactory {
|
||||
balances.wrapEth(this._forwarder.address, this._deployment.tokens.weth.address, wethSpentAmount);
|
||||
// (In reality this is done all at once, but we simulate it order by order)
|
||||
|
||||
// Maker -> Forwarder
|
||||
await balances.transferAssetAsync(
|
||||
order.makerAddress,
|
||||
this._forwarder.address,
|
||||
makerAssetAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
// Maker -> Order fee recipient
|
||||
await balances.transferAssetAsync(
|
||||
order.makerAddress,
|
||||
order.feeRecipientAddress,
|
||||
makerFee,
|
||||
order.makerFeeAssetData,
|
||||
);
|
||||
// Forwarder -> Maker
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
order.makerAddress,
|
||||
takerAssetAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
balances.transferAsset(this._forwarder.address, order.makerAddress, takerAssetAmount, order.takerAssetData);
|
||||
// Maker -> Forwarder
|
||||
balances.transferAsset(order.makerAddress, this._forwarder.address, makerAssetAmount, order.makerAssetData);
|
||||
// Forwarder -> Order fee recipient
|
||||
await balances.transferAssetAsync(
|
||||
this._forwarder.address,
|
||||
order.feeRecipientAddress,
|
||||
takerFee,
|
||||
order.takerFeeAssetData,
|
||||
);
|
||||
balances.transferAsset(this._forwarder.address, order.feeRecipientAddress, takerFee, order.takerFeeAssetData);
|
||||
// Maker -> Order fee recipient
|
||||
balances.transferAsset(order.makerAddress, order.feeRecipientAddress, makerFee, order.makerFeeAssetData);
|
||||
// Forwarder pays the protocol fee in WETH
|
||||
await balances.transferAssetAsync(
|
||||
balances.transferAsset(
|
||||
this._forwarder.address,
|
||||
this._deployment.staking.stakingProxy.address,
|
||||
DeploymentManager.protocolFee,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Forwarder gives acquired maker asset to taker
|
||||
await balances.transferAssetAsync(
|
||||
balances.transferAsset(
|
||||
this._forwarder.address,
|
||||
this._taker.address,
|
||||
makerAssetAcquiredAmount,
|
||||
|
@ -31,6 +31,10 @@ export class Actor {
|
||||
} = {};
|
||||
protected readonly _transactionFactory: TransactionFactory;
|
||||
|
||||
public static reset(): void {
|
||||
Actor.count = 0;
|
||||
}
|
||||
|
||||
constructor(config: ActorConfig) {
|
||||
Actor.count++;
|
||||
|
||||
@ -142,6 +146,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export function validStakeAssertion(
|
||||
before: async (amount: BigNumber, txData: Partial<TxData>) => {
|
||||
// Simulates the transfer of ZRX from staker to vault
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(
|
||||
txData.from as string,
|
||||
zrxVault.address,
|
||||
amount,
|
||||
|
@ -37,7 +37,7 @@ export function validUnstakeAssertion(
|
||||
before: async (amount: BigNumber, txData: Partial<TxData>) => {
|
||||
// Simulates the transfer of ZRX from vault to staker
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
await expectedBalances.transferAssetAsync(
|
||||
expectedBalances.transferAsset(
|
||||
zrxVault.address,
|
||||
txData.from as string,
|
||||
amount,
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
|
||||
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
|
||||
import { constants, hexSlice, Numberish, provider } from '@0x/contracts-test-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { AssetProxyId, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
import { BalanceStore } from './balance_store';
|
||||
import { TokenContractsByName, TokenOwnersByName } from './types';
|
||||
|
||||
@ -74,12 +78,7 @@ export class LocalBalanceStore extends BalanceStore {
|
||||
* @param amount Amount of asset(s) to transfer
|
||||
* @param assetData Asset data of assets being transferred.
|
||||
*/
|
||||
public async transferAssetAsync(
|
||||
fromAddress: string,
|
||||
toAddress: string,
|
||||
amount: BigNumber,
|
||||
assetData: string,
|
||||
): Promise<void> {
|
||||
public transferAsset(fromAddress: string, toAddress: string, amount: BigNumber, assetData: string): void {
|
||||
if (fromAddress === toAddress || amount.isZero()) {
|
||||
return;
|
||||
}
|
||||
@ -174,7 +173,7 @@ export class LocalBalanceStore extends BalanceStore {
|
||||
>('MultiAsset', assetData);
|
||||
for (const [i, amt] of amounts.entries()) {
|
||||
const nestedAmount = amount.times(amt);
|
||||
await this.transferAssetAsync(fromAddress, toAddress, nestedAmount, nestedAssetData[i]);
|
||||
this.transferAsset(fromAddress, toAddress, nestedAmount, nestedAssetData[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -185,4 +184,71 @@ export class LocalBalanceStore extends BalanceStore {
|
||||
throw new Error(`Unhandled asset proxy ID: ${assetProxyId}`);
|
||||
}
|
||||
}
|
||||
|
||||
public simulateFills(
|
||||
orders: SignedOrder[],
|
||||
takerAddresses: string[] | string,
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
deployment: DeploymentManager,
|
||||
msgValue: BigNumber = constants.ZERO_AMOUNT,
|
||||
takerAssetFillAmounts?: BigNumber[],
|
||||
): void {
|
||||
let remainingValue = msgValue;
|
||||
// Transaction gas cost
|
||||
this.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
for (const [index, order] of orders.entries()) {
|
||||
const takerAddress = Array.isArray(takerAddresses) ? takerAddresses[index] : takerAddresses;
|
||||
const fillResults = ReferenceFunctions.calculateFillResults(
|
||||
order,
|
||||
takerAssetFillAmounts ? takerAssetFillAmounts[index] : order.takerAssetAmount,
|
||||
DeploymentManager.protocolFeeMultiplier,
|
||||
DeploymentManager.gasPrice,
|
||||
);
|
||||
|
||||
// Taker -> Maker
|
||||
this.transferAsset(
|
||||
takerAddress,
|
||||
order.makerAddress,
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetData,
|
||||
);
|
||||
// Maker -> Taker
|
||||
this.transferAsset(
|
||||
order.makerAddress,
|
||||
takerAddress,
|
||||
fillResults.makerAssetFilledAmount,
|
||||
order.makerAssetData,
|
||||
);
|
||||
// Taker -> Fee Recipient
|
||||
this.transferAsset(
|
||||
takerAddress,
|
||||
order.feeRecipientAddress,
|
||||
fillResults.takerFeePaid,
|
||||
order.takerFeeAssetData,
|
||||
);
|
||||
// Maker -> Fee Recipient
|
||||
this.transferAsset(
|
||||
order.makerAddress,
|
||||
order.feeRecipientAddress,
|
||||
fillResults.makerFeePaid,
|
||||
order.makerFeeAssetData,
|
||||
);
|
||||
|
||||
// Protocol fee
|
||||
if (remainingValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) {
|
||||
this.sendEth(txReceipt.from, deployment.staking.stakingProxy.address, fillResults.protocolFeePaid);
|
||||
remainingValue = remainingValue.minus(fillResults.protocolFeePaid);
|
||||
} else {
|
||||
this.transferAsset(
|
||||
takerAddress,
|
||||
deployment.staking.stakingProxy.address,
|
||||
fillResults.protocolFeePaid,
|
||||
deployment.assetDataEncoder
|
||||
.ERC20Token(deployment.tokens.weth.address)
|
||||
.getABIEncodedTransactionData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class PoolManagementSimulation extends Simulation {
|
||||
|
||||
blockchainTests.skip('Pool management fuzz test', env => {
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
it('fuzz', async () => {
|
||||
|
@ -34,7 +34,7 @@ export class StakeManagementSimulation extends Simulation {
|
||||
|
||||
blockchainTests.skip('Stake management fuzz test', env => {
|
||||
after(async () => {
|
||||
Actor.count = 0;
|
||||
Actor.reset();
|
||||
});
|
||||
|
||||
it('fuzz', async () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { AbiEncoder } from '@0x/utils';
|
||||
import { DataItem, MethodAbi } from 'ethereum-types';
|
||||
|
||||
// tslint:disable-next-line:completed-docs
|
||||
@ -29,21 +30,11 @@ export function formatABIDataItem(abi: DataItem, value: any, formatter: (type: s
|
||||
}
|
||||
}
|
||||
|
||||
function dataItemsToABIString(dataItems: DataItem[]): string {
|
||||
const types = dataItems.map(item => {
|
||||
if (item.components) {
|
||||
return `(${dataItemsToABIString(item.components)})`;
|
||||
} else {
|
||||
return item.type;
|
||||
}
|
||||
});
|
||||
return `${types.join(',')}`;
|
||||
}
|
||||
/**
|
||||
* Takes a MethodAbi and returns a function signature for ABI encoding/decoding
|
||||
* @return a function signature as a string, e.g. 'functionName(uint256, bytes[])'
|
||||
*/
|
||||
export function methodAbiToFunctionSignature(methodAbi: MethodAbi): string {
|
||||
const inputs = dataItemsToABIString(methodAbi.inputs);
|
||||
return `${methodAbi.name}(${inputs})`;
|
||||
const method = AbiEncoder.createMethod(methodAbi.name, methodAbi.inputs);
|
||||
return method.getSignature();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user