646 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			646 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { DevUtilsContract } from '@0x/contracts-dev-utils';
 | 
						|
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
 | 
						|
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
 | 
						|
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
 | 
						|
import { artifacts, ForwarderContract, ForwarderRevertErrors } from '@0x/contracts-exchange-forwarder';
 | 
						|
import {
 | 
						|
    blockchainTests,
 | 
						|
    constants,
 | 
						|
    expect,
 | 
						|
    getLatestBlockTimestampAsync,
 | 
						|
    getPercentageOfValue,
 | 
						|
    provider,
 | 
						|
    toBaseUnitAmount,
 | 
						|
} from '@0x/contracts-test-utils';
 | 
						|
import { BigNumber } from '@0x/utils';
 | 
						|
 | 
						|
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';
 | 
						|
 | 
						|
import { deployForwarderAsync } from './deploy_forwarder';
 | 
						|
import { ForwarderTestFactory } from './forwarder_test_factory';
 | 
						|
 | 
						|
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
 | 
						|
 | 
						|
blockchainTests('Forwarder integration tests', env => {
 | 
						|
    let deployment: DeploymentManager;
 | 
						|
    let forwarder: ForwarderContract;
 | 
						|
    let balanceStore: BlockchainBalanceStore;
 | 
						|
    let testFactory: ForwarderTestFactory;
 | 
						|
 | 
						|
    let makerToken: DummyERC20TokenContract;
 | 
						|
    let makerFeeToken: DummyERC20TokenContract;
 | 
						|
    let anotherErc20Token: DummyERC20TokenContract;
 | 
						|
    let erc721Token: DummyERC721TokenContract;
 | 
						|
    let nftId: BigNumber;
 | 
						|
    let wethAssetData: string;
 | 
						|
    let makerAssetData: string;
 | 
						|
 | 
						|
    let maker: Maker;
 | 
						|
    let taker: Taker;
 | 
						|
    let orderFeeRecipient: FeeRecipient;
 | 
						|
    let forwarderFeeRecipient: FeeRecipient;
 | 
						|
 | 
						|
    before(async () => {
 | 
						|
        deployment = await DeploymentManager.deployAsync(env, {
 | 
						|
            numErc20TokensToDeploy: 3,
 | 
						|
            numErc721TokensToDeploy: 1,
 | 
						|
            numErc1155TokensToDeploy: 0,
 | 
						|
        });
 | 
						|
        forwarder = await deployForwarderAsync(deployment, env);
 | 
						|
 | 
						|
        [makerToken, makerFeeToken, anotherErc20Token] = deployment.tokens.erc20;
 | 
						|
        [erc721Token] = deployment.tokens.erc721;
 | 
						|
        wethAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
            deployment.tokens.weth.address,
 | 
						|
        );
 | 
						|
        makerAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(makerToken.address);
 | 
						|
 | 
						|
        taker = new Taker({ name: 'Taker', deployment });
 | 
						|
        orderFeeRecipient = new FeeRecipient({
 | 
						|
            name: 'Order fee recipient',
 | 
						|
            deployment,
 | 
						|
        });
 | 
						|
        forwarderFeeRecipient = new FeeRecipient({
 | 
						|
            name: 'Forwarder fee recipient',
 | 
						|
            deployment,
 | 
						|
        });
 | 
						|
        maker = new Maker({
 | 
						|
            name: 'Maker',
 | 
						|
            deployment,
 | 
						|
            orderConfig: {
 | 
						|
                feeRecipientAddress: orderFeeRecipient.address,
 | 
						|
                makerAssetAmount: toBaseUnitAmount(2),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(1),
 | 
						|
                makerAssetData,
 | 
						|
                takerAssetData: wethAssetData,
 | 
						|
                takerFee: constants.ZERO_AMOUNT,
 | 
						|
                makerFeeAssetData: deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                    makerFeeToken.address,
 | 
						|
                ),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            },
 | 
						|
        });
 | 
						|
 | 
						|
        await maker.configureERC20TokenAsync(makerToken);
 | 
						|
        await maker.configureERC20TokenAsync(makerFeeToken);
 | 
						|
        await maker.configureERC20TokenAsync(anotherErc20Token);
 | 
						|
        await forwarder.approveMakerAssetProxy(makerAssetData).awaitTransactionSuccessAsync();
 | 
						|
        [nftId] = await maker.configureERC721TokenAsync(erc721Token);
 | 
						|
 | 
						|
        const tokenOwners = {
 | 
						|
            ...actorAddressesByName([maker, taker, orderFeeRecipient, forwarderFeeRecipient]),
 | 
						|
            Forwarder: forwarder.address,
 | 
						|
            StakingProxy: deployment.staking.stakingProxy.address,
 | 
						|
        };
 | 
						|
        const tokenContracts = {
 | 
						|
            erc20: { makerToken, makerFeeToken, anotherErc20Token, wETH: deployment.tokens.weth },
 | 
						|
            erc721: { erc721Token },
 | 
						|
        };
 | 
						|
        const tokenIds = { erc721: { [erc721Token.address]: [nftId] } };
 | 
						|
        balanceStore = new BlockchainBalanceStore(tokenOwners, tokenContracts, tokenIds);
 | 
						|
 | 
						|
        testFactory = new ForwarderTestFactory(
 | 
						|
            forwarder,
 | 
						|
            deployment,
 | 
						|
            balanceStore,
 | 
						|
            maker,
 | 
						|
            taker,
 | 
						|
            orderFeeRecipient,
 | 
						|
            forwarderFeeRecipient,
 | 
						|
            devUtils,
 | 
						|
        );
 | 
						|
    });
 | 
						|
 | 
						|
    blockchainTests.resets('constructor', () => {
 | 
						|
        it('should revert if assetProxy is unregistered', async () => {
 | 
						|
            const chainId = await env.getChainIdAsync();
 | 
						|
            const exchange = await ExchangeContract.deployFrom0xArtifactAsync(
 | 
						|
                exchangeArtifacts.Exchange,
 | 
						|
                env.provider,
 | 
						|
                env.txDefaults,
 | 
						|
                {},
 | 
						|
                new BigNumber(chainId),
 | 
						|
            );
 | 
						|
            const deployForwarder = ForwarderContract.deployFrom0xArtifactAsync(
 | 
						|
                artifacts.Forwarder,
 | 
						|
                env.provider,
 | 
						|
                env.txDefaults,
 | 
						|
                {},
 | 
						|
                exchange.address,
 | 
						|
                wethAssetData,
 | 
						|
            );
 | 
						|
            await expect(deployForwarder).to.revertWith(new ForwarderRevertErrors.UnregisteredAssetProxyError());
 | 
						|
        });
 | 
						|
    });
 | 
						|
    blockchainTests.resets('marketSellOrdersWithEth without extra fees', () => {
 | 
						|
        it('should fill a single order without a taker fee', async () => {
 | 
						|
            const orderWithoutFee = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([orderWithoutFee], 0.78);
 | 
						|
        });
 | 
						|
        it('should fill multiple orders without taker fees', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync();
 | 
						|
            const secondOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(285),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(21),
 | 
						|
            });
 | 
						|
            const orders = [firstOrder, secondOrder];
 | 
						|
            await testFactory.marketSellTestAsync(orders, 1.51);
 | 
						|
        });
 | 
						|
        it('should fill a single order with a percentage fee', async () => {
 | 
						|
            const orderWithPercentageFee = await maker.signOrderAsync({
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: makerAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketSellTestAsync([orderWithPercentageFee], 0.58);
 | 
						|
        });
 | 
						|
        it('should fill multiple orders with percentage fees', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync({
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: makerAssetData,
 | 
						|
            });
 | 
						|
            const secondOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(190),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(31),
 | 
						|
                takerFee: toBaseUnitAmount(2),
 | 
						|
                takerFeeAssetData: makerAssetData,
 | 
						|
            });
 | 
						|
            const orders = [firstOrder, secondOrder];
 | 
						|
            await testFactory.marketSellTestAsync(orders, 1.34);
 | 
						|
        });
 | 
						|
        it('should fail to fill an order with a percentage fee if the asset proxy is not yet approved', async () => {
 | 
						|
            const unapprovedAsset = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                anotherErc20Token.address,
 | 
						|
            );
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetData: unapprovedAsset,
 | 
						|
                takerFee: toBaseUnitAmount(2),
 | 
						|
                takerFeeAssetData: unapprovedAsset,
 | 
						|
            });
 | 
						|
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            // Execute test case
 | 
						|
            const tx = await forwarder
 | 
						|
                .marketSellOrdersWithEth(
 | 
						|
                    [order],
 | 
						|
                    [order.signature],
 | 
						|
                    constants.ZERO_AMOUNT,
 | 
						|
                    forwarderFeeRecipient.address,
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    value: order.takerAssetAmount.plus(DeploymentManager.protocolFee),
 | 
						|
                    from: taker.address,
 | 
						|
                });
 | 
						|
 | 
						|
            const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
 | 
						|
            expectedBalances.burnGas(tx.from, DeploymentManager.gasPrice.times(tx.gasUsed));
 | 
						|
 | 
						|
            // Verify balances
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            balanceStore.assertEquals(expectedBalances);
 | 
						|
        });
 | 
						|
        it('should fill a single order with a WETH fee', async () => {
 | 
						|
            const orderWithWethFee = await maker.signOrderAsync({
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketSellTestAsync([orderWithWethFee], 0.13);
 | 
						|
        });
 | 
						|
        it('should fill multiple orders with WETH fees', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync({
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            const secondOrderWithWethFee = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(97),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(33),
 | 
						|
                takerFee: toBaseUnitAmount(2),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            const orders = [firstOrder, secondOrderWithWethFee];
 | 
						|
            await testFactory.marketSellTestAsync(orders, 1.25);
 | 
						|
        });
 | 
						|
        it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            const ethValue = order.takerAssetAmount.plus(DeploymentManager.protocolFee).plus(2);
 | 
						|
            const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker.address);
 | 
						|
            const tx = await forwarder
 | 
						|
                .marketSellOrdersWithEth(
 | 
						|
                    [order],
 | 
						|
                    [order.signature],
 | 
						|
                    constants.ZERO_AMOUNT,
 | 
						|
                    forwarderFeeRecipient.address,
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    value: ethValue,
 | 
						|
                    from: taker.address,
 | 
						|
                });
 | 
						|
            const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker.address);
 | 
						|
            const totalEthSpent = order.takerAssetAmount
 | 
						|
                .plus(DeploymentManager.protocolFee)
 | 
						|
                .plus(DeploymentManager.gasPrice.times(tx.gasUsed));
 | 
						|
            expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
 | 
						|
        });
 | 
						|
        it('should fill orders with different makerAssetData', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync();
 | 
						|
            const secondOrderMakerAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                anotherErc20Token.address,
 | 
						|
            );
 | 
						|
            const secondOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetData: secondOrderMakerAssetData,
 | 
						|
            });
 | 
						|
            await forwarder.approveMakerAssetProxy(secondOrderMakerAssetData).awaitTransactionSuccessAsync();
 | 
						|
            const orders = [firstOrder, secondOrder];
 | 
						|
            await testFactory.marketSellTestAsync(orders, 1.5);
 | 
						|
        });
 | 
						|
        it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
 | 
						|
            const takerFeeAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                anotherErc20Token.address,
 | 
						|
            );
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                takerFeeAssetData,
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
            });
 | 
						|
            const revertError = new ForwarderRevertErrors.UnsupportedFeeError(takerFeeAssetData);
 | 
						|
            await testFactory.marketSellTestAsync([order], 0.5, {
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should fill a partially-filled order without a taker fee', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([order], 0.3);
 | 
						|
            await testFactory.marketSellTestAsync([order], 0.8);
 | 
						|
        });
 | 
						|
        it('should skip over an order with an invalid maker asset amount', async () => {
 | 
						|
            const unfillableOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: constants.ZERO_AMOUNT,
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over an order with an invalid taker asset amount', async () => {
 | 
						|
            const unfillableOrder = await maker.signOrderAsync({
 | 
						|
                takerAssetAmount: constants.ZERO_AMOUNT,
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over an expired order', async () => {
 | 
						|
            const currentTimestamp = await getLatestBlockTimestampAsync();
 | 
						|
            const expiredOrder = await maker.signOrderAsync({
 | 
						|
                expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([expiredOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over a fully filled order', async () => {
 | 
						|
            const fullyFilledOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([fullyFilledOrder], 1);
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([fullyFilledOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over a cancelled order', async () => {
 | 
						|
            const cancelledOrder = await maker.signOrderAsync();
 | 
						|
            await maker.cancelOrderAsync(cancelledOrder);
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketSellTestAsync([cancelledOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
    });
 | 
						|
    blockchainTests.resets('marketSellOrdersWithEth with extra fees', () => {
 | 
						|
        it('should fill the order and send fee to feeRecipient', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(157),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(36),
 | 
						|
            });
 | 
						|
            await testFactory.marketSellTestAsync([order], 0.67, {
 | 
						|
                forwarderFeePercentage: new BigNumber(2),
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should fail if the fee is set too high', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            const forwarderFeePercentage = new BigNumber(6);
 | 
						|
            const revertError = new ForwarderRevertErrors.FeePercentageTooLargeError(
 | 
						|
                getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, forwarderFeePercentage),
 | 
						|
            );
 | 
						|
            await testFactory.marketSellTestAsync([order], 0.5, {
 | 
						|
                forwarderFeePercentage,
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    blockchainTests.resets('marketBuyOrdersWithEth without extra fees', () => {
 | 
						|
        it('should buy the exact amount of makerAsset in a single order', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(131),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(20),
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.62);
 | 
						|
        });
 | 
						|
        it('should buy the exact amount of makerAsset in multiple orders', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync();
 | 
						|
            const secondOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(77),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(11),
 | 
						|
            });
 | 
						|
            const orders = [firstOrder, secondOrder];
 | 
						|
            await testFactory.marketBuyTestAsync(orders, 1.96);
 | 
						|
        });
 | 
						|
        it('should buy exactly makerAssetBuyAmount in orders with different makerAssetData', async () => {
 | 
						|
            const firstOrder = await maker.signOrderAsync();
 | 
						|
            const secondOrderMakerAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                anotherErc20Token.address,
 | 
						|
            );
 | 
						|
            const secondOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetData: secondOrderMakerAssetData,
 | 
						|
            });
 | 
						|
            await forwarder.approveMakerAssetProxy(secondOrderMakerAssetData).awaitTransactionSuccessAsync();
 | 
						|
            const orders = [firstOrder, secondOrder];
 | 
						|
            await testFactory.marketBuyTestAsync(orders, 1.5);
 | 
						|
        });
 | 
						|
        it('should buy the exact amount of makerAsset and return excess ETH', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(80),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(17),
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.57, {
 | 
						|
                ethValueAdjustment: 2,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should buy the exact amount of makerAsset from a single order with a WETH fee', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(79),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(16),
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.38);
 | 
						|
        });
 | 
						|
        it('should buy the exact amount of makerAsset from a single order with a percentage fee', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(80),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(17),
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: makerAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.52);
 | 
						|
        });
 | 
						|
        it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            const revertError = new ForwarderRevertErrors.CompleteBuyFailedError(
 | 
						|
                order.makerAssetAmount.times(0.5),
 | 
						|
                constants.ZERO_AMOUNT,
 | 
						|
            );
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.5, {
 | 
						|
                ethValueAdjustment: -2,
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should buy an ERC721 asset from a single order', async () => {
 | 
						|
            const erc721Order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: new BigNumber(1),
 | 
						|
                makerAssetData: deployment.assetDataEncoder.ERC721Token.getABIEncodedTransactionData(
 | 
						|
                    erc721Token.address,
 | 
						|
                    nftId,
 | 
						|
                ),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([erc721Order], 1);
 | 
						|
        });
 | 
						|
        it('should buy an ERC721 asset and pay a WETH fee', async () => {
 | 
						|
            const erc721orderWithWethFee = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: new BigNumber(1),
 | 
						|
                makerAssetData: deployment.assetDataEncoder.ERC721Token.getABIEncodedTransactionData(
 | 
						|
                    erc721Token.address,
 | 
						|
                    nftId,
 | 
						|
                ),
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
                takerFeeAssetData: wethAssetData,
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([erc721orderWithWethFee], 1);
 | 
						|
        });
 | 
						|
        it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
 | 
						|
            const takerFeeAssetData = deployment.assetDataEncoder.ERC20Token.getABIEncodedTransactionData(
 | 
						|
                anotherErc20Token.address,
 | 
						|
            );
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                takerFeeAssetData,
 | 
						|
                takerFee: toBaseUnitAmount(1),
 | 
						|
            });
 | 
						|
            const revertError = new ForwarderRevertErrors.UnsupportedFeeError(takerFeeAssetData);
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.5, {
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should fill a partially-filled order without a taker fee', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.3);
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.8);
 | 
						|
        });
 | 
						|
        it('should skip over an order with an invalid maker asset amount', async () => {
 | 
						|
            const unfillableOrder = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: constants.ZERO_AMOUNT,
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over an order with an invalid taker asset amount', async () => {
 | 
						|
            const unfillableOrder = await maker.signOrderAsync({
 | 
						|
                takerAssetAmount: constants.ZERO_AMOUNT,
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over an expired order', async () => {
 | 
						|
            const currentTimestamp = await getLatestBlockTimestampAsync();
 | 
						|
            const expiredOrder = await maker.signOrderAsync({
 | 
						|
                expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10),
 | 
						|
            });
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([expiredOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over a fully filled order', async () => {
 | 
						|
            const fullyFilledOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([fullyFilledOrder], 1);
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([fullyFilledOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('should skip over a cancelled order', async () => {
 | 
						|
            const cancelledOrder = await maker.signOrderAsync();
 | 
						|
            await maker.cancelOrderAsync(cancelledOrder);
 | 
						|
            const fillableOrder = await maker.signOrderAsync();
 | 
						|
            await testFactory.marketBuyTestAsync([cancelledOrder, fillableOrder], 1.5);
 | 
						|
        });
 | 
						|
        it('Should buy slightly greater makerAsset when exchange rate is rounded', async () => {
 | 
						|
            // The 0x Protocol contracts round the exchange rate in favor of the Maker.
 | 
						|
            // In this case, the taker must round up how much they're going to spend, which
 | 
						|
            // in turn increases the amount of MakerAsset being purchased.
 | 
						|
            // Example:
 | 
						|
            //  The taker wants to buy 5 units of the MakerAsset at a rate of 3M/2T.
 | 
						|
            //  For every 2 units of TakerAsset, the taker will receive 3 units of MakerAsset.
 | 
						|
            //  To purchase 5 units, the taker must spend 10/3 = 3.33 units of TakerAssset.
 | 
						|
            //  However, the Taker can only spend whole units.
 | 
						|
            //  Spending floor(10/3) = 3 units will yield a profit of Floor(3*3/2) = Floor(4.5) = 4 units of MakerAsset.
 | 
						|
            //  Spending ceil(10/3) = 4 units will yield a profit of Floor(4*3/2) = 6 units of MakerAsset.
 | 
						|
            //
 | 
						|
            //  The forwarding contract will opt for the second option, which overbuys, to ensure the taker
 | 
						|
            //  receives at least the amount of MakerAsset they requested.
 | 
						|
            //
 | 
						|
            // Construct test case using values from example above
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: new BigNumber('30'),
 | 
						|
                takerAssetAmount: new BigNumber('20'),
 | 
						|
                makerFee: new BigNumber(0),
 | 
						|
                takerFee: new BigNumber(0),
 | 
						|
            });
 | 
						|
            const desiredMakerAssetFillAmount = new BigNumber('5');
 | 
						|
            const makerAssetFillAmount = new BigNumber('6');
 | 
						|
            const primaryTakerAssetFillAmount = new BigNumber('4');
 | 
						|
            const ethValue = primaryTakerAssetFillAmount.plus(DeploymentManager.protocolFee);
 | 
						|
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            // Execute test case
 | 
						|
            const tx = await forwarder
 | 
						|
                .marketBuyOrdersWithEth(
 | 
						|
                    [order],
 | 
						|
                    desiredMakerAssetFillAmount,
 | 
						|
                    [order.signature],
 | 
						|
                    constants.ZERO_AMOUNT,
 | 
						|
                    forwarderFeeRecipient.address,
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    value: ethValue,
 | 
						|
                    from: taker.address,
 | 
						|
                });
 | 
						|
 | 
						|
            // Compute expected balances
 | 
						|
            const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                maker.address,
 | 
						|
                taker.address,
 | 
						|
                makerAssetFillAmount,
 | 
						|
                makerAssetData,
 | 
						|
            );
 | 
						|
            expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, ethValue);
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                taker.address,
 | 
						|
                maker.address,
 | 
						|
                primaryTakerAssetFillAmount,
 | 
						|
                wethAssetData,
 | 
						|
            );
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                taker.address,
 | 
						|
                deployment.staking.stakingProxy.address,
 | 
						|
                DeploymentManager.protocolFee,
 | 
						|
                wethAssetData,
 | 
						|
            );
 | 
						|
            expectedBalances.burnGas(tx.from, DeploymentManager.gasPrice.times(tx.gasUsed));
 | 
						|
 | 
						|
            // Verify balances
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            balanceStore.assertEquals(expectedBalances);
 | 
						|
        });
 | 
						|
        it('Should buy slightly greater MakerAsset when exchange rate is rounded (Regression Test)', async () => {
 | 
						|
            // Order taken from a transaction on mainnet that failed due to a rounding error.
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: new BigNumber('268166666666666666666'),
 | 
						|
                takerAssetAmount: new BigNumber('219090625878836371'),
 | 
						|
                makerFee: new BigNumber(0),
 | 
						|
                takerFee: new BigNumber(0),
 | 
						|
            });
 | 
						|
            // The taker will receive more than the desired amount of makerAsset due to rounding
 | 
						|
            const desiredMakerAssetFillAmount = new BigNumber('5000000000000000000');
 | 
						|
            const takerAssetFillAmount = new BigNumber('4084971271824171');
 | 
						|
            const makerAssetFillAmount = takerAssetFillAmount
 | 
						|
                .times(order.makerAssetAmount)
 | 
						|
                .dividedToIntegerBy(order.takerAssetAmount);
 | 
						|
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            // Execute test case
 | 
						|
            const tx = await forwarder
 | 
						|
                .marketBuyOrdersWithEth(
 | 
						|
                    [order],
 | 
						|
                    desiredMakerAssetFillAmount,
 | 
						|
                    [order.signature],
 | 
						|
                    constants.ZERO_AMOUNT,
 | 
						|
                    forwarderFeeRecipient.address,
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    value: takerAssetFillAmount.plus(DeploymentManager.protocolFee),
 | 
						|
                    from: taker.address,
 | 
						|
                });
 | 
						|
 | 
						|
            // Compute expected balances
 | 
						|
            const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore);
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                maker.address,
 | 
						|
                taker.address,
 | 
						|
                makerAssetFillAmount,
 | 
						|
                makerAssetData,
 | 
						|
            );
 | 
						|
            expectedBalances.wrapEth(
 | 
						|
                taker.address,
 | 
						|
                deployment.tokens.weth.address,
 | 
						|
                takerAssetFillAmount.plus(DeploymentManager.protocolFee),
 | 
						|
            );
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                taker.address,
 | 
						|
                maker.address,
 | 
						|
                takerAssetFillAmount,
 | 
						|
                wethAssetData,
 | 
						|
            );
 | 
						|
            await expectedBalances.transferAssetAsync(
 | 
						|
                taker.address,
 | 
						|
                deployment.staking.stakingProxy.address,
 | 
						|
                DeploymentManager.protocolFee,
 | 
						|
                wethAssetData,
 | 
						|
            );
 | 
						|
            expectedBalances.burnGas(tx.from, DeploymentManager.gasPrice.times(tx.gasUsed));
 | 
						|
 | 
						|
            // Verify balances
 | 
						|
            await balanceStore.updateBalancesAsync();
 | 
						|
            balanceStore.assertEquals(expectedBalances);
 | 
						|
        });
 | 
						|
    });
 | 
						|
    blockchainTests.resets('marketBuyOrdersWithEth with extra fees', () => {
 | 
						|
        it('should buy the asset and send fee to feeRecipient', async () => {
 | 
						|
            const order = await maker.signOrderAsync({
 | 
						|
                makerAssetAmount: toBaseUnitAmount(125),
 | 
						|
                takerAssetAmount: toBaseUnitAmount(11),
 | 
						|
            });
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.33, {
 | 
						|
                forwarderFeePercentage: new BigNumber(2),
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should fail if the fee is set too high', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            const revertError = new ForwarderRevertErrors.FeePercentageTooLargeError(
 | 
						|
                getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, new BigNumber(6)),
 | 
						|
            );
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.5, {
 | 
						|
                forwarderFeePercentage: new BigNumber(6),
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('should fail if there is not enough ETH remaining to pay the fee', async () => {
 | 
						|
            const order = await maker.signOrderAsync();
 | 
						|
            const forwarderFeePercentage = new BigNumber(2);
 | 
						|
            const ethFee = getPercentageOfValue(
 | 
						|
                order.takerAssetAmount.times(0.5).plus(DeploymentManager.protocolFee),
 | 
						|
                forwarderFeePercentage,
 | 
						|
            );
 | 
						|
            const revertError = new ForwarderRevertErrors.InsufficientEthForFeeError(ethFee, ethFee.minus(1));
 | 
						|
            await testFactory.marketBuyTestAsync([order], 0.5, {
 | 
						|
                ethValueAdjustment: -1,
 | 
						|
                forwarderFeePercentage,
 | 
						|
                revertError,
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 | 
						|
// tslint:disable:max-file-line-count
 |