916 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			916 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
 | 
						|
import { BlockchainBalanceStore, ExchangeContract, LocalBalanceStore } from '@0x/contracts-exchange';
 | 
						|
import { constants, expect, OrderStatus } from '@0x/contracts-test-utils';
 | 
						|
import { orderHashUtils } from '@0x/order-utils';
 | 
						|
import { BatchMatchedFillResults, FillResults, MatchedFillResults, SignedOrder } from '@0x/types';
 | 
						|
import { BigNumber } from '@0x/utils';
 | 
						|
import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
 | 
						|
import * as _ from 'lodash';
 | 
						|
 | 
						|
import { DeploymentManager } from '../deployment_manager';
 | 
						|
 | 
						|
export interface FillEventArgs {
 | 
						|
    orderHash: string;
 | 
						|
    makerAddress: string;
 | 
						|
    takerAddress: string;
 | 
						|
    makerAssetFilledAmount: BigNumber;
 | 
						|
    takerAssetFilledAmount: BigNumber;
 | 
						|
    makerFeePaid: BigNumber;
 | 
						|
    takerFeePaid: BigNumber;
 | 
						|
    protocolFeePaid: BigNumber;
 | 
						|
}
 | 
						|
 | 
						|
export interface MatchTransferAmounts {
 | 
						|
    // Assets being traded.
 | 
						|
    leftMakerAssetSoldByLeftMakerAmount: BigNumber; // leftMakerAssetBoughtByRightMakerAmount if omitted.
 | 
						|
    rightMakerAssetSoldByRightMakerAmount: BigNumber; // rightMakerAssetBoughtByLeftMakerAmount if omitted.
 | 
						|
    rightMakerAssetBoughtByLeftMakerAmount: BigNumber; // rightMakerAssetSoldByRightMakerAmount if omitted.
 | 
						|
    leftMakerAssetBoughtByRightMakerAmount: BigNumber; // leftMakerAssetSoldByLeftMakerAmount if omitted.
 | 
						|
    // Taker profit.
 | 
						|
    leftMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    rightMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    // Maker fees.
 | 
						|
    leftMakerFeeAssetPaidByLeftMakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    rightMakerFeeAssetPaidByRightMakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    // Taker fees.
 | 
						|
    leftTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    rightTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted.
 | 
						|
    // Protocol fees.
 | 
						|
    leftProtocolFeePaidByTakerInEthAmount: BigNumber; // 0 if omitted
 | 
						|
    leftProtocolFeePaidByTakerInWethAmount: BigNumber; // 0 if omitted
 | 
						|
    rightProtocolFeePaidByTakerInWethAmount: BigNumber; // 0 if omitted
 | 
						|
    rightProtocolFeePaidByTakerInEthAmount: BigNumber; // 0 if omitted
 | 
						|
}
 | 
						|
 | 
						|
export interface MatchResults {
 | 
						|
    orders: MatchedOrders;
 | 
						|
    fills: FillEventArgs[];
 | 
						|
}
 | 
						|
 | 
						|
export interface BatchMatchResults {
 | 
						|
    matches: MatchResults[];
 | 
						|
    filledAmounts: Array<[SignedOrder, BigNumber, string]>;
 | 
						|
    leftFilledResults: FillEventArgs[];
 | 
						|
    rightFilledResults: FillEventArgs[];
 | 
						|
}
 | 
						|
 | 
						|
export interface BatchMatchedOrders {
 | 
						|
    leftOrders: SignedOrder[];
 | 
						|
    rightOrders: SignedOrder[];
 | 
						|
    leftOrdersTakerAssetFilledAmounts: BigNumber[];
 | 
						|
    rightOrdersTakerAssetFilledAmounts: BigNumber[];
 | 
						|
}
 | 
						|
 | 
						|
export interface MatchedOrders {
 | 
						|
    leftOrder: SignedOrder;
 | 
						|
    rightOrder: SignedOrder;
 | 
						|
    leftOrderTakerAssetFilledAmount?: BigNumber;
 | 
						|
    rightOrderTakerAssetFilledAmount?: BigNumber;
 | 
						|
}
 | 
						|
 | 
						|
export class MatchOrderTester {
 | 
						|
    /**
 | 
						|
     * Constructs new MatchOrderTester.
 | 
						|
     */
 | 
						|
    constructor(
 | 
						|
        protected readonly _deployment: DeploymentManager,
 | 
						|
        protected readonly _blockchainBalanceStore: BlockchainBalanceStore,
 | 
						|
    ) {}
 | 
						|
 | 
						|
    /**
 | 
						|
     * Performs batch order matching on a set of complementary orders and asserts results.
 | 
						|
     * @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
 | 
						|
     *                   the expected simulation.
 | 
						|
     * @param expectedTransferAmounts Expected amounts transferred as a result of each round of
 | 
						|
     *                                order matching. Omitted fields are either set to 0 or their
 | 
						|
     *                                complementary field.
 | 
						|
     * @param withMaximalFill A boolean that indicates whether the "maximal fill" order matching
 | 
						|
     *                        strategy should be used.
 | 
						|
     * @return Results of `batchMatchOrders()`.
 | 
						|
     */
 | 
						|
    public async batchMatchOrdersAndAssertEffectsAsync(
 | 
						|
        orders: BatchMatchedOrders,
 | 
						|
        takerAddress: string,
 | 
						|
        value: BigNumber,
 | 
						|
        matchPairs: Array<[number, number]>,
 | 
						|
        expectedTransferAmounts: Array<Partial<MatchTransferAmounts>>,
 | 
						|
        withMaximalFill: boolean,
 | 
						|
    ): Promise<BatchMatchResults> {
 | 
						|
        // Ensure that the provided input is valid.
 | 
						|
        expect(matchPairs.length).to.be.eq(expectedTransferAmounts.length);
 | 
						|
        expect(orders.leftOrders.length).to.be.eq(orders.leftOrdersTakerAssetFilledAmounts.length);
 | 
						|
        expect(orders.rightOrders.length).to.be.eq(orders.rightOrdersTakerAssetFilledAmounts.length);
 | 
						|
 | 
						|
        // Ensure that the exchange is in the expected state.
 | 
						|
        await this._assertBatchOrderStatesAsync(orders);
 | 
						|
 | 
						|
        // Update the blockchain balance store and create a new local balance store
 | 
						|
        // with the same initial balances.
 | 
						|
        await this._blockchainBalanceStore.updateBalancesAsync();
 | 
						|
        const localBalanceStore = LocalBalanceStore.create(this._deployment.devUtils, this._blockchainBalanceStore);
 | 
						|
 | 
						|
        // Execute `batchMatchOrders()`
 | 
						|
        let actualBatchMatchResults;
 | 
						|
        let transactionReceipt;
 | 
						|
        if (withMaximalFill) {
 | 
						|
            [actualBatchMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
 | 
						|
                this._deployment.exchange.batchMatchOrdersWithMaximalFill,
 | 
						|
                orders.leftOrders,
 | 
						|
                orders.rightOrders,
 | 
						|
                orders.leftOrders.map(order => order.signature),
 | 
						|
                orders.rightOrders.map(order => order.signature),
 | 
						|
                { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
 | 
						|
            );
 | 
						|
        } else {
 | 
						|
            [actualBatchMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
 | 
						|
                this._deployment.exchange.batchMatchOrders,
 | 
						|
                orders.leftOrders,
 | 
						|
                orders.rightOrders,
 | 
						|
                orders.leftOrders.map(order => order.signature),
 | 
						|
                orders.rightOrders.map(order => order.signature),
 | 
						|
                { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        // Burn the gas used to execute the transaction in the local balance store.
 | 
						|
        localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed));
 | 
						|
 | 
						|
        // Simulate the batch order match.
 | 
						|
        const expectedBatchMatchResults = await this._simulateBatchMatchOrdersAsync(
 | 
						|
            orders,
 | 
						|
            takerAddress,
 | 
						|
            matchPairs,
 | 
						|
            expectedTransferAmounts,
 | 
						|
            localBalanceStore,
 | 
						|
        );
 | 
						|
 | 
						|
        const expectedResults = convertToBatchMatchResults(expectedBatchMatchResults);
 | 
						|
        expect(actualBatchMatchResults).to.be.eql(expectedResults);
 | 
						|
 | 
						|
        // Validate the simulation against reality.
 | 
						|
        await this._assertBatchMatchResultsAsync(expectedBatchMatchResults, transactionReceipt, localBalanceStore);
 | 
						|
        return expectedBatchMatchResults;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Matches two complementary orders and asserts results.
 | 
						|
     * @param orders The matched orders and filled states.
 | 
						|
     * @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
 | 
						|
     *                                Omitted fields are either set to 0 or their complementary
 | 
						|
     *                                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
 | 
						|
     *                        strategy should be used.
 | 
						|
     * @return Results of `matchOrders()`.
 | 
						|
     */
 | 
						|
    public async matchOrdersAndAssertEffectsAsync(
 | 
						|
        orders: MatchedOrders,
 | 
						|
        expectedTransferAmounts: Partial<MatchTransferAmounts>,
 | 
						|
        takerAddress: string,
 | 
						|
        value: BigNumber,
 | 
						|
        withMaximalFill: boolean,
 | 
						|
    ): Promise<MatchResults> {
 | 
						|
        await this._assertInitialOrderStatesAsync(orders);
 | 
						|
 | 
						|
        // Update the blockchain balance store and create a new local balance store
 | 
						|
        // with the same initial balances.
 | 
						|
        await this._blockchainBalanceStore.updateBalancesAsync();
 | 
						|
        const localBalanceStore = LocalBalanceStore.create(this._deployment.devUtils, this._blockchainBalanceStore);
 | 
						|
 | 
						|
        // Execute `matchOrders()`
 | 
						|
        let actualMatchResults;
 | 
						|
        let transactionReceipt;
 | 
						|
        if (withMaximalFill) {
 | 
						|
            [actualMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
 | 
						|
                this._deployment.exchange.matchOrdersWithMaximalFill,
 | 
						|
                orders.leftOrder,
 | 
						|
                orders.rightOrder,
 | 
						|
                orders.leftOrder.signature,
 | 
						|
                orders.rightOrder.signature,
 | 
						|
                { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
 | 
						|
            );
 | 
						|
        } else {
 | 
						|
            [actualMatchResults, transactionReceipt] = await this._deployment.txHelper.getResultAndReceiptAsync(
 | 
						|
                this._deployment.exchange.matchOrders,
 | 
						|
                orders.leftOrder,
 | 
						|
                orders.rightOrder,
 | 
						|
                orders.leftOrder.signature,
 | 
						|
                orders.rightOrder.signature,
 | 
						|
                { from: takerAddress, gasPrice: DeploymentManager.gasPrice, value },
 | 
						|
            );
 | 
						|
        }
 | 
						|
        localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed));
 | 
						|
 | 
						|
        // Simulate the fill.
 | 
						|
        const expectedMatchResults = await this._simulateMatchOrdersAsync(
 | 
						|
            orders,
 | 
						|
            takerAddress,
 | 
						|
            toFullMatchTransferAmounts(expectedTransferAmounts),
 | 
						|
            localBalanceStore,
 | 
						|
        );
 | 
						|
        const expectedResults = convertToMatchResults(expectedMatchResults);
 | 
						|
        expect(actualMatchResults).to.be.eql(expectedResults);
 | 
						|
 | 
						|
        // Validate the simulation against reality.
 | 
						|
        await this._assertMatchResultsAsync(expectedMatchResults, transactionReceipt, localBalanceStore);
 | 
						|
        return expectedMatchResults;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Simulates matching two orders by transferring amounts defined in
 | 
						|
     * `transferAmounts` and returns the results.
 | 
						|
     * @param orders The orders being matched and their filled states.
 | 
						|
     * @param takerAddress Address of taker (the address who matched the two orders)
 | 
						|
     * @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 _simulateMatchOrdersAsync(
 | 
						|
        orders: MatchedOrders,
 | 
						|
        takerAddress: string,
 | 
						|
        transferAmounts: MatchTransferAmounts,
 | 
						|
        localBalanceStore: LocalBalanceStore,
 | 
						|
    ): Promise<MatchResults> {
 | 
						|
        // prettier-ignore
 | 
						|
        const matchResults = {
 | 
						|
        orders: {
 | 
						|
            leftOrder: orders.leftOrder,
 | 
						|
            leftOrderTakerAssetFilledAmount:
 | 
						|
                (orders.leftOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT).plus(
 | 
						|
                    transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
 | 
						|
                ),
 | 
						|
            rightOrder: orders.rightOrder,
 | 
						|
            rightOrderTakerAssetFilledAmount:
 | 
						|
                (orders.rightOrderTakerAssetFilledAmount || constants.ZERO_AMOUNT).plus(
 | 
						|
                    transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
 | 
						|
                ),
 | 
						|
        },
 | 
						|
        fills: simulateFillEvents(orders, takerAddress, transferAmounts),
 | 
						|
    };
 | 
						|
 | 
						|
        // Right maker asset -> left maker
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            orders.rightOrder.makerAddress,
 | 
						|
            orders.leftOrder.makerAddress,
 | 
						|
            transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
 | 
						|
            orders.rightOrder.makerAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) {
 | 
						|
            // Left maker fees
 | 
						|
            await localBalanceStore.transferAssetAsync(
 | 
						|
                orders.leftOrder.makerAddress,
 | 
						|
                orders.leftOrder.feeRecipientAddress,
 | 
						|
                transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
 | 
						|
                orders.leftOrder.makerFeeAssetData,
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        // Left maker asset -> right maker
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            orders.leftOrder.makerAddress,
 | 
						|
            orders.rightOrder.makerAddress,
 | 
						|
            transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
 | 
						|
            orders.leftOrder.makerAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) {
 | 
						|
            // Right maker fees
 | 
						|
            await localBalanceStore.transferAssetAsync(
 | 
						|
                orders.rightOrder.makerAddress,
 | 
						|
                orders.rightOrder.feeRecipientAddress,
 | 
						|
                transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
 | 
						|
                orders.rightOrder.makerFeeAssetData,
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        // Left taker profit
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            orders.leftOrder.makerAddress,
 | 
						|
            takerAddress,
 | 
						|
            transferAmounts.leftMakerAssetReceivedByTakerAmount,
 | 
						|
            orders.leftOrder.makerAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        // Right taker profit
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            orders.rightOrder.makerAddress,
 | 
						|
            takerAddress,
 | 
						|
            transferAmounts.rightMakerAssetReceivedByTakerAmount,
 | 
						|
            orders.rightOrder.makerAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        // Left taker fees
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            takerAddress,
 | 
						|
            orders.leftOrder.feeRecipientAddress,
 | 
						|
            transferAmounts.leftTakerFeeAssetPaidByTakerAmount,
 | 
						|
            orders.leftOrder.takerFeeAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        // Right taker fees
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            takerAddress,
 | 
						|
            orders.rightOrder.feeRecipientAddress,
 | 
						|
            transferAmounts.rightTakerFeeAssetPaidByTakerAmount,
 | 
						|
            orders.rightOrder.takerFeeAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        // Protocol Fee
 | 
						|
        const wethAssetData = this._deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
            this._deployment.tokens.weth.address,
 | 
						|
        );
 | 
						|
        localBalanceStore.sendEth(
 | 
						|
            takerAddress,
 | 
						|
            this._deployment.staking.stakingProxy.address,
 | 
						|
            transferAmounts.leftProtocolFeePaidByTakerInEthAmount,
 | 
						|
        );
 | 
						|
        localBalanceStore.sendEth(
 | 
						|
            takerAddress,
 | 
						|
            this._deployment.staking.stakingProxy.address,
 | 
						|
            transferAmounts.rightProtocolFeePaidByTakerInEthAmount,
 | 
						|
        );
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            takerAddress,
 | 
						|
            this._deployment.staking.stakingProxy.address,
 | 
						|
            transferAmounts.leftProtocolFeePaidByTakerInWethAmount,
 | 
						|
            wethAssetData,
 | 
						|
        );
 | 
						|
        await localBalanceStore.transferAssetAsync(
 | 
						|
            takerAddress,
 | 
						|
            this._deployment.staking.stakingProxy.address,
 | 
						|
            transferAmounts.rightProtocolFeePaidByTakerInWethAmount,
 | 
						|
            wethAssetData,
 | 
						|
        );
 | 
						|
 | 
						|
        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.
 | 
						|
     * @param batchMatchResults The results of a `simulateBatchMatchOrders()`.
 | 
						|
     * @param transactionReceipt The transaction receipt of a call to `matchOrders()`.
 | 
						|
     * @param localBalanceStore The balance store to use during the simulation.
 | 
						|
     */
 | 
						|
    protected async _assertBatchMatchResultsAsync(
 | 
						|
        batchMatchResults: BatchMatchResults,
 | 
						|
        transactionReceipt: TransactionReceiptWithDecodedLogs,
 | 
						|
        localBalanceStore: LocalBalanceStore,
 | 
						|
    ): Promise<void> {
 | 
						|
        // Ensure that the batchMatchResults contain at least one match
 | 
						|
        expect(batchMatchResults.matches.length).to.be.gt(0);
 | 
						|
 | 
						|
        // Check the fill events.
 | 
						|
        assertFillEvents(
 | 
						|
            batchMatchResults.matches.map(match => match.fills).reduce((total, fills) => total.concat(fills)),
 | 
						|
            transactionReceipt,
 | 
						|
        );
 | 
						|
 | 
						|
        // Update the blockchain balance store balances.
 | 
						|
        await this._blockchainBalanceStore.updateBalancesAsync();
 | 
						|
 | 
						|
        // Ensure that the actual and expected token balances are equivalent.
 | 
						|
        localBalanceStore.assertEquals(this._blockchainBalanceStore);
 | 
						|
 | 
						|
        // Check the Exchange state.
 | 
						|
        await this._assertPostBatchExchangeStateAsync(batchMatchResults);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Checks that the results of `simulateMatchOrders()` agrees with reality.
 | 
						|
     * @param matchResults The results of a `simulateMatchOrders()`.
 | 
						|
     * @param transactionReceipt The transaction receipt of a call to `matchOrders()`.
 | 
						|
     * @param localBalanceStore The balance store to use during the simulation.
 | 
						|
     */
 | 
						|
    protected async _assertMatchResultsAsync(
 | 
						|
        matchResults: MatchResults,
 | 
						|
        transactionReceipt: TransactionReceiptWithDecodedLogs,
 | 
						|
        localBalanceStore: LocalBalanceStore,
 | 
						|
    ): Promise<void> {
 | 
						|
        // Check the fill events.
 | 
						|
        assertFillEvents(matchResults.fills, transactionReceipt);
 | 
						|
 | 
						|
        // Update the blockchain balance store balances.
 | 
						|
        await this._blockchainBalanceStore.updateBalancesAsync();
 | 
						|
 | 
						|
        // Check the token balances.
 | 
						|
        localBalanceStore.assertEquals(this._blockchainBalanceStore);
 | 
						|
 | 
						|
        // Check the Exchange state.
 | 
						|
        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
 | 
						|
 *      the expected transfer amounts.
 | 
						|
 * @param orders The matched orders.
 | 
						|
 * @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders.
 | 
						|
 */
 | 
						|
function assertFillEvents(expectedFills: FillEventArgs[], transactionReceipt: TransactionReceiptWithDecodedLogs): void {
 | 
						|
    // Extract the actual `Fill` events.
 | 
						|
    const actualFills = extractFillEventsfromReceipt(transactionReceipt);
 | 
						|
    expect(actualFills.length, 'wrong number of Fill events').to.be.equal(expectedFills.length);
 | 
						|
    // Validate event arguments.
 | 
						|
    const fillPairs = _.zip(expectedFills, actualFills) as Array<[FillEventArgs, FillEventArgs]>;
 | 
						|
    for (const [expected, actual] of fillPairs) {
 | 
						|
        const side = expected === expectedFills[0] ? 'Left' : 'Right';
 | 
						|
        expect(actual.orderHash, `${side} order Fill event orderHash`).to.equal(expected.orderHash);
 | 
						|
        expect(actual.makerAddress, `${side} order Fill event makerAddress`).to.equal(expected.makerAddress);
 | 
						|
        expect(actual.takerAddress, `${side} order Fill event takerAddress`).to.equal(expected.takerAddress);
 | 
						|
        expect(actual.makerAssetFilledAmount, `${side} order Fill event makerAssetFilledAmount`).to.bignumber.equal(
 | 
						|
            expected.makerAssetFilledAmount,
 | 
						|
        );
 | 
						|
        expect(actual.takerAssetFilledAmount, `${side} order Fill event takerAssetFilledAmount`).to.bignumber.equal(
 | 
						|
            expected.takerAssetFilledAmount,
 | 
						|
        );
 | 
						|
        expect(actual.makerFeePaid, `${side} order Fill event makerFeePaid`).to.bignumber.equal(expected.makerFeePaid);
 | 
						|
        expect(actual.takerFeePaid, `${side} order Fill event takerFeePaid`).to.bignumber.equal(expected.takerFeePaid);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *  Create a pair of `Fill` events for a simulated `matchOrder()`.
 | 
						|
 */
 | 
						|
function simulateFillEvents(
 | 
						|
    orders: MatchedOrders,
 | 
						|
    takerAddress: string,
 | 
						|
    transferAmounts: MatchTransferAmounts,
 | 
						|
): [FillEventArgs, FillEventArgs] {
 | 
						|
    // prettier-ignore
 | 
						|
    return [
 | 
						|
        // Left order Fill
 | 
						|
        {
 | 
						|
            orderHash: orderHashUtils.getOrderHashHex(orders.leftOrder),
 | 
						|
            makerAddress: orders.leftOrder.makerAddress,
 | 
						|
            takerAddress,
 | 
						|
            makerAssetFilledAmount: transferAmounts.leftMakerAssetSoldByLeftMakerAmount,
 | 
						|
            takerAssetFilledAmount: transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
 | 
						|
            makerFeePaid: transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
 | 
						|
            takerFeePaid: transferAmounts.leftTakerFeeAssetPaidByTakerAmount,
 | 
						|
            protocolFeePaid: transferAmounts.leftProtocolFeePaidByTakerInEthAmount.plus(
 | 
						|
                transferAmounts.leftProtocolFeePaidByTakerInWethAmount,
 | 
						|
            ),
 | 
						|
        },
 | 
						|
        // Right order Fill
 | 
						|
        {
 | 
						|
            orderHash: orderHashUtils.getOrderHashHex(orders.rightOrder),
 | 
						|
            makerAddress: orders.rightOrder.makerAddress,
 | 
						|
            takerAddress,
 | 
						|
            makerAssetFilledAmount: transferAmounts.rightMakerAssetSoldByRightMakerAmount,
 | 
						|
            takerAssetFilledAmount: transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
 | 
						|
            makerFeePaid: transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
 | 
						|
            takerFeePaid: transferAmounts.rightTakerFeeAssetPaidByTakerAmount,
 | 
						|
            protocolFeePaid: transferAmounts.rightProtocolFeePaidByTakerInEthAmount.plus(
 | 
						|
                transferAmounts.rightProtocolFeePaidByTakerInWethAmount,
 | 
						|
            ),
 | 
						|
        },
 | 
						|
    ];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Extract `Fill` events from a transaction receipt.
 | 
						|
 */
 | 
						|
function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs): FillEventArgs[] {
 | 
						|
    interface RawFillEventArgs {
 | 
						|
        orderHash: string;
 | 
						|
        makerAddress: string;
 | 
						|
        takerAddress: string;
 | 
						|
        makerAssetFilledAmount: string;
 | 
						|
        takerAssetFilledAmount: string;
 | 
						|
        makerFeePaid: string;
 | 
						|
        takerFeePaid: string;
 | 
						|
        protocolFeePaid: string;
 | 
						|
    }
 | 
						|
    const actualFills = (_.filter(receipt.logs, ['event', 'Fill']) as any) as Array<
 | 
						|
        LogWithDecodedArgs<RawFillEventArgs>
 | 
						|
    >;
 | 
						|
    // Convert RawFillEventArgs to FillEventArgs.
 | 
						|
    return actualFills.map(fill => ({
 | 
						|
        orderHash: fill.args.orderHash,
 | 
						|
        makerAddress: fill.args.makerAddress,
 | 
						|
        takerAddress: fill.args.takerAddress,
 | 
						|
        makerAssetFilledAmount: new BigNumber(fill.args.makerAssetFilledAmount),
 | 
						|
        takerAssetFilledAmount: new BigNumber(fill.args.takerAssetFilledAmount),
 | 
						|
        makerFeePaid: new BigNumber(fill.args.makerFeePaid),
 | 
						|
        takerFeePaid: new BigNumber(fill.args.takerFeePaid),
 | 
						|
        protocolFeePaid: new BigNumber(fill.args.protocolFeePaid),
 | 
						|
    }));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets the last match in a BatchMatchResults object.
 | 
						|
 * @param batchMatchResults The BatchMatchResults object.
 | 
						|
 * @return The last match of the results.
 | 
						|
 */
 | 
						|
function getLastMatch(batchMatchResults: BatchMatchResults): MatchResults {
 | 
						|
    return batchMatchResults.matches[batchMatchResults.matches.length - 1];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Add a new fill results object to a total fill results object destructively.
 | 
						|
 * @param total The total fill results that should be updated.
 | 
						|
 * @param fill The new fill results that should be used to accumulate.
 | 
						|
 */
 | 
						|
function addFillResults(total: FillEventArgs, fill: FillEventArgs): void {
 | 
						|
    // Ensure that the total and fill are compatibe fill events
 | 
						|
    expect(total.orderHash).to.be.eq(fill.orderHash);
 | 
						|
    expect(total.makerAddress).to.be.eq(fill.makerAddress);
 | 
						|
    expect(total.takerAddress).to.be.eq(fill.takerAddress);
 | 
						|
 | 
						|
    // Add the fill results together
 | 
						|
    total.makerAssetFilledAmount = total.makerAssetFilledAmount.plus(fill.makerAssetFilledAmount);
 | 
						|
    total.takerAssetFilledAmount = total.takerAssetFilledAmount.plus(fill.takerAssetFilledAmount);
 | 
						|
    total.makerFeePaid = total.makerFeePaid.plus(fill.makerFeePaid);
 | 
						|
    total.takerFeePaid = total.takerFeePaid.plus(fill.takerFeePaid);
 | 
						|
    total.protocolFeePaid = total.protocolFeePaid.plus(fill.protocolFeePaid);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a BatchMatchResults object to the associated value that correspondes to a value that could be
 | 
						|
 * returned by `batchMatchOrders` or `batchMatchOrdersWithMaximalFill`.
 | 
						|
 * @param results The results object to convert
 | 
						|
 * @return The associated object that can be compared to the return value of `batchMatchOrders`
 | 
						|
 */
 | 
						|
function convertToBatchMatchResults(results: BatchMatchResults): BatchMatchedFillResults {
 | 
						|
    // Initialize the results object
 | 
						|
    const batchMatchedFillResults: BatchMatchedFillResults = {
 | 
						|
        left: [],
 | 
						|
        right: [],
 | 
						|
        profitInLeftMakerAsset: constants.ZERO_AMOUNT,
 | 
						|
        profitInRightMakerAsset: constants.ZERO_AMOUNT,
 | 
						|
    };
 | 
						|
    for (const match of results.matches) {
 | 
						|
        const leftSpread = match.fills[0].makerAssetFilledAmount.minus(match.fills[1].takerAssetFilledAmount);
 | 
						|
        // If the left maker spread is positive for match, update the profitInLeftMakerAsset
 | 
						|
        if (leftSpread.isGreaterThan(constants.ZERO_AMOUNT)) {
 | 
						|
            batchMatchedFillResults.profitInLeftMakerAsset = batchMatchedFillResults.profitInLeftMakerAsset.plus(
 | 
						|
                leftSpread,
 | 
						|
            );
 | 
						|
        }
 | 
						|
        const rightSpread = match.fills[1].makerAssetFilledAmount.minus(match.fills[0].takerAssetFilledAmount);
 | 
						|
        // If the right maker spread is positive for match, update the profitInRightMakerAsset
 | 
						|
        if (rightSpread.isGreaterThan(constants.ZERO_AMOUNT)) {
 | 
						|
            batchMatchedFillResults.profitInRightMakerAsset = batchMatchedFillResults.profitInRightMakerAsset.plus(
 | 
						|
                rightSpread,
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
    for (const fill of results.leftFilledResults) {
 | 
						|
        batchMatchedFillResults.left.push(convertToFillResults(fill));
 | 
						|
    }
 | 
						|
    for (const fill of results.rightFilledResults) {
 | 
						|
        batchMatchedFillResults.right.push(convertToFillResults(fill));
 | 
						|
    }
 | 
						|
    return batchMatchedFillResults;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a MatchResults object to the associated value that correspondes to a value that could be
 | 
						|
 * returned by `matchOrders` or `matchOrdersWithMaximalFill`.
 | 
						|
 * @param results The results object to convert
 | 
						|
 * @return The associated object that can be compared to the return value of `matchOrders`
 | 
						|
 */
 | 
						|
function convertToMatchResults(result: MatchResults): MatchedFillResults {
 | 
						|
    // If the left spread is negative, set it to zero
 | 
						|
    let profitInLeftMakerAsset = result.fills[0].makerAssetFilledAmount.minus(result.fills[1].takerAssetFilledAmount);
 | 
						|
    if (profitInLeftMakerAsset.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
 | 
						|
        profitInLeftMakerAsset = constants.ZERO_AMOUNT;
 | 
						|
    }
 | 
						|
 | 
						|
    // If the right spread is negative, set it to zero
 | 
						|
    let profitInRightMakerAsset = result.fills[1].makerAssetFilledAmount.minus(result.fills[0].takerAssetFilledAmount);
 | 
						|
    if (profitInRightMakerAsset.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
 | 
						|
        profitInRightMakerAsset = constants.ZERO_AMOUNT;
 | 
						|
    }
 | 
						|
 | 
						|
    const matchedFillResults: MatchedFillResults = {
 | 
						|
        left: {
 | 
						|
            makerAssetFilledAmount: result.fills[0].makerAssetFilledAmount,
 | 
						|
            takerAssetFilledAmount: result.fills[0].takerAssetFilledAmount,
 | 
						|
            makerFeePaid: result.fills[0].makerFeePaid,
 | 
						|
            takerFeePaid: result.fills[0].takerFeePaid,
 | 
						|
            protocolFeePaid: result.fills[0].protocolFeePaid,
 | 
						|
        },
 | 
						|
        right: {
 | 
						|
            makerAssetFilledAmount: result.fills[1].makerAssetFilledAmount,
 | 
						|
            takerAssetFilledAmount: result.fills[1].takerAssetFilledAmount,
 | 
						|
            makerFeePaid: result.fills[1].makerFeePaid,
 | 
						|
            takerFeePaid: result.fills[1].takerFeePaid,
 | 
						|
            protocolFeePaid: result.fills[1].protocolFeePaid,
 | 
						|
        },
 | 
						|
        profitInLeftMakerAsset,
 | 
						|
        profitInRightMakerAsset,
 | 
						|
    };
 | 
						|
    return matchedFillResults;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a fill event args object to the associated FillResults object.
 | 
						|
 * @param result The result to be converted to a FillResults object.
 | 
						|
 * @return The converted value.
 | 
						|
 */
 | 
						|
function convertToFillResults(result: FillEventArgs): FillResults {
 | 
						|
    const fillResults: FillResults = {
 | 
						|
        makerAssetFilledAmount: result.makerAssetFilledAmount,
 | 
						|
        takerAssetFilledAmount: result.takerAssetFilledAmount,
 | 
						|
        makerFeePaid: result.makerFeePaid,
 | 
						|
        takerFeePaid: result.takerFeePaid,
 | 
						|
        protocolFeePaid: result.protocolFeePaid,
 | 
						|
    };
 | 
						|
    return fillResults;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates an empty FillEventArgs object.
 | 
						|
 * @return The empty FillEventArgs object.
 | 
						|
 */
 | 
						|
function emptyFillEventArgs(): FillEventArgs {
 | 
						|
    const empty: FillEventArgs = {
 | 
						|
        orderHash: '',
 | 
						|
        makerAddress: '',
 | 
						|
        takerAddress: '',
 | 
						|
        makerAssetFilledAmount: constants.ZERO_AMOUNT,
 | 
						|
        takerAssetFilledAmount: constants.ZERO_AMOUNT,
 | 
						|
        makerFeePaid: constants.ZERO_AMOUNT,
 | 
						|
        takerFeePaid: constants.ZERO_AMOUNT,
 | 
						|
        protocolFeePaid: constants.ZERO_AMOUNT,
 | 
						|
    };
 | 
						|
    return empty;
 | 
						|
}
 | 
						|
// tslint:disable-line:max-file-line-count
 |