501 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			501 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// tslint:disable: max-file-line-count
 | 
						|
import { IAssetDataContract } from '@0x/contracts-asset-proxy';
 | 
						|
import { exchangeDataEncoder, ExchangeRevertErrors } from '@0x/contracts-exchange';
 | 
						|
import {
 | 
						|
    blockchainTests,
 | 
						|
    constants,
 | 
						|
    describe,
 | 
						|
    ExchangeFunctionName,
 | 
						|
    expect,
 | 
						|
    orderHashUtils,
 | 
						|
    transactionHashUtils,
 | 
						|
} from '@0x/contracts-test-utils';
 | 
						|
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
 | 
						|
import { BigNumber } from '@0x/utils';
 | 
						|
 | 
						|
import { Actor } from '../framework/actors/base';
 | 
						|
import { FeeRecipient } from '../framework/actors/fee_recipient';
 | 
						|
import { Maker } from '../framework/actors/maker';
 | 
						|
import { Taker } from '../framework/actors/taker';
 | 
						|
import { actorAddressesByName } from '../framework/actors/utils';
 | 
						|
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
 | 
						|
import { LocalBalanceStore } from '../framework/balances/local_balance_store';
 | 
						|
import { DeploymentManager } from '../framework/deployment_manager';
 | 
						|
 | 
						|
// tslint:disable:no-unnecessary-type-assertion
 | 
						|
blockchainTests.resets('Transaction <> protocol fee integration tests', env => {
 | 
						|
    let deployment: DeploymentManager;
 | 
						|
    let balanceStore: BlockchainBalanceStore;
 | 
						|
 | 
						|
    let maker: Maker;
 | 
						|
    let feeRecipient: FeeRecipient;
 | 
						|
    let alice: Taker;
 | 
						|
    let bob: Taker;
 | 
						|
    let charlie: Taker;
 | 
						|
    let wethless: Taker; // Used to test revert scenarios
 | 
						|
 | 
						|
    let order: SignedOrder; // All orders will have the same fields, modulo salt and expiration time
 | 
						|
    let transactionA: SignedZeroExTransaction; // fillOrder transaction signed by Alice
 | 
						|
    let transactionB: SignedZeroExTransaction; // fillOrder transaction signed by Bob
 | 
						|
    let transactionC: SignedZeroExTransaction; // fillOrder transaction signed by Charlie
 | 
						|
 | 
						|
    before(async () => {
 | 
						|
        deployment = await DeploymentManager.deployAsync(env, {
 | 
						|
            numErc20TokensToDeploy: 4,
 | 
						|
            numErc721TokensToDeploy: 0,
 | 
						|
            numErc1155TokensToDeploy: 0,
 | 
						|
        });
 | 
						|
        const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider);
 | 
						|
        const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20;
 | 
						|
 | 
						|
        alice = new Taker({ name: 'Alice', deployment });
 | 
						|
        bob = new Taker({ name: 'Bob', deployment });
 | 
						|
        charlie = new Taker({ name: 'Charlie', deployment });
 | 
						|
        wethless = new Taker({ name: 'wethless', deployment });
 | 
						|
        feeRecipient = new FeeRecipient({
 | 
						|
            name: 'Fee recipient',
 | 
						|
            deployment,
 | 
						|
        });
 | 
						|
        maker = new Maker({
 | 
						|
            name: 'Maker',
 | 
						|
            deployment,
 | 
						|
            orderConfig: {
 | 
						|
                feeRecipientAddress: feeRecipient.address,
 | 
						|
                makerAssetData: assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(),
 | 
						|
                takerAssetData: assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(),
 | 
						|
                makerFeeAssetData: assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(),
 | 
						|
                takerFeeAssetData: assetDataEncoder.ERC20Token(takerFeeToken.address).getABIEncodedTransactionData(),
 | 
						|
            },
 | 
						|
        });
 | 
						|
 | 
						|
        for (const taker of [alice, bob, charlie]) {
 | 
						|
            await taker.configureERC20TokenAsync(takerToken);
 | 
						|
            await taker.configureERC20TokenAsync(takerFeeToken);
 | 
						|
            await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
 | 
						|
        }
 | 
						|
        await wethless.configureERC20TokenAsync(takerToken);
 | 
						|
        await wethless.configureERC20TokenAsync(takerFeeToken);
 | 
						|
        await wethless.configureERC20TokenAsync(
 | 
						|
            deployment.tokens.weth,
 | 
						|
            deployment.staking.stakingProxy.address,
 | 
						|
            constants.ZERO_AMOUNT, // wethless taker has approved the proxy, but has no weth
 | 
						|
        );
 | 
						|
        await maker.configureERC20TokenAsync(makerToken);
 | 
						|
        await maker.configureERC20TokenAsync(makerFeeToken);
 | 
						|
 | 
						|
        balanceStore = new BlockchainBalanceStore(
 | 
						|
            {
 | 
						|
                ...actorAddressesByName([alice, bob, charlie, maker, feeRecipient]),
 | 
						|
                StakingProxy: deployment.staking.stakingProxy.address,
 | 
						|
            },
 | 
						|
            { erc20: { makerToken, takerToken, makerFeeToken, takerFeeToken, wETH: deployment.tokens.weth } },
 | 
						|
            {},
 | 
						|
        );
 | 
						|
        await balanceStore.updateBalancesAsync();
 | 
						|
 | 
						|
        order = await maker.signOrderAsync();
 | 
						|
        let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
        transactionA = await alice.signTransactionAsync({ data });
 | 
						|
 | 
						|
        order = await maker.signOrderAsync();
 | 
						|
        data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
        transactionB = await bob.signTransactionAsync({ data });
 | 
						|
 | 
						|
        order = await maker.signOrderAsync();
 | 
						|
        data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
        transactionC = await charlie.signTransactionAsync({ data });
 | 
						|
    });
 | 
						|
 | 
						|
    after(async () => {
 | 
						|
        Actor.reset();
 | 
						|
    });
 | 
						|
 | 
						|
    const REFUND_AMOUNT = new BigNumber(1);
 | 
						|
 | 
						|
    function protocolFeeError(
 | 
						|
        failedOrder: SignedOrder,
 | 
						|
        failedTransaction: SignedZeroExTransaction,
 | 
						|
    ): ExchangeRevertErrors.TransactionExecutionError {
 | 
						|
        const nestedError = new ExchangeRevertErrors.PayProtocolFeeError(
 | 
						|
            orderHashUtils.getOrderHashHex(failedOrder),
 | 
						|
            DeploymentManager.protocolFee,
 | 
						|
            maker.address,
 | 
						|
            wethless.address,
 | 
						|
            '0x',
 | 
						|
        ).encode();
 | 
						|
        return new ExchangeRevertErrors.TransactionExecutionError(
 | 
						|
            transactionHashUtils.getTransactionHashHex(failedTransaction),
 | 
						|
            nestedError,
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    describe('executeTransaction', () => {
 | 
						|
        const ETH_FEE_WITH_REFUND = DeploymentManager.protocolFee.plus(REFUND_AMOUNT);
 | 
						|
 | 
						|
        let expectedBalances: LocalBalanceStore;
 | 
						|
        beforeEach(async () => {
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            expectedBalances = LocalBalanceStore.create(balanceStore);
 | 
						|
        });
 | 
						|
        afterEach(async () => {
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            balanceStore.assertEquals(expectedBalances);
 | 
						|
        });
 | 
						|
 | 
						|
        describe('Simple', () => {
 | 
						|
            it('Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => {
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(transactionA, transactionA.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => {
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(transactionB, transactionB.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => {
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(transactionA, transactionA.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => {
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(transactionB, transactionB.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
 | 
						|
                const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
                const transaction = await wethless.signTransactionAsync({ data });
 | 
						|
                const tx = deployment.exchange
 | 
						|
                    .executeTransaction(transaction, transaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                return expect(tx).to.revertWith(protocolFeeError(order, transaction));
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice batchFillOrders; mixed protocol fees', async () => {
 | 
						|
                const orders = [order, await maker.signOrderAsync()];
 | 
						|
                const data = exchangeDataEncoder.encodeOrdersToExchangeData(
 | 
						|
                    ExchangeFunctionName.BatchFillOrders,
 | 
						|
                    orders,
 | 
						|
                );
 | 
						|
                const batchFillTransaction = await alice.signTransactionAsync({ data });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(batchFillTransaction, batchFillTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(orders, alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Bob batchFillOrders; mixed protocol fees', async () => {
 | 
						|
                const orders = [order, await maker.signOrderAsync()];
 | 
						|
                const data = exchangeDataEncoder.encodeOrdersToExchangeData(
 | 
						|
                    ExchangeFunctionName.BatchFillOrders,
 | 
						|
                    orders,
 | 
						|
                );
 | 
						|
                const batchFillTransaction = await bob.signTransactionAsync({ data });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(batchFillTransaction, batchFillTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(orders, bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
        });
 | 
						|
        describe('Nested', () => {
 | 
						|
            it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => {
 | 
						|
                const recursiveData = deployment.exchange
 | 
						|
                    .executeTransaction(transactionA, transactionA.signature)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(recursiveTransaction, recursiveTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => {
 | 
						|
                const recursiveData = deployment.exchange
 | 
						|
                    .executeTransaction(transactionB, transactionB.signature)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(recursiveTransaction, recursiveTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => {
 | 
						|
                const recursiveData = deployment.exchange
 | 
						|
                    .executeTransaction(transactionA, transactionA.signature)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(recursiveTransaction, recursiveTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => {
 | 
						|
                const recursiveData = deployment.exchange
 | 
						|
                    .executeTransaction(transactionB, transactionB.signature)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(recursiveTransaction, recursiveTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT);
 | 
						|
            });
 | 
						|
            it('Alice executeTransaction => Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
 | 
						|
                const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
                const transaction = await wethless.signTransactionAsync({ data });
 | 
						|
                const recursiveData = deployment.exchange
 | 
						|
                    .executeTransaction(transaction, transaction.signature)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
                const tx = deployment.exchange
 | 
						|
                    .executeTransaction(recursiveTransaction, recursiveTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                const expectedError = new ExchangeRevertErrors.TransactionExecutionError(
 | 
						|
                    transactionHashUtils.getTransactionHashHex(recursiveTransaction),
 | 
						|
                    protocolFeeError(order, transaction).encode(),
 | 
						|
                );
 | 
						|
                return expect(tx).to.revertWith(expectedError);
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('batchExecuteTransactions', () => {
 | 
						|
        let expectedBalances: LocalBalanceStore;
 | 
						|
        beforeEach(async () => {
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            expectedBalances = LocalBalanceStore.create(balanceStore);
 | 
						|
        });
 | 
						|
        afterEach(async () => {
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            balanceStore.assertEquals(expectedBalances);
 | 
						|
        });
 | 
						|
 | 
						|
        describe('Simple', () => {
 | 
						|
            // All orders' protocol fees paid in ETH by sender
 | 
						|
            const ETH_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(3).plus(REFUND_AMOUNT);
 | 
						|
            // First order's protocol fee paid in ETH by sender, the other two paid in WETH by their respective takers
 | 
						|
            const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(1).plus(REFUND_AMOUNT);
 | 
						|
 | 
						|
            it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in ETH', async () => {
 | 
						|
                const transactions = [transactionA, transactionB, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [alice.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    ETH_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in ETH', async () => {
 | 
						|
                const transactions = [transactionB, transactionA, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, alice.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    ETH_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in ETH', async () => {
 | 
						|
                const transactions = [transactionB, transactionC, transactionA];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, charlie.address, alice.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    ETH_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in wETH', async () => {
 | 
						|
                const transactions = [transactionA, transactionB, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [alice.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    REFUND_AMOUNT,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in wETH', async () => {
 | 
						|
                const transactions = [transactionB, transactionA, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, alice.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    REFUND_AMOUNT,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in wETH', async () => {
 | 
						|
                const transactions = [transactionB, transactionC, transactionA];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, charlie.address, alice.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    REFUND_AMOUNT,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
 | 
						|
                const transactions = [transactionA, transactionB, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [alice.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
 | 
						|
                const transactions = [transactionB, transactionA, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, alice.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; mixed protocol fees', async () => {
 | 
						|
                const transactions = [transactionB, transactionC, transactionA];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [bob.address, charlie.address, alice.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => {
 | 
						|
                const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]);
 | 
						|
                const failTransaction = await wethless.signTransactionAsync({ data });
 | 
						|
                const transactions = [transactionA, transactionB, failTransaction];
 | 
						|
                const signatures = transactions.map(transaction => transaction.signature);
 | 
						|
                const tx = deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expect(tx).to.revertWith(protocolFeeError(order, failTransaction));
 | 
						|
            });
 | 
						|
        });
 | 
						|
        describe('Nested', () => {
 | 
						|
            // First two orders' protocol fees paid in ETH by sender, the others paid in WETH by their respective takers
 | 
						|
            const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(2.5);
 | 
						|
 | 
						|
            // Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder
 | 
						|
            let nestedTransaction: SignedZeroExTransaction;
 | 
						|
            // Second fillOrder transaction signed by Bob
 | 
						|
            let transactionB2: SignedZeroExTransaction;
 | 
						|
            // Second fillOrder transaction signed by Charlie
 | 
						|
            let transactionC2: SignedZeroExTransaction;
 | 
						|
 | 
						|
            before(async () => {
 | 
						|
                let newOrder = await maker.signOrderAsync();
 | 
						|
                let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]);
 | 
						|
                transactionB2 = await bob.signTransactionAsync({ data });
 | 
						|
 | 
						|
                newOrder = await maker.signOrderAsync();
 | 
						|
                data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]);
 | 
						|
                transactionC2 = await charlie.signTransactionAsync({ data });
 | 
						|
 | 
						|
                const transactions = [transactionA, transactionB, transactionC];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const recursiveData = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .getABIEncodedTransactionData();
 | 
						|
                nestedTransaction = await alice.signTransactionAsync({ data: recursiveData });
 | 
						|
            });
 | 
						|
 | 
						|
            it('Alice executeTransaction => nested batchExecuteTransactions; mixed protocol fees', async () => {
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .executeTransaction(nestedTransaction, nestedTransaction.signature)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order],
 | 
						|
                    [alice.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => nested batchExecuteTransactions, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => {
 | 
						|
                const transactions = [nestedTransaction, transactionB2, transactionC2];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order, order, order],
 | 
						|
                    [alice.address, bob.address, charlie.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, nested batchExecuteTransactions, Charlie fillOrder; mixed protocol fees', async () => {
 | 
						|
                const transactions = [transactionB2, nestedTransaction, transactionC2];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order, order, order],
 | 
						|
                    [bob.address, alice.address, bob.address, charlie.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, nested batchExecuteTransactions; mixed protocol fees', async () => {
 | 
						|
                const transactions = [transactionB2, transactionC2, nestedTransaction];
 | 
						|
                const signatures = transactions.map(tx => tx.signature);
 | 
						|
                const txReceipt = await deployment.exchange
 | 
						|
                    .batchExecuteTransactions(transactions, signatures)
 | 
						|
                    .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND });
 | 
						|
                expectedBalances.simulateFills(
 | 
						|
                    [order, order, order, order, order],
 | 
						|
                    [bob.address, charlie.address, alice.address, bob.address, charlie.address],
 | 
						|
                    txReceipt,
 | 
						|
                    deployment,
 | 
						|
                    MIXED_FEES_WITH_REFUND,
 | 
						|
                );
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |