Refactor integration tests (CoordinatorTestFactory)
This commit is contained in:
@@ -3,30 +3,22 @@ import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } f
|
||||
import {
|
||||
artifacts as exchangeArtifacts,
|
||||
constants as exchangeConstants,
|
||||
ExchangeCancelEventArgs,
|
||||
ExchangeCancelUpToEventArgs,
|
||||
ExchangeContract,
|
||||
exchangeDataEncoder,
|
||||
ExchangeEvents,
|
||||
ExchangeFillEventArgs,
|
||||
ExchangeFunctionName,
|
||||
TestProtocolFeeCollectorContract,
|
||||
} from '@0x/contracts-exchange';
|
||||
import {
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
filterLogsToArguments,
|
||||
getLatestBlockTimestampAsync,
|
||||
OrderFactory,
|
||||
TransactionFactory,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, CoordinatorRevertErrors, orderHashUtils, transactionHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { assetDataUtils, CoordinatorRevertErrors, transactionHashUtils } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
|
||||
import { ApprovalFactory, artifacts, CoordinatorContract } from '../src';
|
||||
import { ApprovalFactory, artifacts, CoordinatorTestFactory, CoordinatorContract } from '../src';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests.resets('Coordinator tests', env => {
|
||||
@@ -40,14 +32,18 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
let erc20TokenA: DummyERC20TokenContract;
|
||||
let erc20TokenB: DummyERC20TokenContract;
|
||||
let makerFeeToken: DummyERC20TokenContract;
|
||||
let takerFeeToken: DummyERC20TokenContract;
|
||||
let coordinatorContract: CoordinatorContract;
|
||||
let exchange: ExchangeContract;
|
||||
let protocolFeeCollector: TestProtocolFeeCollectorContract;
|
||||
let wethContract: WETH9Contract;
|
||||
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
let orderFactory: OrderFactory;
|
||||
let takerTransactionFactory: TransactionFactory;
|
||||
let makerTransactionFactory: TransactionFactory;
|
||||
let approvalFactory: ApprovalFactory;
|
||||
let testFactory: CoordinatorTestFactory;
|
||||
|
||||
const GAS_PRICE = new BigNumber(env.txDefaults.gasPrice || constants.DEFAULT_GAS_PRICE);
|
||||
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150);
|
||||
@@ -70,8 +66,8 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
// Set up ERC20
|
||||
erc20Wrapper = new ERC20Wrapper(env.provider, usedAddresses, owner);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, makerFeeToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
const numDummyErc20ToDeploy = 4;
|
||||
[erc20TokenA, erc20TokenB, makerFeeToken, takerFeeToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
@@ -79,7 +75,7 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address, { from: owner });
|
||||
|
||||
// Set up WETH
|
||||
const wethContract = await WETH9Contract.deployFrom0xArtifactAsync(
|
||||
wethContract = await WETH9Contract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.WETH9,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
@@ -90,7 +86,7 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
// Set up Protocol Fee Collector
|
||||
const protocolFeeCollector = await TestProtocolFeeCollectorContract.deployFrom0xArtifactAsync(
|
||||
protocolFeeCollector = await TestProtocolFeeCollectorContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.TestProtocolFeeCollector,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
@@ -112,6 +108,7 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
},
|
||||
);
|
||||
}
|
||||
erc20Wrapper.addTokenOwnerAddress(protocolFeeCollector.address);
|
||||
|
||||
// Deploy Coordinator
|
||||
coordinatorContract = await CoordinatorContract.deployFrom0xArtifactAsync(
|
||||
@@ -122,6 +119,7 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
exchange.address,
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
erc20Wrapper.addTokenOwnerAddress(coordinatorContract.address);
|
||||
|
||||
// Configure order defaults
|
||||
const defaultOrderParams = {
|
||||
@@ -132,7 +130,7 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
|
||||
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerFeeToken.address),
|
||||
exchangeAddress: exchange.address,
|
||||
chainId,
|
||||
};
|
||||
@@ -143,55 +141,28 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address, chainId);
|
||||
takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address, chainId);
|
||||
approvalFactory = new ApprovalFactory(feeRecipientPrivateKey, coordinatorContract.address);
|
||||
});
|
||||
|
||||
function verifyEvents<TEventArgs>(
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
expectedEvents: TEventArgs[],
|
||||
eventName: string,
|
||||
): void {
|
||||
const logs = filterLogsToArguments<TEventArgs>(txReceipt.logs, eventName);
|
||||
expect(logs.length).to.eq(expectedEvents.length);
|
||||
logs.forEach((log, index) => {
|
||||
expect(log).to.deep.equal(expectedEvents[index]);
|
||||
});
|
||||
}
|
||||
|
||||
function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs {
|
||||
return {
|
||||
makerAddress: order.makerAddress,
|
||||
testFactory = new CoordinatorTestFactory(
|
||||
coordinatorContract,
|
||||
erc20Wrapper,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
senderAddress: order.senderAddress,
|
||||
feeRecipientAddress: order.feeRecipientAddress,
|
||||
makerAssetData: order.makerAssetData,
|
||||
takerAssetData: order.takerAssetData,
|
||||
makerFeeAssetData: order.makerFeeAssetData,
|
||||
takerFeeAssetData: order.takerFeeAssetData,
|
||||
makerAssetFilledAmount: order.makerAssetAmount,
|
||||
takerAssetFilledAmount: order.takerAssetAmount,
|
||||
makerFeePaid: order.makerFee,
|
||||
takerFeePaid: order.takerFee,
|
||||
protocolFeePaid: PROTOCOL_FEE,
|
||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||
};
|
||||
}
|
||||
|
||||
function expectedCancelEvent(order: SignedOrder): ExchangeCancelEventArgs {
|
||||
return {
|
||||
makerAddress: order.makerAddress,
|
||||
senderAddress: order.senderAddress,
|
||||
feeRecipientAddress: order.feeRecipientAddress,
|
||||
makerAssetData: order.makerAssetData,
|
||||
takerAssetData: order.takerAssetData,
|
||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||
};
|
||||
}
|
||||
feeRecipientAddress,
|
||||
protocolFeeCollector.address,
|
||||
erc20TokenA.address,
|
||||
erc20TokenB.address,
|
||||
makerFeeToken.address,
|
||||
takerFeeToken.address,
|
||||
weth.address,
|
||||
GAS_PRICE,
|
||||
PROTOCOL_FEE_MULTIPLIER,
|
||||
);
|
||||
});
|
||||
|
||||
describe('single order fills', () => {
|
||||
for (const fnName of exchangeConstants.SINGLE_FILL_FN_NAMES) {
|
||||
it(`${fnName} should fill the order with a signed approval`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
@@ -200,38 +171,95 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const txData = { from: takerAddress, value: PROTOCOL_FEE };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress, value: PROTOCOL_FEE },
|
||||
txData,
|
||||
);
|
||||
verifyEvents(transactionReceipt, [expectedFillEvent(orders[0])], ExchangeEvents.Fill);
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
it(`${fnName} should fill the order if called by approver (eth protocol fee, no refund)`, async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: feeRecipientAddress, value: PROTOCOL_FEE },
|
||||
txData,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver (eth protocol fee, refund)`, async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE.plus(1) };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
[],
|
||||
[],
|
||||
txData,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver (weth protocol fee, no refund)`, async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const txData = { from: feeRecipientAddress };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
[],
|
||||
[],
|
||||
txData,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver (weth protocol fee, refund)`, async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const txData = { from: feeRecipientAddress, value: new BigNumber(1) };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
[],
|
||||
[],
|
||||
txData,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver`, async () => {
|
||||
const order = await orderFactory.newSignedOrderAsync();
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE };
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
[order],
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
[],
|
||||
[],
|
||||
txData,
|
||||
);
|
||||
verifyEvents(transactionReceipt, [expectedFillEvent(orders[0])], ExchangeEvents.Fill);
|
||||
});
|
||||
it(`${fnName} should revert with no approval signature`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
@@ -239,10 +267,6 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
|
||||
value: PROTOCOL_FEE,
|
||||
},
|
||||
);
|
||||
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expect(tx).to.revertWith(
|
||||
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress),
|
||||
);
|
||||
});
|
||||
@@ -258,17 +282,14 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: takerAddress, value: PROTOCOL_FEE },
|
||||
);
|
||||
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expect(tx).to.revertWith(
|
||||
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress),
|
||||
);
|
||||
});
|
||||
@@ -283,18 +304,14 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress, value: PROTOCOL_FEE },
|
||||
);
|
||||
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expect(tx).to.revertWith(
|
||||
new CoordinatorRevertErrors.ApprovalExpiredError(transactionHash, approvalExpirationTimeSeconds),
|
||||
);
|
||||
});
|
||||
@@ -309,16 +326,15 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: owner, value: PROTOCOL_FEE },
|
||||
new CoordinatorRevertErrors.InvalidOriginError(takerAddress),
|
||||
);
|
||||
expect(tx).to.revertWith(new CoordinatorRevertErrors.InvalidOriginError(takerAddress));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -335,10 +351,10 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{
|
||||
@@ -347,29 +363,26 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
value: PROTOCOL_FEE.times(orders.length),
|
||||
},
|
||||
);
|
||||
|
||||
const expectedEvents = orders.map(order => expectedFillEvent(order));
|
||||
verifyEvents(transactionReceipt, expectedEvents, ExchangeEvents.Fill);
|
||||
});
|
||||
it(`${fnName} should fill the orders if called by approver`, async () => {
|
||||
it(`${fnName} should fill the orders if called by approver (eth fee, no refund)`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: feeRecipientAddress,
|
||||
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
|
||||
value: PROTOCOL_FEE.times(orders.length),
|
||||
},
|
||||
);
|
||||
|
||||
const expectedEvents = orders.map(order => expectedFillEvent(order));
|
||||
verifyEvents(transactionReceipt, expectedEvents, ExchangeEvents.Fill);
|
||||
await testFactory.executeFillTransactionTestAsync(orders, transaction, feeRecipientAddress, [], [], {
|
||||
from: feeRecipientAddress,
|
||||
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
|
||||
value: PROTOCOL_FEE.times(orders.length),
|
||||
});
|
||||
});
|
||||
it(`${fnName} should fill the orders if called by approver (mixed fees, refund)`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
await testFactory.executeFillTransactionTestAsync(orders, transaction, feeRecipientAddress, [], [], {
|
||||
from: feeRecipientAddress,
|
||||
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
|
||||
value: PROTOCOL_FEE.times(orders.length).plus(1),
|
||||
});
|
||||
});
|
||||
it(`${fnName} should revert with an invalid approval signature`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
@@ -383,17 +396,14 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: takerAddress, value: PROTOCOL_FEE.times(orders.length) },
|
||||
);
|
||||
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expect(tx).to.revertWith(
|
||||
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress),
|
||||
);
|
||||
});
|
||||
@@ -408,17 +418,14 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress, value: PROTOCOL_FEE.times(orders.length) },
|
||||
);
|
||||
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expect(tx).to.revertWith(
|
||||
new CoordinatorRevertErrors.ApprovalExpiredError(transactionHash, approvalExpirationTimeSeconds),
|
||||
);
|
||||
});
|
||||
@@ -433,16 +440,15 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
|
||||
const tx = coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeFillTransactionTestAsync(
|
||||
orders,
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: owner, value: PROTOCOL_FEE.times(orders.length) },
|
||||
new CoordinatorRevertErrors.InvalidOriginError(takerAddress),
|
||||
);
|
||||
expect(tx).to.revertWith(new CoordinatorRevertErrors.InvalidOriginError(takerAddress));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -451,55 +457,48 @@ blockchainTests.resets('Coordinator tests', env => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders);
|
||||
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeCancelTransactionTestAsync(
|
||||
ExchangeFunctionName.CancelOrder,
|
||||
orders,
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
);
|
||||
verifyEvents(transactionReceipt, [expectedCancelEvent(orders[0])], ExchangeEvents.Cancel);
|
||||
});
|
||||
it('batchCancelOrders call should be successful without an approval', 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 coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeCancelTransactionTestAsync(
|
||||
ExchangeFunctionName.BatchCancelOrders,
|
||||
orders,
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
);
|
||||
const expectedEvents = orders.map(order => expectedCancelEvent(order));
|
||||
verifyEvents(transactionReceipt, expectedEvents, ExchangeEvents.Cancel);
|
||||
});
|
||||
it('cancelOrdersUpTo call should be successful without an approval', async () => {
|
||||
const targetEpoch = constants.ZERO_AMOUNT;
|
||||
const data = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch);
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo, []);
|
||||
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data });
|
||||
const transactionReceipt = await coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
await testFactory.executeCancelTransactionTestAsync(
|
||||
ExchangeFunctionName.CancelOrdersUpTo,
|
||||
[],
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
);
|
||||
const expectedEvent: ExchangeCancelUpToEventArgs = {
|
||||
makerAddress,
|
||||
orderSenderAddress: coordinatorContract.address,
|
||||
orderEpoch: targetEpoch.plus(1),
|
||||
};
|
||||
verifyEvents(transactionReceipt, [expectedEvent], ExchangeEvents.CancelUpTo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -173,11 +173,21 @@ blockchainTests.resets('Mixins tests', env => {
|
||||
expect(orders).to.deep.eq(decodedSignedOrders);
|
||||
});
|
||||
}
|
||||
for (const fnName of [
|
||||
ExchangeFunctionName.CancelOrder,
|
||||
ExchangeFunctionName.BatchCancelOrders,
|
||||
ExchangeFunctionName.CancelOrdersUpTo,
|
||||
]) {
|
||||
for (const fnName of exchangeConstants.MATCH_ORDER_FN_NAMES) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const decodedSignedOrders = decodedOrders.map(order => ({
|
||||
...order,
|
||||
signature: constants.NULL_BYTES,
|
||||
exchangeAddress: constants.NULL_ADDRESS,
|
||||
chainId,
|
||||
}));
|
||||
expect(orders).to.deep.eq(decodedSignedOrders);
|
||||
});
|
||||
}
|
||||
for (const fnName of exchangeConstants.CANCEL_ORDER_FN_NAMES) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
@@ -378,7 +388,7 @@ blockchainTests.resets('Mixins tests', env => {
|
||||
for (const fnName of [
|
||||
...exchangeConstants.BATCH_FILL_FN_NAMES,
|
||||
...exchangeConstants.MARKET_FILL_FN_NAMES,
|
||||
ExchangeFunctionName.MatchOrders,
|
||||
...exchangeConstants.MATCH_ORDER_FN_NAMES,
|
||||
]) {
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
|
268
contracts/coordinator/test/utils/coordinator_test_factory.ts
Normal file
268
contracts/coordinator/test/utils/coordinator_test_factory.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import {
|
||||
ExchangeCancelEventArgs,
|
||||
ExchangeCancelUpToEventArgs,
|
||||
ExchangeEvents,
|
||||
ExchangeFillEventArgs,
|
||||
ExchangeFunctionName,
|
||||
} from '@0x/contracts-exchange';
|
||||
import { expect, filterLogsToArguments, Numberish, TokenBalances, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber, RevertError } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { CoordinatorContract } from '../../src';
|
||||
|
||||
export class CoordinatorTestFactory {
|
||||
private readonly _addresses: string[];
|
||||
private readonly _protocolFee: BigNumber;
|
||||
|
||||
public static verifyEvents<TEventArgs>(
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
expectedEvents: TEventArgs[],
|
||||
eventName: string,
|
||||
): void {
|
||||
const logs = filterLogsToArguments<TEventArgs>(txReceipt.logs, eventName);
|
||||
expect(logs.length).to.eq(expectedEvents.length);
|
||||
logs.forEach((log, index) => {
|
||||
expect(log).to.deep.equal(expectedEvents[index]);
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _coordinatorContract: CoordinatorContract,
|
||||
private readonly _erc20Wrapper: ERC20Wrapper,
|
||||
private readonly _makerAddress: string,
|
||||
private readonly _takerAddress: string,
|
||||
private readonly _feeRecipientAddress: string,
|
||||
private readonly _protocolFeeCollectorAddress: string,
|
||||
private readonly _makerAssetAddress: string,
|
||||
private readonly _takerAssetAddress: string,
|
||||
private readonly _makerFeeAssetAddress: string,
|
||||
private readonly _takerFeeAssetAddress: string,
|
||||
private readonly _wethAddress: string,
|
||||
private readonly _gasPrice: BigNumber,
|
||||
_protocolFeeMultiplier: BigNumber,
|
||||
) {
|
||||
this._addresses = [
|
||||
_makerAddress,
|
||||
_takerAddress,
|
||||
_coordinatorContract.address,
|
||||
_feeRecipientAddress,
|
||||
_protocolFeeCollectorAddress,
|
||||
];
|
||||
this._protocolFee = _gasPrice.times(_protocolFeeMultiplier);
|
||||
}
|
||||
|
||||
public async executeFillTransactionTestAsync(
|
||||
orders: SignedOrder[],
|
||||
transaction: SignedZeroExTransaction,
|
||||
txOrigin: string,
|
||||
approvalExpirationTimeSeconds: BigNumber[],
|
||||
approvalSignatures: string[],
|
||||
txData: Partial<TxData>,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
const initBalances = await this._getTokenBalances();
|
||||
const tx = this._coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
txOrigin,
|
||||
transaction.signature,
|
||||
approvalExpirationTimeSeconds,
|
||||
approvalSignatures,
|
||||
txData,
|
||||
);
|
||||
|
||||
if (revertError !== undefined) {
|
||||
return expect(tx).to.revertWith(revertError);
|
||||
}
|
||||
|
||||
const transactionReceipt = await tx;
|
||||
CoordinatorTestFactory.verifyEvents(
|
||||
transactionReceipt,
|
||||
orders.map(order => this._expectedFillEvent(order)),
|
||||
ExchangeEvents.Fill,
|
||||
);
|
||||
|
||||
const expectedBalances = this._getExpectedBalances(initBalances, orders, transactionReceipt, txData.value);
|
||||
await this._verifyBalancesAsync(expectedBalances);
|
||||
}
|
||||
|
||||
public async executeCancelTransactionTestAsync(
|
||||
fnName: ExchangeFunctionName,
|
||||
orders: SignedOrder[],
|
||||
transaction: SignedZeroExTransaction,
|
||||
txOrigin: string,
|
||||
approvalExpirationTimeSeconds: BigNumber[],
|
||||
approvalSignatures: string[],
|
||||
txData: Partial<TxData>,
|
||||
): Promise<void> {
|
||||
const transactionReceipt = await this._coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
|
||||
transaction,
|
||||
txOrigin,
|
||||
transaction.signature,
|
||||
approvalExpirationTimeSeconds,
|
||||
approvalSignatures,
|
||||
txData,
|
||||
);
|
||||
|
||||
if (fnName === ExchangeFunctionName.CancelOrdersUpTo) {
|
||||
const expectedEvent: ExchangeCancelUpToEventArgs = {
|
||||
makerAddress: this._makerAddress,
|
||||
orderSenderAddress: this._coordinatorContract.address,
|
||||
orderEpoch: new BigNumber(1),
|
||||
};
|
||||
CoordinatorTestFactory.verifyEvents(transactionReceipt, [expectedEvent], ExchangeEvents.CancelUpTo);
|
||||
} else {
|
||||
CoordinatorTestFactory.verifyEvents(
|
||||
transactionReceipt,
|
||||
orders.map(order => CoordinatorTestFactory._expectedCancelEvent(order)),
|
||||
ExchangeEvents.Cancel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getTokenBalances(): Promise<TokenBalances> {
|
||||
let erc20Balances = await this._erc20Wrapper.getBalancesAsync();
|
||||
const ethBalances = _.zipObject(
|
||||
this._addresses,
|
||||
await Promise.all(this._addresses.map(address => web3Wrapper.getBalanceInWeiAsync(address))),
|
||||
);
|
||||
|
||||
return {
|
||||
erc20: erc20Balances,
|
||||
erc721: {},
|
||||
erc1155: {},
|
||||
eth: ethBalances,
|
||||
};
|
||||
}
|
||||
|
||||
private _getExpectedBalances(
|
||||
initBalances: TokenBalances,
|
||||
orders: SignedOrder[],
|
||||
txReceipt: TransactionReceiptWithDecodedLogs,
|
||||
txValue?: Numberish,
|
||||
): TokenBalances {
|
||||
const { erc20: erc20Balances, eth: ethBalances } = initBalances;
|
||||
let remainingValue = new BigNumber(txValue || 0);
|
||||
ethBalances[txReceipt.from] = ethBalances[txReceipt.from].minus(this._gasPrice.times(txReceipt.gasUsed));
|
||||
|
||||
for (const order of orders) {
|
||||
const [makerAssetAddress, takerAssetAddress, makerFeeAssetAddress, takerFeeAssetAddress] = [
|
||||
order.makerAssetData,
|
||||
order.takerAssetData,
|
||||
order.makerFeeAssetData,
|
||||
order.takerFeeAssetData,
|
||||
].map(assetData => assetDataUtils.decodeERC20AssetData(assetData).tokenAddress);
|
||||
|
||||
erc20Balances[order.makerAddress][makerAssetAddress] = erc20Balances[order.makerAddress][
|
||||
makerAssetAddress
|
||||
].minus(order.makerAssetAmount);
|
||||
erc20Balances[this._takerAddress][makerAssetAddress] = erc20Balances[this._takerAddress][
|
||||
makerAssetAddress
|
||||
].plus(order.makerAssetAmount);
|
||||
erc20Balances[order.makerAddress][takerAssetAddress] = erc20Balances[order.makerAddress][
|
||||
takerAssetAddress
|
||||
].plus(order.takerAssetAmount);
|
||||
erc20Balances[this._takerAddress][takerAssetAddress] = erc20Balances[this._takerAddress][
|
||||
takerAssetAddress
|
||||
].minus(order.takerAssetAmount);
|
||||
erc20Balances[order.makerAddress][makerFeeAssetAddress] = erc20Balances[order.makerAddress][
|
||||
makerFeeAssetAddress
|
||||
].minus(order.makerFee);
|
||||
erc20Balances[this._takerAddress][takerFeeAssetAddress] = erc20Balances[this._takerAddress][
|
||||
takerFeeAssetAddress
|
||||
].minus(order.takerFee);
|
||||
erc20Balances[order.feeRecipientAddress][makerFeeAssetAddress] = erc20Balances[order.feeRecipientAddress][
|
||||
makerFeeAssetAddress
|
||||
].plus(order.makerFee);
|
||||
erc20Balances[order.feeRecipientAddress][takerFeeAssetAddress] = erc20Balances[order.feeRecipientAddress][
|
||||
takerFeeAssetAddress
|
||||
].plus(order.takerFee);
|
||||
|
||||
if (remainingValue.isGreaterThanOrEqualTo(this._protocolFee)) {
|
||||
ethBalances[txReceipt.from] = ethBalances[txReceipt.from].minus(this._protocolFee);
|
||||
ethBalances[this._protocolFeeCollectorAddress] = ethBalances[this._protocolFeeCollectorAddress].plus(
|
||||
this._protocolFee,
|
||||
);
|
||||
remainingValue = remainingValue.minus(this._protocolFee);
|
||||
} else {
|
||||
erc20Balances[this._takerAddress][this._wethAddress] = erc20Balances[this._takerAddress][
|
||||
this._wethAddress
|
||||
].minus(this._protocolFee);
|
||||
erc20Balances[this._protocolFeeCollectorAddress][this._wethAddress] = erc20Balances[
|
||||
this._protocolFeeCollectorAddress
|
||||
][this._wethAddress].plus(this._protocolFee);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
erc20: erc20Balances,
|
||||
erc721: {},
|
||||
erc1155: {},
|
||||
eth: ethBalances,
|
||||
};
|
||||
}
|
||||
|
||||
private async _verifyBalancesAsync(expectedBalances: TokenBalances): Promise<void> {
|
||||
const { erc20: expectedErc20Balances, eth: expectedEthBalances } = expectedBalances;
|
||||
const { erc20: actualErc20Balances, eth: actualEthBalances } = await this._getTokenBalances();
|
||||
const ownersByName = {
|
||||
maker: this._makerAddress,
|
||||
taker: this._takerAddress,
|
||||
feeRecipient: this._feeRecipientAddress,
|
||||
coordinator: this._coordinatorContract.address,
|
||||
protocolFeeCollector: this._protocolFeeCollectorAddress,
|
||||
};
|
||||
const tokensByName = {
|
||||
makerAsset: this._makerAssetAddress,
|
||||
takerAsset: this._takerAssetAddress,
|
||||
makerFeeAsset: this._makerFeeAssetAddress,
|
||||
takerFeeAsset: this._takerFeeAssetAddress,
|
||||
weth: this._wethAddress,
|
||||
};
|
||||
_.forIn(ownersByName, (ownerAddress, ownerName) => {
|
||||
expect(actualEthBalances[ownerAddress], `${ownerName} eth balance`).to.bignumber.equal(
|
||||
expectedEthBalances[ownerAddress],
|
||||
);
|
||||
_.forIn(tokensByName, (tokenAddress, tokenName) => {
|
||||
expect(
|
||||
actualErc20Balances[ownerAddress][tokenAddress],
|
||||
`${ownerName} ${tokenName} balance`,
|
||||
).to.bignumber.equal(expectedErc20Balances[ownerAddress][tokenAddress]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs {
|
||||
return {
|
||||
makerAddress: order.makerAddress,
|
||||
takerAddress: this._takerAddress,
|
||||
senderAddress: order.senderAddress,
|
||||
feeRecipientAddress: order.feeRecipientAddress,
|
||||
makerAssetData: order.makerAssetData,
|
||||
takerAssetData: order.takerAssetData,
|
||||
makerFeeAssetData: order.makerFeeAssetData,
|
||||
takerFeeAssetData: order.takerFeeAssetData,
|
||||
makerAssetFilledAmount: order.makerAssetAmount,
|
||||
takerAssetFilledAmount: order.takerAssetAmount,
|
||||
makerFeePaid: order.makerFee,
|
||||
takerFeePaid: order.takerFee,
|
||||
protocolFeePaid: this._protocolFee,
|
||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||
};
|
||||
}
|
||||
|
||||
private static _expectedCancelEvent(order: SignedOrder): ExchangeCancelEventArgs {
|
||||
return {
|
||||
makerAddress: order.makerAddress,
|
||||
senderAddress: order.senderAddress,
|
||||
feeRecipientAddress: order.feeRecipientAddress,
|
||||
makerAssetData: order.makerAssetData,
|
||||
takerAssetData: order.takerAssetData,
|
||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
export { hashUtils } from './hash_utils';
|
||||
export { ApprovalFactory } from './approval_factory';
|
||||
export { CoordinatorTestFactory } from './coordinator_test_factory';
|
||||
export * from './types';
|
||||
|
Reference in New Issue
Block a user