aboutsummaryrefslogblamecommitdiffstats
path: root/packages/contracts/test/forwarder/forwarder.ts
blob: b4555d4174da451a86957e494952f0c0488429fd (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                                           
                                                                           




                                                                   

                                                                                                



                                                                                
                                                                   























































































































































                                                                                                                  
                                                                                                                  












                                                                                                          
                          





                                                                      
                                                                                                



                                                                                
                          





                                                                      
                                                                                                


































































                                                                                                                   
                                                



                                                                                             
                                                   












                                                                                                   
                                                



                                                                                             
                                            










                                                                                                             
                                                



                                                                                      
                                                   







































                                                                                                                     
                                               


                                                            

                                                                                           
                  
                                                   









































































































                                                                                                                       
                                                



                                                                                                                
                                                   














                                                                                              
                                                



                                                                                                                
                                                          









                                                                                              
                                                



                                                                                                                
                                                         





















                                                                                                       
                                                









                                                                              
                                             























































































































































































































































                                                                                                                    
                                                



                                                                                                         
                                                                      











                                                                                                             
                                                



                                                                                                         
                                                





                                              
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetProxyUtils } from '@0xproject/order-utils';
import { AssetProxyId, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';

import { DummyERC20TokenContract } from '../../generated_contract_wrappers/dummy_erc20_token';
import { DummyERC721TokenContract } from '../../generated_contract_wrappers/dummy_erc721_token';
import { ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { ForwarderContract } from '../../generated_contract_wrappers/forwarder';
import { WETH9Contract } from '../../generated_contract_wrappers/weth9';
import { artifacts } from '../utils/artifacts';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';
import { ExchangeWrapper } from '../utils/exchange_wrapper';
import { formatters } from '../utils/formatters';
import { ForwarderWrapper } from '../utils/forwarder_wrapper';
import { OrderFactory } from '../utils/order_factory';
import { ContractName, ERC20BalancesByOwner } from '../utils/types';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';

chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const DECIMALS_DEFAULT = 18;
// Set a gasPrice so when checking balance of msg.sender we can accurately calculate gasPrice*gasUsed
const DEFAULT_GAS_PRICE = new BigNumber(1);

describe(ContractName.Forwarder, () => {
    let makerAddress: string;
    let owner: string;
    let takerAddress: string;
    let feeRecipientAddress: string;
    let otherAddress: string;
    let defaultMakerAssetAddress: string;

    let weth: DummyERC20TokenContract;
    let zrxToken: DummyERC20TokenContract;
    let erc721Token: DummyERC721TokenContract;
    let forwarderContract: ForwarderContract;
    let wethContract: WETH9Contract;
    let forwarderWrapper: ForwarderWrapper;
    let exchangeWrapper: ExchangeWrapper;

    let signedOrder: SignedOrder;
    let signedOrders: SignedOrder[];
    let orderWithFee: SignedOrder;
    let signedOrdersWithFee: SignedOrder[];
    let feeOrder: SignedOrder;
    let feeOrders: SignedOrder[];
    let orderFactory: OrderFactory;
    let erc20Wrapper: ERC20Wrapper;
    let erc20Balances: ERC20BalancesByOwner;
    let tx: TransactionReceiptWithDecodedLogs;

    let erc721MakerAssetIds: BigNumber[];
    let feeProportion: number = 0;

    before(async () => {
        await blockchainLifecycle.startAsync();
        const accounts = await web3Wrapper.getAvailableAddressesAsync();
        const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts);

        const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
        erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);

        const numDummyErc20ToDeploy = 3;
        let erc20TokenA;
        [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
            numDummyErc20ToDeploy,
            constants.DUMMY_TOKEN_DECIMALS,
        );
        const erc20Proxy = await erc20Wrapper.deployProxyAsync();
        await erc20Wrapper.setBalancesAndAllowancesAsync();

        [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
        const erc721Proxy = await erc721Wrapper.deployProxyAsync();
        await erc721Wrapper.setBalancesAndAllowancesAsync();
        const erc721Balances = await erc721Wrapper.getBalancesAsync();
        erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];

        wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.EtherToken, provider, txDefaults);
        weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider);
        erc20Wrapper.addDummyTokenContract(weth);

        const wethAssetData = assetProxyUtils.encodeERC20AssetData(wethContract.address);
        const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
        const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
            artifacts.Exchange,
            provider,
            txDefaults,
            zrxAssetData,
        );
        const exchangeContract = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
        exchangeWrapper = new ExchangeWrapper(exchangeContract, provider);
        await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
        await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);

        await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
            from: owner,
        });
        await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
            from: owner,
        });

        defaultMakerAssetAddress = erc20TokenA.address;
        const defaultTakerAssetAddress = wethContract.address;
        const defaultOrderParams = {
            exchangeAddress: exchangeInstance.address,
            makerAddress,
            feeRecipientAddress,
            makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress),
            takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress),
            makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
            takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
            makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
        };
        const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
        orderFactory = new OrderFactory(privateKey, defaultOrderParams);

        const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync(
            artifacts.Forwarder,
            provider,
            txDefaults,
            exchangeInstance.address,
            wethContract.address,
            zrxToken.address,
            AssetProxyId.ERC20,
            zrxAssetData,
            wethAssetData,
        );
        forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider);
        forwarderWrapper = new ForwarderWrapper(forwarderContract, provider, zrxToken.address);
        erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address);

        web3Wrapper.abiDecoder.addABI(forwarderContract.abi);
        web3Wrapper.abiDecoder.addABI(exchangeInstance.abi);
    });
    after(async () => {
        await blockchainLifecycle.revertAsync();
    });
    beforeEach(async () => {
        await blockchainLifecycle.startAsync();
        feeProportion = 0;
        erc20Balances = await erc20Wrapper.getBalancesAsync();
        signedOrder = orderFactory.newSignedOrder();
        signedOrders = [signedOrder];
        feeOrder = orderFactory.newSignedOrder({
            makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
        });
        feeOrders = [feeOrder];
        orderWithFee = orderFactory.newSignedOrder({
            takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
        });
        signedOrdersWithFee = [orderWithFee];
    });
    afterEach(async () => {
        await blockchainLifecycle.revertAsync();
    });
    describe('calculations', () => {
        it('throws if partially filled orders passed in are not enough to satisfy requested amount', async () => {
            feeOrders = [feeOrder];
            const makerTokenFillAmount = feeOrder.makerAssetAmount.div(2);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                feeOrders,
                [],
                feeProportion,
                makerTokenFillAmount,
            );
            // Fill the feeOrder
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(feeOrders, [], makerTokenFillAmount, {
                from: takerAddress,
                value: fillAmountWei,
            });
            return expect(
                forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                    feeOrders,
                    [],
                    feeProportion,
                    makerTokenFillAmount,
                ),
            ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
        });
        it('throws if orders passed are cancelled', async () => {
            tx = await exchangeWrapper.cancelOrderAsync(feeOrder, makerAddress);
            // Cancel the feeOrder
            return expect(
                forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                    feeOrders,
                    [],
                    feeProportion,
                    feeOrder.makerAssetAmount.div(2),
                ),
            ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
        });
    });
    describe('marketSellEthForERC20 without extra fees', () => {
        it('should fill the order', async () => {
            const fillAmount = signedOrder.takerAssetAmount.div(2);
            const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
            const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
            feeOrders = [];
            tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
                value: fillAmount,
                from: takerAddress,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
            const makerTokenFillAmount = fillAmount
                .times(signedOrder.makerAssetAmount)
                .dividedToIntegerBy(signedOrder.takerAssetAmount);

            expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(makerTokenFillAmount));
            expect(takerBalanceAfter).to.be.bignumber.equal(takerBalanceBefore.plus(makerTokenFillAmount));
            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
        });
        it('should fill the order and perform fee abstraction', async () => {
            const fillAmount = signedOrder.takerAssetAmount.div(4);
            const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
            tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
                value: fillAmount,
                from: takerAddress,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];

            const acceptPercentage = 98;
            const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
            const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
            expect(isWithinThreshold).to.be.true();
            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
        });
        it('should fill the order when token is ZRX with fees', async () => {
            orderWithFee = orderFactory.newSignedOrder({
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
            });
            signedOrdersWithFee = [orderWithFee];
            feeOrders = [];
            const fillAmount = signedOrder.takerAssetAmount.div(4);
            const takerBalanceBefore = erc20Balances[takerAddress][zrxToken.address];
            tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
                value: fillAmount,
                from: takerAddress,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerBalanceAfter = newBalances[takerAddress][zrxToken.address];

            const acceptPercentage = 98;
            const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
            const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
            expect(isWithinThreshold).to.be.true();
            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
        });
        it('should fail if sent an ETH amount too high', async () => {
            signedOrder = orderFactory.newSignedOrder({
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
            });
            const fillAmount = signedOrder.takerAssetAmount.times(2);
            return expectTransactionFailedAsync(
                forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
                    value: fillAmount,
                    from: takerAddress,
                }),
                RevertReason.UnacceptableThreshold,
            );
        });
        it('should fail if fee abstraction amount is too high', async () => {
            orderWithFee = orderFactory.newSignedOrder({
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
            });
            signedOrdersWithFee = [orderWithFee];
            feeOrder = orderFactory.newSignedOrder({
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
            });
            feeOrders = [feeOrder];
            const fillAmount = signedOrder.takerAssetAmount.div(4);
            return expectTransactionFailedAsync(
                forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
                    value: fillAmount,
                    from: takerAddress,
                }),
                RevertReason.TransferFailed,
            );
        });
        it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            const erc721SignedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            const erc20SignedOrder = orderFactory.newSignedOrder();
            signedOrders = [erc20SignedOrder, erc721SignedOrder];
            const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
            return expectTransactionFailedAsync(
                forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.InvalidOrderSignature,
            );
        });
    });
    describe('marketSellEthForERC20 with extra fees', () => {
        it('should fill the order and send fee to fee recipient', async () => {
            const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            const fillAmount = signedOrder.takerAssetAmount.div(2);
            feeProportion = 150; // 1.5%
            feeOrders = [];
            tx = await forwarderWrapper.marketSellEthForERC20Async(
                signedOrders,
                feeOrders,
                {
                    from: takerAddress,
                    value: fillAmount,
                    gasPrice: DEFAULT_GAS_PRICE,
                },
                {
                    feeProportion,
                    feeRecipient: feeRecipientAddress,
                },
            );
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
            const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
            const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            const takerBoughtAmount = takerBalanceAfter.minus(erc20Balances[takerAddress][defaultMakerAssetAddress]);

            expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(takerBoughtAmount));
            expect(afterEthBalance).to.be.bignumber.equal(
                initEthBalance.plus(fillAmount.times(feeProportion).dividedBy(10000)),
            );
            expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
        });
        it('should fail if the fee is set too high', async () => {
            const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            const fillAmount = signedOrder.takerAssetAmount.div(2);
            feeProportion = 1500; // 15.0%
            feeOrders = [];
            await expectTransactionFailedAsync(
                forwarderWrapper.marketSellEthForERC20Async(
                    signedOrders,
                    feeOrders,
                    { from: takerAddress, value: fillAmount, gasPrice: DEFAULT_GAS_PRICE },
                    { feeProportion, feeRecipient: feeRecipientAddress },
                ),
                RevertReason.FeeProportionTooLarge,
            );
            const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            expect(afterEthBalance).to.be.bignumber.equal(initEthBalance);
        });
    });
    describe('marketBuyTokensWithEth', () => {
        it('should buy the exact amount of assets', async () => {
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const balancesBefore = await erc20Wrapper.getBalancesAsync();
            const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
            const fillAmountWei = makerAssetAmount.dividedToIntegerBy(rate);
            feeOrders = [];
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: fillAmountWei,
                gasPrice: DEFAULT_GAS_PRICE,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
            const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmountWei).minus(tx.gasUsed);
            expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
            expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
        });
        it('should buy the exact amount of assets and return excess ETH', async () => {
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const balancesBefore = await erc20Wrapper.getBalancesAsync();
            const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
            const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
            const excessFillAmount = fillAmount.times(2);
            feeOrders = [];
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: excessFillAmount,
                gasPrice: DEFAULT_GAS_PRICE,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
            const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmount).minus(tx.gasUsed);
            expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
            expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
        });
        it('should buy the exact amount of assets with fee abstraction', async () => {
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const balancesBefore = await erc20Wrapper.getBalancesAsync();
            const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
            const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
            const excessFillAmount = fillAmount.times(2);
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: excessFillAmount,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
            const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
            expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
        });
        it('should buy the exact amount of assets when buying zrx with fee abstraction', async () => {
            signedOrder = orderFactory.newSignedOrder({
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
            });
            signedOrdersWithFee = [signedOrder];
            feeOrders = [];
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const takerWeiBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const balancesBefore = await erc20Wrapper.getBalancesAsync();
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrdersWithFee,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: fillAmountWei,
                gasPrice: DEFAULT_GAS_PRICE,
            });
            const newBalances = await erc20Wrapper.getBalancesAsync();
            const takerTokenBalanceBefore = balancesBefore[takerAddress][zrxToken.address];
            const takerTokenBalanceAfter = newBalances[takerAddress][zrxToken.address];
            const takerWeiBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const expectedCostAfterGas = fillAmountWei.plus(tx.gasUsed);
            expect(takerTokenBalanceAfter).to.be.bignumber.greaterThan(takerTokenBalanceBefore.plus(makerAssetAmount));
            expect(takerWeiBalanceAfter).to.be.bignumber.equal(takerWeiBalanceBefore.minus(expectedCostAfterGas));
        });
        it('throws if fees are higher than 5% when buying zrx', async () => {
            const highFeeZRXOrder = orderFactory.newSignedOrder({
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                makerAssetAmount: signedOrder.makerAssetAmount,
                takerFee: signedOrder.makerAssetAmount.times(0.06),
            });
            signedOrdersWithFee = [highFeeZRXOrder];
            feeOrders = [];
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrdersWithFee,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            return expectTransactionFailedAsync(
                forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.UnacceptableThreshold,
            );
        });
        it('throws if fees are higher than 5% when buying erc20', async () => {
            const highFeeERC20Order = orderFactory.newSignedOrder({
                takerFee: signedOrder.makerAssetAmount.times(0.06),
            });
            signedOrdersWithFee = [highFeeERC20Order];
            feeOrders = [feeOrder];
            const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrdersWithFee,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            return expectTransactionFailedAsync(
                forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.UnacceptableThreshold as any,
            );
        });
        it('throws if makerAssetAmount is 0', async () => {
            const makerAssetAmount = new BigNumber(0);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrdersWithFee,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            return expectTransactionFailedAsync(
                forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.ValueGreaterThanZero as any,
            );
        });
        it('throws if the amount of ETH sent in is less than the takerAssetFilledAmount', async () => {
            const makerAssetAmount = signedOrder.makerAssetAmount;
            const fillAmount = signedOrder.takerAssetAmount.div(2);
            const zero = new BigNumber(0);
            // Deposit enough taker balance to fill the order
            const wethDepositTxHash = await wethContract.deposit.sendTransactionAsync({
                from: takerAddress,
                value: signedOrder.takerAssetAmount,
            });
            await web3Wrapper.awaitTransactionSuccessAsync(wethDepositTxHash);
            // Transfer all of this WETH to the forwarding contract
            const wethTransferTxHash = await wethContract.transfer.sendTransactionAsync(
                forwarderContract.address,
                signedOrder.takerAssetAmount,
                { from: takerAddress },
            );
            await web3Wrapper.awaitTransactionSuccessAsync(wethTransferTxHash);
            // We use the contract directly to get around wrapper validations and calculations
            const formattedOrders = formatters.createMarketSellOrders(signedOrders, zero);
            const formattedFeeOrders = formatters.createMarketSellOrders(feeOrders, zero);
            return expectTransactionFailedAsync(
                forwarderContract.marketBuyTokensWithEth.sendTransactionAsync(
                    formattedOrders.orders,
                    formattedOrders.signatures,
                    formattedFeeOrders.orders,
                    formattedFeeOrders.signatures,
                    makerAssetAmount,
                    zero,
                    constants.NULL_ADDRESS,
                    { value: fillAmount, from: takerAddress },
                ),
                RevertReason.InvalidMsgValue,
            );
        });
    });
    describe('marketBuyTokensWithEth - ERC721', async () => {
        it('buys ERC721 assets', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            signedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            feeOrders = [];
            signedOrders = [signedOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: fillAmountWei,
            });
            const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
            expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
        });
        it('buys ERC721 assets with fee abstraction', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            signedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            signedOrders = [signedOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: fillAmountWei,
            });
            const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
            expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
        });
        it('buys ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            signedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            signedOrders = [signedOrder];
            feeProportion = 100;
            const initTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const initFeeRecipientBalanceWei = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(
                signedOrders,
                feeOrders,
                makerAssetAmount,
                {
                    from: takerAddress,
                    value: fillAmountWei,
                    gasPrice: DEFAULT_GAS_PRICE,
                },
                {
                    feeProportion,
                    feeRecipient: feeRecipientAddress,
                },
            );
            const afterFeeRecipientEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
            const afterTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const takerFilledAmount = initTakerBalanceWei.minus(afterTakerBalanceWei).plus(tx.gasUsed);
            const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
            expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
            const balanceDiff = afterFeeRecipientEthBalance.minus(initFeeRecipientBalanceWei);
            expect(takerFilledAmount.dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(101);
            expect(takerFilledAmount.minus(balanceDiff).dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(100);
        });
        it('buys multiple ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
            const makerAssetId1 = erc721MakerAssetIds[0];
            const makerAssetId2 = erc721MakerAssetIds[1];
            const signedOrder1 = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId1),
            });
            const signedOrder2 = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId2),
            });
            signedOrders = [signedOrder1, signedOrder2];
            feeProportion = 10;
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                makerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: fillAmountWei,
            });
            const newOwnerTakerAsset1 = await erc721Token.ownerOf.callAsync(makerAssetId1);
            expect(newOwnerTakerAsset1).to.be.bignumber.equal(takerAddress);
            const newOwnerTakerAsset2 = await erc721Token.ownerOf.callAsync(makerAssetId2);
            expect(newOwnerTakerAsset2).to.be.bignumber.equal(takerAddress);
        });
        it('buys ERC721 assets with fee abstraction and handles fee orders filled and excess eth', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            feeProportion = 0;
            // In this scenario a total of 6 ZRX fees need to be paid.
            // There are two fee orders, but the first fee order is partially filled while
            // the Forwarding contract tx is in the mempool.
            const erc721MakerAssetAmount = new BigNumber(1);
            signedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: erc721MakerAssetAmount,
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            signedOrders = [signedOrder];
            const firstFeeOrder = orderFactory.newSignedOrder({
                makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
            });
            const secondFeeOrder = orderFactory.newSignedOrder({
                makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
            });
            feeOrders = [firstFeeOrder, secondFeeOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                erc721MakerAssetAmount,
            );
            // Simulate another otherAddress user partially filling firstFeeOrder
            const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
                from: otherAddress,
                value: fillAmountWei,
            });
            // For tests we calculate how much this should've cost given that firstFeeOrder was filled
            const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                erc721MakerAssetAmount,
            );
            // With 4 ZRX remaining in firstFeeOrder, the secondFeeOrder will need to be filled to make up
            // the total amount of fees required (6)
            // Since the fee orders can be filled while the transaction is pending the user safely sends in
            // extra ether to cover any slippage
            const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const slippageFillAmountWei = fillAmountWei.times(2);
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: slippageFillAmountWei,
                gasPrice: DEFAULT_GAS_PRICE,
            });
            const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
            const expectedEthBalanceAfterGasCosts = initEthBalance.minus(expectedFillAmountWei).minus(tx.gasUsed);
            const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
            expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
            expect(afterEthBalance).to.be.bignumber.equal(expectedEthBalanceAfterGasCosts);
        });
        it('buys ERC721 assets with fee abstraction and handles fee orders filled', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            feeProportion = 0;
            // In this scenario a total of 6 ZRX fees need to be paid.
            // There are two fee orders, but the first fee order is partially filled while
            // the Forwarding contract tx is in the mempool.
            const erc721MakerAssetAmount = new BigNumber(1);
            signedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: erc721MakerAssetAmount,
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            const zrxMakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT);
            signedOrders = [signedOrder];
            const firstFeeOrder = orderFactory.newSignedOrder({
                makerAssetAmount: zrxMakerAssetAmount,
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
            });
            const secondFeeOrder = orderFactory.newSignedOrder({
                makerAssetAmount: zrxMakerAssetAmount,
                takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
                makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
                takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
            });
            feeOrders = [firstFeeOrder, secondFeeOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                erc721MakerAssetAmount,
            );
            // Simulate another otherAddress user partially filling firstFeeOrder
            const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
                from: otherAddress,
                value: fillAmountWei,
            });
            const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
                signedOrders,
                feeOrders,
                feeProportion,
                erc721MakerAssetAmount,
            );
            tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                from: takerAddress,
                value: expectedFillAmountWei,
            });
            const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
            expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
        });
        it('throws when mixed ERC721 and ERC20 assets', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            const erc721SignedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            const erc20SignedOrder = orderFactory.newSignedOrder();
            signedOrders = [erc721SignedOrder, erc20SignedOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
            return expectTransactionFailedAsync(
                forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
            );
        });
        it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
            const makerAssetId = erc721MakerAssetIds[0];
            const erc721SignedOrder = orderFactory.newSignedOrder({
                makerAssetAmount: new BigNumber(1),
                makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
            });
            const erc20SignedOrder = orderFactory.newSignedOrder();
            signedOrders = [erc20SignedOrder, erc721SignedOrder];
            const makerAssetAmount = new BigNumber(signedOrders.length);
            const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
            return expectTransactionFailedAsync(
                forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
                    from: takerAddress,
                    value: fillAmountWei,
                }),
                RevertReason.InvalidTakerAmount,
            );
        });
    });
});
// tslint:disable:max-file-line-count
// tslint:enable:no-unnecessary-type-assertion