@0x:contracts-integrations Addressed review feedback

This commit is contained in:
Alex Towle
2019-11-12 17:17:07 -08:00
parent 5fc6a03784
commit b58cbca61a
3 changed files with 811 additions and 889 deletions

View File

@@ -414,14 +414,19 @@ blockchainTests('Forwarder integration tests', env => {
it('should buy an ERC721 asset and pay a WETH fee', async () => { it('should buy an ERC721 asset and pay a WETH fee', async () => {
const erc721orderWithWethFee = await maker.signOrderAsync({ const erc721orderWithWethFee = await maker.signOrderAsync({
makerAssetAmount: new BigNumber(1), makerAssetAmount: new BigNumber(1),
makerAssetData: deployment.assetDataEncoder.ERC721Token.getABIEncodedTransactionData(erc721Token.address, nftId), makerAssetData: deployment.assetDataEncoder.ERC721Token.getABIEncodedTransactionData(
erc721Token.address,
nftId,
),
takerFee: toBaseUnitAmount(1), takerFee: toBaseUnitAmount(1),
takerFeeAssetData: wethAssetData, takerFeeAssetData: wethAssetData,
}); });
await testFactory.marketBuyTestAsync([erc721orderWithWethFee], 1); await testFactory.marketBuyTestAsync([erc721orderWithWethFee], 1);
}); });
it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => { it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
const takerFeeAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(anotherErc20Token.address); const takerFeeAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
anotherErc20Token.address,
);
const order = await maker.signOrderAsync({ const order = await maker.signOrderAsync({
takerFeeAssetData, takerFeeAssetData,
takerFee: toBaseUnitAmount(1), takerFee: toBaseUnitAmount(1),

View File

@@ -1,5 +1,4 @@
import { IAssetDataContract } from '@0x/contracts-asset-proxy'; import { IAssetDataContract } from '@0x/contracts-asset-proxy';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { BlockchainBalanceStore, ExchangeContract, LocalBalanceStore } from '@0x/contracts-exchange'; import { BlockchainBalanceStore, ExchangeContract, LocalBalanceStore } from '@0x/contracts-exchange';
import { constants, expect, OrderStatus } from '@0x/contracts-test-utils'; import { constants, expect, OrderStatus } from '@0x/contracts-test-utils';
import { orderHashUtils } from '@0x/order-utils'; import { orderHashUtils } from '@0x/order-utils';
@@ -74,18 +73,17 @@ export class MatchOrderTester {
* Constructs new MatchOrderTester. * Constructs new MatchOrderTester.
*/ */
constructor( constructor(
protected readonly _assetDataEncoder: IAssetDataContract,
protected readonly _deployment: DeploymentManager, protected readonly _deployment: DeploymentManager,
protected readonly _devUtils: DevUtilsContract,
protected readonly _blockchainBalanceStore: BlockchainBalanceStore, protected readonly _blockchainBalanceStore: BlockchainBalanceStore,
) {} ) {}
/** /**
* Performs batch order matching on a set of complementary orders and asserts results. * Performs batch order matching on a set of complementary orders and asserts results.
* @param orders The list of orders and filled states * @param orders The list of orders and filled states
* @param takerAddress Address of taker (the address who matched the two orders)
* @param value The amount of value that should be sent in the contract call.
* @param matchPairs An array of left and right indices that will be used to perform * @param matchPairs An array of left and right indices that will be used to perform
* the expected simulation. * the expected simulation.
* @param takerAddress Address of taker (the address who matched the two orders)
* @param expectedTransferAmounts Expected amounts transferred as a result of each round of * @param expectedTransferAmounts Expected amounts transferred as a result of each round of
* order matching. Omitted fields are either set to 0 or their * order matching. Omitted fields are either set to 0 or their
* complementary field. * complementary field.
@@ -107,12 +105,12 @@ export class MatchOrderTester {
expect(orders.rightOrders.length).to.be.eq(orders.rightOrdersTakerAssetFilledAmounts.length); expect(orders.rightOrders.length).to.be.eq(orders.rightOrdersTakerAssetFilledAmounts.length);
// Ensure that the exchange is in the expected state. // Ensure that the exchange is in the expected state.
await assertBatchOrderStatesAsync(orders, this._deployment.exchange); await this._assertBatchOrderStatesAsync(orders);
// Update the blockchain balance store and create a new local balance store // Update the blockchain balance store and create a new local balance store
// with the same initial balances. // with the same initial balances.
await this._blockchainBalanceStore.updateBalancesAsync(); await this._blockchainBalanceStore.updateBalancesAsync();
const localBalanceStore = LocalBalanceStore.create(this._devUtils, this._blockchainBalanceStore); const localBalanceStore = LocalBalanceStore.create(this._deployment.devUtils, this._blockchainBalanceStore);
// Execute `batchMatchOrders()` // Execute `batchMatchOrders()`
let actualBatchMatchResults; let actualBatchMatchResults;
@@ -124,7 +122,7 @@ export class MatchOrderTester {
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value }, { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
); );
} else { } else {
[actualBatchMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync( [actualBatchMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
@@ -133,46 +131,38 @@ export class MatchOrderTester {
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value }, { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
); );
} }
// Burn the gas used to execute the transaction in the local balance store. // Burn the gas used to execute the transaction in the local balance store.
localBalanceStore.burnGas(takerAddress, constants.DEFAULT_GAS_PRICE * transactionReceipt.gasUsed); localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed));
// Simulate the batch order match. // Simulate the batch order match.
const expectedBatchMatchResults = await simulateBatchMatchOrdersAsync( const expectedBatchMatchResults = await this._simulateBatchMatchOrdersAsync(
orders, orders,
takerAddress, takerAddress,
this._deployment,
this._blockchainBalanceStore,
localBalanceStore,
matchPairs, matchPairs,
expectedTransferAmounts, expectedTransferAmounts,
this._assetDataEncoder, localBalanceStore,
); );
const expectedResults = convertToBatchMatchResults(expectedBatchMatchResults); const expectedResults = convertToBatchMatchResults(expectedBatchMatchResults);
expect(actualBatchMatchResults).to.be.eql(expectedResults); expect(actualBatchMatchResults).to.be.eql(expectedResults);
// Validate the simulation against reality. // Validate the simulation against reality.
await assertBatchMatchResultsAsync( await this._assertBatchMatchResultsAsync(expectedBatchMatchResults, transactionReceipt, localBalanceStore);
expectedBatchMatchResults,
transactionReceipt,
this._blockchainBalanceStore,
localBalanceStore,
this._deployment.exchange,
);
return expectedBatchMatchResults; return expectedBatchMatchResults;
} }
/** /**
* Matches two complementary orders and asserts results. * Matches two complementary orders and asserts results.
* @param orders The matched orders and filled states. * @param orders The matched orders and filled states.
* @param takerAddress Address of taker (the address who matched the two orders)
* @param expectedTransferAmounts Expected amounts transferred as a result of order matching. * @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
* Omitted fields are either set to 0 or their complementary * Omitted fields are either set to 0 or their complementary
* field. * field.
* @param takerAddress Address of taker (the address who matched the two orders)
* @param value The amount of value that should be sent in the contract call.
* @param withMaximalFill A boolean that indicates whether the "maximal fill" order matching * @param withMaximalFill A boolean that indicates whether the "maximal fill" order matching
* strategy should be used. * strategy should be used.
* @return Results of `matchOrders()`. * @return Results of `matchOrders()`.
@@ -184,12 +174,12 @@ export class MatchOrderTester {
value: BigNumber, value: BigNumber,
withMaximalFill: boolean, withMaximalFill: boolean,
): Promise<MatchResults> { ): Promise<MatchResults> {
await assertInitialOrderStatesAsync(orders, this._deployment.exchange); await this._assertInitialOrderStatesAsync(orders);
// Update the blockchain balance store and create a new local balance store // Update the blockchain balance store and create a new local balance store
// with the same initial balances. // with the same initial balances.
await this._blockchainBalanceStore.updateBalancesAsync(); await this._blockchainBalanceStore.updateBalancesAsync();
const localBalanceStore = LocalBalanceStore.create(this._devUtils, this._blockchainBalanceStore); const localBalanceStore = LocalBalanceStore.create(this._deployment.devUtils, this._blockchainBalanceStore);
// Execute `matchOrders()` // Execute `matchOrders()`
let actualMatchResults; let actualMatchResults;
@@ -201,7 +191,7 @@ export class MatchOrderTester {
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value }, { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
); );
} else { } else {
[actualMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync( [actualMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
@@ -210,225 +200,39 @@ export class MatchOrderTester {
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value }, { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
); );
} }
localBalanceStore.burnGas(takerAddress, constants.DEFAULT_GAS_PRICE * transactionReceipt.gasUsed); localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed));
// Simulate the fill. // Simulate the fill.
const expectedMatchResults = await simulateMatchOrdersAsync( const expectedMatchResults = await this._simulateMatchOrdersAsync(
orders, orders,
takerAddress, takerAddress,
this._deployment,
toFullMatchTransferAmounts(expectedTransferAmounts), toFullMatchTransferAmounts(expectedTransferAmounts),
this._assetDataEncoder,
this._blockchainBalanceStore,
localBalanceStore, localBalanceStore,
); );
const expectedResults = convertToMatchResults(expectedMatchResults); const expectedResults = convertToMatchResults(expectedMatchResults);
expect(actualMatchResults).to.be.eql(expectedResults); expect(actualMatchResults).to.be.eql(expectedResults);
// Validate the simulation against reality. // Validate the simulation against reality.
await assertMatchResultsAsync( await this._assertMatchResultsAsync(expectedMatchResults, transactionReceipt, localBalanceStore);
expectedMatchResults,
transactionReceipt,
localBalanceStore,
this._blockchainBalanceStore,
this._deployment.exchange,
);
return expectedMatchResults; return expectedMatchResults;
} }
}
/**
* Converts a `Partial<MatchTransferAmounts>` to a `MatchTransferAmounts` by
* filling in missing fields with zero.
*/
function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): MatchTransferAmounts {
// prettier-ignore
return {
leftMakerAssetSoldByLeftMakerAmount:
partial.leftMakerAssetSoldByLeftMakerAmount ||
partial.leftMakerAssetBoughtByRightMakerAmount ||
constants.ZERO_AMOUNT,
rightMakerAssetSoldByRightMakerAmount:
partial.rightMakerAssetSoldByRightMakerAmount ||
partial.rightMakerAssetBoughtByLeftMakerAmount ||
constants.ZERO_AMOUNT,
rightMakerAssetBoughtByLeftMakerAmount:
partial.rightMakerAssetBoughtByLeftMakerAmount ||
partial.rightMakerAssetSoldByRightMakerAmount ||
constants.ZERO_AMOUNT,
leftMakerAssetBoughtByRightMakerAmount: partial.leftMakerAssetBoughtByRightMakerAmount ||
partial.leftMakerAssetSoldByLeftMakerAmount ||
constants.ZERO_AMOUNT,
leftMakerFeeAssetPaidByLeftMakerAmount:
partial.leftMakerFeeAssetPaidByLeftMakerAmount || constants.ZERO_AMOUNT,
rightMakerFeeAssetPaidByRightMakerAmount:
partial.rightMakerFeeAssetPaidByRightMakerAmount || constants.ZERO_AMOUNT,
leftMakerAssetReceivedByTakerAmount:
partial.leftMakerAssetReceivedByTakerAmount || constants.ZERO_AMOUNT,
rightMakerAssetReceivedByTakerAmount:
partial.rightMakerAssetReceivedByTakerAmount || constants.ZERO_AMOUNT,
leftTakerFeeAssetPaidByTakerAmount:
partial.leftTakerFeeAssetPaidByTakerAmount || constants.ZERO_AMOUNT,
rightTakerFeeAssetPaidByTakerAmount:
partial.rightTakerFeeAssetPaidByTakerAmount || constants.ZERO_AMOUNT,
leftProtocolFeePaidByTakerInEthAmount:
partial.leftProtocolFeePaidByTakerInEthAmount || constants.ZERO_AMOUNT,
leftProtocolFeePaidByTakerInWethAmount:
partial.leftProtocolFeePaidByTakerInWethAmount || constants.ZERO_AMOUNT,
rightProtocolFeePaidByTakerInEthAmount:
partial.rightProtocolFeePaidByTakerInEthAmount || constants.ZERO_AMOUNT,
rightProtocolFeePaidByTakerInWethAmount:
partial.rightProtocolFeePaidByTakerInWethAmount || constants.ZERO_AMOUNT,
};
}
/**
* Simulates matching a batch of orders by transferring amounts defined in
* `transferAmounts` and returns the results.
* @param orders The orders being batch matched and their filled states.
* @param takerAddress Address of taker (the address who matched the two orders)
* @param tokenBalances Current token balances.
* @param transferAmounts Amounts to transfer during the simulation.
* @return The new account balances and fill events that occurred during the match.
*/
async function simulateBatchMatchOrdersAsync(
orders: BatchMatchedOrders,
takerAddress: string,
deployment: DeploymentManager,
blockchainBalanceStore: BlockchainBalanceStore,
localBalanceStore: LocalBalanceStore,
matchPairs: Array<[number, number]>,
transferAmounts: Array<Partial<MatchTransferAmounts>>,
assetDataEncoder: IAssetDataContract,
): Promise<BatchMatchResults> {
// Initialize variables
let leftIdx = 0;
let rightIdx = 0;
let lastLeftIdx = -1;
let lastRightIdx = -1;
let matchedOrders: MatchedOrders;
const batchMatchResults: BatchMatchResults = {
matches: [],
filledAmounts: [],
leftFilledResults: [],
rightFilledResults: [],
};
// Loop over all of the matched pairs from the round
for (let i = 0; i < matchPairs.length; i++) {
leftIdx = matchPairs[i][0];
rightIdx = matchPairs[i][1];
// Construct a matched order out of the current left and right orders
matchedOrders = {
leftOrder: orders.leftOrders[leftIdx],
rightOrder: orders.rightOrders[rightIdx],
leftOrderTakerAssetFilledAmount: orders.leftOrdersTakerAssetFilledAmounts[leftIdx],
rightOrderTakerAssetFilledAmount: orders.rightOrdersTakerAssetFilledAmounts[rightIdx],
};
// If there has been a match recorded and one or both of the side indices have not changed,
// replace the side's taker asset filled amount
if (batchMatchResults.matches.length > 0) {
if (lastLeftIdx === leftIdx) {
matchedOrders.leftOrderTakerAssetFilledAmount = getLastMatch(
batchMatchResults,
).orders.leftOrderTakerAssetFilledAmount;
} else {
batchMatchResults.filledAmounts.push([
orders.leftOrders[lastLeftIdx],
getLastMatch(batchMatchResults).orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'left',
]);
}
if (lastRightIdx === rightIdx) {
matchedOrders.rightOrderTakerAssetFilledAmount = getLastMatch(
batchMatchResults,
).orders.rightOrderTakerAssetFilledAmount;
} else {
batchMatchResults.filledAmounts.push([
orders.rightOrders[lastRightIdx],
getLastMatch(batchMatchResults).orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'right',
]);
}
}
// Add the latest match to the batch match results
batchMatchResults.matches.push(
await simulateMatchOrdersAsync(
matchedOrders,
takerAddress,
deployment,
toFullMatchTransferAmounts(transferAmounts[i]),
assetDataEncoder,
blockchainBalanceStore,
localBalanceStore,
),
);
// Update the left and right fill results
if (lastLeftIdx === leftIdx) {
addFillResults(batchMatchResults.leftFilledResults[leftIdx], getLastMatch(batchMatchResults).fills[0]);
} else {
batchMatchResults.leftFilledResults.push({ ...getLastMatch(batchMatchResults).fills[0] });
}
if (lastRightIdx === rightIdx) {
addFillResults(batchMatchResults.rightFilledResults[rightIdx], getLastMatch(batchMatchResults).fills[1]);
} else {
batchMatchResults.rightFilledResults.push({ ...getLastMatch(batchMatchResults).fills[1] });
}
lastLeftIdx = leftIdx;
lastRightIdx = rightIdx;
}
for (let i = leftIdx + 1; i < orders.leftOrders.length; i++) {
batchMatchResults.leftFilledResults.push(emptyFillEventArgs());
}
for (let i = rightIdx + 1; i < orders.rightOrders.length; i++) {
batchMatchResults.rightFilledResults.push(emptyFillEventArgs());
}
// The two orders indexed by lastLeftIdx and lastRightIdx were potentially
// filled; however, the TakerAssetFilledAmounts that pertain to these orders
// will not have been added to batchMatchResults, so we need to write them
// here.
batchMatchResults.filledAmounts.push([
orders.leftOrders[lastLeftIdx],
getLastMatch(batchMatchResults).orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'left',
]);
batchMatchResults.filledAmounts.push([
orders.rightOrders[lastRightIdx],
getLastMatch(batchMatchResults).orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'right',
]);
// Return the batch match results
return batchMatchResults;
}
/** /**
* Simulates matching two orders by transferring amounts defined in * Simulates matching two orders by transferring amounts defined in
* `transferAmounts` and returns the results. * `transferAmounts` and returns the results.
* @param orders The orders being matched and their filled states. * @param orders The orders being matched and their filled states.
* @param takerAddress Address of taker (the address who matched the two orders) * @param takerAddress Address of taker (the address who matched the two orders)
* @param tokenBalances Current token balances.
* @param transferAmounts Amounts to transfer during the simulation. * @param transferAmounts Amounts to transfer during the simulation.
* @param localBalanceStore The balance store to use for the simulation.
* @return The new account balances and fill events that occurred during the match. * @return The new account balances and fill events that occurred during the match.
*/ */
async function simulateMatchOrdersAsync( protected async _simulateMatchOrdersAsync(
orders: MatchedOrders, orders: MatchedOrders,
takerAddress: string, takerAddress: string,
deployment: DeploymentManager,
transferAmounts: MatchTransferAmounts, transferAmounts: MatchTransferAmounts,
assetDataEncoder: IAssetDataContract,
blockchainBalanceStore: BlockchainBalanceStore,
localBalanceStore: LocalBalanceStore, localBalanceStore: LocalBalanceStore,
): Promise<MatchResults> { ): Promise<MatchResults> {
// prettier-ignore // prettier-ignore
@@ -517,26 +321,28 @@ async function simulateMatchOrdersAsync(
); );
// Protocol Fee // Protocol Fee
const wethAssetData = assetDataEncoder.ERC20Token.getABIEncodedTransactionData(deployment.tokens.weth.address); const wethAssetData = this._deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
this._deployment.tokens.weth.address,
);
localBalanceStore.sendEth( localBalanceStore.sendEth(
takerAddress, takerAddress,
deployment.staking.stakingProxy.address, this._deployment.staking.stakingProxy.address,
transferAmounts.leftProtocolFeePaidByTakerInEthAmount, transferAmounts.leftProtocolFeePaidByTakerInEthAmount,
); );
localBalanceStore.sendEth( localBalanceStore.sendEth(
takerAddress, takerAddress,
deployment.staking.stakingProxy.address, this._deployment.staking.stakingProxy.address,
transferAmounts.rightProtocolFeePaidByTakerInEthAmount, transferAmounts.rightProtocolFeePaidByTakerInEthAmount,
); );
await localBalanceStore.transferAssetAsync( await localBalanceStore.transferAssetAsync(
takerAddress, takerAddress,
deployment.staking.stakingProxy.address, this._deployment.staking.stakingProxy.address,
transferAmounts.leftProtocolFeePaidByTakerInWethAmount, transferAmounts.leftProtocolFeePaidByTakerInWethAmount,
wethAssetData, wethAssetData,
); );
await localBalanceStore.transferAssetAsync( await localBalanceStore.transferAssetAsync(
takerAddress, takerAddress,
deployment.staking.stakingProxy.address, this._deployment.staking.stakingProxy.address,
transferAmounts.rightProtocolFeePaidByTakerInWethAmount, transferAmounts.rightProtocolFeePaidByTakerInWethAmount,
wethAssetData, wethAssetData,
); );
@@ -544,19 +350,142 @@ async function simulateMatchOrdersAsync(
return matchResults; return matchResults;
} }
/**
* Simulates matching a batch of orders by transferring amounts defined in
* `transferAmounts` and returns the results.
* @param orders The orders being batch matched and their filled states.
* @param takerAddress Address of taker (the address who matched the two orders)
* @param matchPairs The pairs of orders that are expected to be matched.
* @param transferAmounts Amounts to transfer during the simulation.
* @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 _simulateBatchMatchOrdersAsync(
orders: BatchMatchedOrders,
takerAddress: string,
matchPairs: Array<[number, number]>,
transferAmounts: Array<Partial<MatchTransferAmounts>>,
localBalanceStore: LocalBalanceStore,
): Promise<BatchMatchResults> {
// Initialize variables
let leftIdx = 0;
let rightIdx = 0;
let lastLeftIdx = -1;
let lastRightIdx = -1;
let matchedOrders: MatchedOrders;
const batchMatchResults: BatchMatchResults = {
matches: [],
filledAmounts: [],
leftFilledResults: [],
rightFilledResults: [],
};
// Loop over all of the matched pairs from the round
for (let i = 0; i < matchPairs.length; i++) {
leftIdx = matchPairs[i][0];
rightIdx = matchPairs[i][1];
// Construct a matched order out of the current left and right orders
matchedOrders = {
leftOrder: orders.leftOrders[leftIdx],
rightOrder: orders.rightOrders[rightIdx],
leftOrderTakerAssetFilledAmount: orders.leftOrdersTakerAssetFilledAmounts[leftIdx],
rightOrderTakerAssetFilledAmount: orders.rightOrdersTakerAssetFilledAmounts[rightIdx],
};
// If there has been a match recorded and one or both of the side indices have not changed,
// replace the side's taker asset filled amount
if (batchMatchResults.matches.length > 0) {
if (lastLeftIdx === leftIdx) {
matchedOrders.leftOrderTakerAssetFilledAmount = getLastMatch(
batchMatchResults,
).orders.leftOrderTakerAssetFilledAmount;
} else {
batchMatchResults.filledAmounts.push([
orders.leftOrders[lastLeftIdx],
getLastMatch(batchMatchResults).orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'left',
]);
}
if (lastRightIdx === rightIdx) {
matchedOrders.rightOrderTakerAssetFilledAmount = getLastMatch(
batchMatchResults,
).orders.rightOrderTakerAssetFilledAmount;
} else {
batchMatchResults.filledAmounts.push([
orders.rightOrders[lastRightIdx],
getLastMatch(batchMatchResults).orders.rightOrderTakerAssetFilledAmount ||
constants.ZERO_AMOUNT,
'right',
]);
}
}
// Add the latest match to the batch match results
batchMatchResults.matches.push(
await this._simulateMatchOrdersAsync(
matchedOrders,
takerAddress,
toFullMatchTransferAmounts(transferAmounts[i]),
localBalanceStore,
),
);
// Update the left and right fill results
if (lastLeftIdx === leftIdx) {
addFillResults(batchMatchResults.leftFilledResults[leftIdx], getLastMatch(batchMatchResults).fills[0]);
} else {
batchMatchResults.leftFilledResults.push({ ...getLastMatch(batchMatchResults).fills[0] });
}
if (lastRightIdx === rightIdx) {
addFillResults(
batchMatchResults.rightFilledResults[rightIdx],
getLastMatch(batchMatchResults).fills[1],
);
} else {
batchMatchResults.rightFilledResults.push({ ...getLastMatch(batchMatchResults).fills[1] });
}
lastLeftIdx = leftIdx;
lastRightIdx = rightIdx;
}
for (let i = leftIdx + 1; i < orders.leftOrders.length; i++) {
batchMatchResults.leftFilledResults.push(emptyFillEventArgs());
}
for (let i = rightIdx + 1; i < orders.rightOrders.length; i++) {
batchMatchResults.rightFilledResults.push(emptyFillEventArgs());
}
// The two orders indexed by lastLeftIdx and lastRightIdx were potentially
// filled; however, the TakerAssetFilledAmounts that pertain to these orders
// will not have been added to batchMatchResults, so we need to write them
// here.
batchMatchResults.filledAmounts.push([
orders.leftOrders[lastLeftIdx],
getLastMatch(batchMatchResults).orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'left',
]);
batchMatchResults.filledAmounts.push([
orders.rightOrders[lastRightIdx],
getLastMatch(batchMatchResults).orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT,
'right',
]);
// Return the batch match results
return batchMatchResults;
}
/** /**
* Checks that the results of `simulateBatchMatchOrders()` agrees with reality. * Checks that the results of `simulateBatchMatchOrders()` agrees with reality.
* @param batchMatchResults The results of a `simulateBatchMatchOrders()`. * @param batchMatchResults The results of a `simulateBatchMatchOrders()`.
* @param transactionReceipt The transaction receipt of a call to `matchOrders()`. * @param transactionReceipt The transaction receipt of a call to `matchOrders()`.
* @param actualTokenBalances The actual, on-chain token balances of known addresses. * @param localBalanceStore The balance store to use during the simulation.
* @param exchangeWrapper The ExchangeWrapper instance.
*/ */
async function assertBatchMatchResultsAsync( protected async _assertBatchMatchResultsAsync(
batchMatchResults: BatchMatchResults, batchMatchResults: BatchMatchResults,
transactionReceipt: TransactionReceiptWithDecodedLogs, transactionReceipt: TransactionReceiptWithDecodedLogs,
blockchainBalanceStore: BlockchainBalanceStore,
localBalanceStore: LocalBalanceStore, localBalanceStore: LocalBalanceStore,
exchange: ExchangeContract,
): Promise<void> { ): Promise<void> {
// Ensure that the batchMatchResults contain at least one match // Ensure that the batchMatchResults contain at least one match
expect(batchMatchResults.matches.length).to.be.gt(0); expect(batchMatchResults.matches.length).to.be.gt(0);
@@ -568,49 +497,189 @@ async function assertBatchMatchResultsAsync(
); );
// Update the blockchain balance store balances. // Update the blockchain balance store balances.
await blockchainBalanceStore.updateBalancesAsync(); await this._blockchainBalanceStore.updateBalancesAsync();
// Ensure that the actual and expected token balances are equivalent. // Ensure that the actual and expected token balances are equivalent.
localBalanceStore.assertEquals(blockchainBalanceStore); localBalanceStore.assertEquals(this._blockchainBalanceStore);
// Check the Exchange state. // Check the Exchange state.
await assertPostBatchExchangeStateAsync(batchMatchResults, exchange); await this._assertPostBatchExchangeStateAsync(batchMatchResults);
} }
/** /**
* Checks that the results of `simulateMatchOrders()` agrees with reality. * Checks that the results of `simulateMatchOrders()` agrees with reality.
* @param matchResults The results of a `simulateMatchOrders()`. * @param matchResults The results of a `simulateMatchOrders()`.
* @param transactionReceipt The transaction receipt of a call to `matchOrders()`. * @param transactionReceipt The transaction receipt of a call to `matchOrders()`.
* @param actualTokenBalances The actual, on-chain token balances of known addresses. * @param localBalanceStore The balance store to use during the simulation.
* @param exchangeWrapper The ExchangeWrapper instance.
*/ */
async function assertMatchResultsAsync( protected async _assertMatchResultsAsync(
matchResults: MatchResults, matchResults: MatchResults,
transactionReceipt: TransactionReceiptWithDecodedLogs, transactionReceipt: TransactionReceiptWithDecodedLogs,
localBalanceStore: LocalBalanceStore, localBalanceStore: LocalBalanceStore,
blockchainBalanceStore: BlockchainBalanceStore,
exchange: ExchangeContract,
): Promise<void> { ): Promise<void> {
// Check the fill events. // Check the fill events.
assertFillEvents(matchResults.fills, transactionReceipt); assertFillEvents(matchResults.fills, transactionReceipt);
// Update the blockchain balance store balances. // Update the blockchain balance store balances.
await blockchainBalanceStore.updateBalancesAsync(); await this._blockchainBalanceStore.updateBalancesAsync();
// Check the token balances. // Check the token balances.
localBalanceStore.assertEquals(blockchainBalanceStore); localBalanceStore.assertEquals(this._blockchainBalanceStore);
// Check the Exchange state. // Check the Exchange state.
await assertPostExchangeStateAsync(matchResults, exchange); await this._assertPostExchangeStateAsync(matchResults);
}
/**
* Asserts the initial exchange state for batch matched orders.
* @param orders Batch matched orders with intial filled amounts.
*/
private async _assertBatchOrderStatesAsync(orders: BatchMatchedOrders): Promise<void> {
for (let i = 0; i < orders.leftOrders.length; i++) {
await this._assertOrderFilledAmountAsync(
orders.leftOrders[i],
orders.leftOrdersTakerAssetFilledAmounts[i],
'left',
);
}
for (let i = 0; i < orders.rightOrders.length; i++) {
await this._assertOrderFilledAmountAsync(
orders.rightOrders[i],
orders.rightOrdersTakerAssetFilledAmounts[i],
'right',
);
}
}
/**
* Asserts the initial exchange state for matched orders.
* @param orders Matched orders with intial filled amounts.
*/
private async _assertInitialOrderStatesAsync(orders: MatchedOrders): Promise<void> {
const pairs = [
[orders.leftOrder, orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT],
[orders.rightOrder, orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT],
] as Array<[SignedOrder, BigNumber]>;
await Promise.all(
pairs.map(async ([order, expectedFilledAmount]) => {
const side = order === orders.leftOrder ? 'left' : 'right';
await this._assertOrderFilledAmountAsync(order, expectedFilledAmount, side);
}),
);
}
/**
* Asserts the exchange state after a call to `batchMatchOrders()`.
* @param batchMatchResults Results from a call to `simulateBatchMatchOrders()`.
*/
private async _assertPostBatchExchangeStateAsync(batchMatchResults: BatchMatchResults): Promise<void> {
await this._assertTriplesExchangeStateAsync(batchMatchResults.filledAmounts);
}
/**
* Asserts the exchange state after a call to `matchOrders()`.
* @param matchResults Results from a call to `simulateMatchOrders()`.
*/
private async _assertPostExchangeStateAsync(matchResults: MatchResults): Promise<void> {
const triples = [
[matchResults.orders.leftOrder, matchResults.orders.leftOrderTakerAssetFilledAmount, 'left'],
[matchResults.orders.rightOrder, matchResults.orders.rightOrderTakerAssetFilledAmount, 'right'],
] as Array<[SignedOrder, BigNumber, string]>;
await this._assertTriplesExchangeStateAsync(triples);
}
/**
* Asserts the exchange state represented by provided sequence of triples.
* @param triples The sequence of triples to verifiy. Each triple consists
* of an `order`, a `takerAssetFilledAmount`, and a `side`,
* which will be used to determine if the exchange's state
* is valid.
*/
private async _assertTriplesExchangeStateAsync(triples: Array<[SignedOrder, BigNumber, string]>): Promise<void> {
await Promise.all(
triples.map(async ([order, expectedFilledAmount, side]) => {
expect(['left', 'right']).to.include(side);
await this._assertOrderFilledAmountAsync(order, expectedFilledAmount, side);
}),
);
}
/**
* Asserts that the provided order's fill amount and order status
* are the expected values.
* @param order The order to verify for a correct state.
* @param expectedFilledAmount The amount that the order should
* have been filled.
* @param side The side that the provided order should be matched on.
*/
private async _assertOrderFilledAmountAsync(
order: SignedOrder,
expectedFilledAmount: BigNumber,
side: string,
): Promise<void> {
const orderInfo = await this._deployment.exchange.getOrderInfo.callAsync(order);
// Check filled amount of order.
const actualFilledAmount = orderInfo.orderTakerAssetFilledAmount;
expect(actualFilledAmount, `${side} order final filled amount`).to.be.bignumber.equal(expectedFilledAmount);
// Check status of order.
const expectedStatus = expectedFilledAmount.isGreaterThanOrEqualTo(order.takerAssetAmount)
? OrderStatus.FullyFilled
: OrderStatus.Fillable;
const actualStatus = orderInfo.orderStatus;
expect(actualStatus, `${side} order final status`).to.equal(expectedStatus);
}
}
/**
* Converts a `Partial<MatchTransferAmounts>` to a `MatchTransferAmounts` by
* filling in missing fields with zero.
*/
function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): MatchTransferAmounts {
// prettier-ignore
return {
leftMakerAssetSoldByLeftMakerAmount:
partial.leftMakerAssetSoldByLeftMakerAmount ||
partial.leftMakerAssetBoughtByRightMakerAmount ||
constants.ZERO_AMOUNT,
rightMakerAssetSoldByRightMakerAmount:
partial.rightMakerAssetSoldByRightMakerAmount ||
partial.rightMakerAssetBoughtByLeftMakerAmount ||
constants.ZERO_AMOUNT,
rightMakerAssetBoughtByLeftMakerAmount:
partial.rightMakerAssetBoughtByLeftMakerAmount ||
partial.rightMakerAssetSoldByRightMakerAmount ||
constants.ZERO_AMOUNT,
leftMakerAssetBoughtByRightMakerAmount: partial.leftMakerAssetBoughtByRightMakerAmount ||
partial.leftMakerAssetSoldByLeftMakerAmount ||
constants.ZERO_AMOUNT,
leftMakerFeeAssetPaidByLeftMakerAmount:
partial.leftMakerFeeAssetPaidByLeftMakerAmount || constants.ZERO_AMOUNT,
rightMakerFeeAssetPaidByRightMakerAmount:
partial.rightMakerFeeAssetPaidByRightMakerAmount || constants.ZERO_AMOUNT,
leftMakerAssetReceivedByTakerAmount:
partial.leftMakerAssetReceivedByTakerAmount || constants.ZERO_AMOUNT,
rightMakerAssetReceivedByTakerAmount:
partial.rightMakerAssetReceivedByTakerAmount || constants.ZERO_AMOUNT,
leftTakerFeeAssetPaidByTakerAmount:
partial.leftTakerFeeAssetPaidByTakerAmount || constants.ZERO_AMOUNT,
rightTakerFeeAssetPaidByTakerAmount:
partial.rightTakerFeeAssetPaidByTakerAmount || constants.ZERO_AMOUNT,
leftProtocolFeePaidByTakerInEthAmount:
partial.leftProtocolFeePaidByTakerInEthAmount || constants.ZERO_AMOUNT,
leftProtocolFeePaidByTakerInWethAmount:
partial.leftProtocolFeePaidByTakerInWethAmount || constants.ZERO_AMOUNT,
rightProtocolFeePaidByTakerInEthAmount:
partial.rightProtocolFeePaidByTakerInEthAmount || constants.ZERO_AMOUNT,
rightProtocolFeePaidByTakerInWethAmount:
partial.rightProtocolFeePaidByTakerInWethAmount || constants.ZERO_AMOUNT,
};
} }
/** /**
* Checks values from the logs produced by Exchange.matchOrders against * Checks values from the logs produced by Exchange.matchOrders against
* the expected transfer amounts. * the expected transfer amounts.
* @param orders The matched orders. * @param orders The matched orders.
* @param takerAddress Address of taker (account that called Exchange.matchOrders)
* @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders. * @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders.
* @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
*/ */
function assertFillEvents(expectedFills: FillEventArgs[], transactionReceipt: TransactionReceiptWithDecodedLogs): void { function assertFillEvents(expectedFills: FillEventArgs[], transactionReceipt: TransactionReceiptWithDecodedLogs): void {
// Extract the actual `Fill` events. // Extract the actual `Fill` events.
@@ -703,120 +772,6 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
})); }));
} }
/**
* Asserts the initial exchange state for batch matched orders.
* @param orders Batch matched orders with intial filled amounts.
* @param exchangeWrapper ExchangeWrapper instance.
*/
async function assertBatchOrderStatesAsync(orders: BatchMatchedOrders, exchange: ExchangeContract): Promise<void> {
for (let i = 0; i < orders.leftOrders.length; i++) {
await assertOrderFilledAmountAsync(
orders.leftOrders[i],
orders.leftOrdersTakerAssetFilledAmounts[i],
'left',
exchange,
);
}
for (let i = 0; i < orders.rightOrders.length; i++) {
await assertOrderFilledAmountAsync(
orders.rightOrders[i],
orders.rightOrdersTakerAssetFilledAmounts[i],
'right',
exchange,
);
}
}
/**
* Asserts the initial exchange state for matched orders.
* @param orders Matched orders with intial filled amounts.
* @param exchangeWrapper ExchangeWrapper instance.
*/
async function assertInitialOrderStatesAsync(orders: MatchedOrders, exchange: ExchangeContract): Promise<void> {
const pairs = [
[orders.leftOrder, orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT],
[orders.rightOrder, orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT],
] as Array<[SignedOrder, BigNumber]>;
await Promise.all(
pairs.map(async ([order, expectedFilledAmount]) => {
const side = order === orders.leftOrder ? 'left' : 'right';
await assertOrderFilledAmountAsync(order, expectedFilledAmount, side, exchange);
}),
);
}
/**
* Asserts the exchange state after a call to `batchMatchOrders()`.
* @param batchMatchResults Results from a call to `simulateBatchMatchOrders()`.
* @param exchangeWrapper The ExchangeWrapper instance.
*/
async function assertPostBatchExchangeStateAsync(
batchMatchResults: BatchMatchResults,
exchange: ExchangeContract,
): Promise<void> {
await assertTriplesExchangeStateAsync(batchMatchResults.filledAmounts, exchange);
}
/**
* Asserts the exchange state after a call to `matchOrders()`.
* @param matchResults Results from a call to `simulateMatchOrders()`.
* @param exchangeWrapper The ExchangeWrapper instance.
*/
async function assertPostExchangeStateAsync(matchResults: MatchResults, exchange: ExchangeContract): Promise<void> {
const triples = [
[matchResults.orders.leftOrder, matchResults.orders.leftOrderTakerAssetFilledAmount, 'left'],
[matchResults.orders.rightOrder, matchResults.orders.rightOrderTakerAssetFilledAmount, 'right'],
] as Array<[SignedOrder, BigNumber, string]>;
await assertTriplesExchangeStateAsync(triples, exchange);
}
/**
* Asserts the exchange state represented by provided sequence of triples.
* @param triples The sequence of triples to verifiy. Each triple consists
* of an `order`, a `takerAssetFilledAmount`, and a `side`,
* which will be used to determine if the exchange's state
* is valid.
* @param exchangeWrapper The ExchangeWrapper instance.
*/
async function assertTriplesExchangeStateAsync(
triples: Array<[SignedOrder, BigNumber, string]>,
exchange: ExchangeContract,
): Promise<void> {
await Promise.all(
triples.map(async ([order, expectedFilledAmount, side]) => {
expect(['left', 'right']).to.include(side);
await assertOrderFilledAmountAsync(order, expectedFilledAmount, side, exchange);
}),
);
}
/**
* Asserts that the provided order's fill amount and order status
* are the expected values.
* @param order The order to verify for a correct state.
* @param expectedFilledAmount The amount that the order should
* have been filled.
* @param side The side that the provided order should be matched on.
* @param exchange The exchange contract that is in the current deployment.
*/
async function assertOrderFilledAmountAsync(
order: SignedOrder,
expectedFilledAmount: BigNumber,
side: string,
exchange: ExchangeContract,
): Promise<void> {
const orderInfo = await exchange.getOrderInfo.callAsync(order);
// Check filled amount of order.
const actualFilledAmount = orderInfo.orderTakerAssetFilledAmount;
expect(actualFilledAmount, `${side} order final filled amount`).to.be.bignumber.equal(expectedFilledAmount);
// Check status of order.
const expectedStatus = expectedFilledAmount.isGreaterThanOrEqualTo(order.takerAssetAmount)
? OrderStatus.FullyFilled
: OrderStatus.Fillable;
const actualStatus = orderInfo.orderStatus;
expect(actualStatus, `${side} order final status`).to.equal(expectedStatus);
}
/** /**
* Gets the last match in a BatchMatchResults object. * Gets the last match in a BatchMatchResults object.
* @param batchMatchResults The BatchMatchResults object. * @param batchMatchResults The BatchMatchResults object.