* add LibERC721Order.sol * Add ERC721 interface to vendor/ * Add ERC721OrdersFeature interface * Storage lib for ERC721 orders feature * Implement basic functionality for ERC721 orders (buy, sell, cancel, etc) * Add isValidERC721OrderSignature to interface * implement onERC721Received * Implement batchBuyERC721s * left/right orders -> sell/buy orders * Add missing @return comments * Implement matching functions * Use SafeMath where necessary * add rich errors for ERC721OrdersFeature * Add comments * Add presign support for ERC721 orders * Cancel using just the order nonce * Add IERC721OrdersFeature to IZeroEx * Add taker callback * Assembly optimizations in LibERC721Order * Add ERC721Orders TS class * create zero-ex/contracts/test/integration/ and tokens/ directories * TestMintableERC721Token * tmp * address feedback from original PR (#391) * address feedback from original PR * Update contracts/zero-ex/contracts/src/features/ERC721OrdersFeature.sol Co-authored-by: Kim Persson <kimpers@users.noreply.github.com> * address review feedback and improve order parameter naming * Add batchCancel function * Emit order fields in preSign * Fix tests Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: Kim Persson <kimpers@users.noreply.github.com> Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com> * Remove revertIfIncomplete from batchMatch * Sanity check maker address in preSign * ERC1155OrdersFeature contracts * Commence refactor, abstract base contract * ERC721OrdersFeature inherits from NFTOrders * Refactor ERC1155OrdersFeature to inherit from NFTOrders * Fix order hashing * Fix ERC721OrdersFeature tests * Typos * Remove maker address from preSigned mapping * disable dex sampler tests * Refactor TS tooling * Address PR feedback * Rearrange event fields to better align with struct fields * Update comments * update AbiEncoder.create params * Add ERC1155Order to protocol-utils * Add ERC1155OrdersFeeature tests * Bump package versions and regenerate contract wrappers * Add ERC165Feature * NFT orders: address audit findings (#417) * CVF-1: use pragma solidity ^0.6 instead of ^0.6.5 * CVF-11: fix inaccurate comment * CVF-16: Enable taker callbacks for batchBuyERC1155s * CVF-17: use internal call if revertIfIncomplete is true * CVF-21: avoid duplicate SLOAD * CVF-23: merge if statements * CVF-24: Reorder status checks to be consistent with ERC721OrdersFeature * CVF-25: Update unclear comment (canonical hash -> EIP-712 hash) * CVF-31: Document keys of orderState mapping * CVF-45: DRY up fees/properties hashing * CVF-47, CVF-50, CVF-57: calculate properties.length once; hash propertyStructHashArray in-place using assembly * CVF-56: More descriptive names for assembly variables * CVF-71: Update confusing comment about rounding in _payFees * CVF-72: Move ETH assertions outside of loop in _payFees * CVF-74: Move property validation loop to else branch * CVF-82: Update inaccurate comment * CVF-86: Enable taker callbacks for batchBuyERC721s * CVF-87: use internal call if revertIfIncomplete is true * CVF-89: Perform token mismatch checks before stateful operations * CVF-90, CVF-91: Defer ERC20 token mismatch check * CVF-93: Add inline comments for _payFees parameters in matchERC721Orders * CVF-94: Fix comment (Step 7 -> Step 5) * CVF-98: Use binary & operator instead of mod * CVF-99: Update unclear comment (canonical hash -> EIP-712 hash) * CVF-65, CVF-66, CVF-67: Copy params.ethAvailable into local variable; check that ethSpent does not exceed ethAvailable; remove ethAvailable < erc20FillAmount check * CVF-52, CVF-55, CVF-59: calculate fees.length once; hash feeStructHashArray in-place using assembly * CVF-14, CVF-32: OrderState struct; separate storage mapping for 1155 cancellations so orders can be cancelled by nonce * Update changelogs, IZeroEx artifact/wrapper Co-authored-by: Lawrence Forman <lawrence@0xproject.com> Co-authored-by: Lawrence Forman <me@merklejerk.com> Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
		
			
				
	
	
		
			1732 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1732 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
    blockchainTests,
 | 
						|
    constants,
 | 
						|
    describe,
 | 
						|
    expect,
 | 
						|
    getRandomInteger,
 | 
						|
    randomAddress,
 | 
						|
    verifyEventsFromLogs,
 | 
						|
} from '@0x/contracts-test-utils';
 | 
						|
import { ERC721Order, NFTOrder, RevertErrors, SIGNATURE_ABI, SignatureType } from '@0x/protocol-utils';
 | 
						|
import { AbiEncoder, BigNumber, hexUtils, NULL_BYTES, StringRevertError } from '@0x/utils';
 | 
						|
 | 
						|
import {
 | 
						|
    IOwnableFeatureContract,
 | 
						|
    IZeroExContract,
 | 
						|
    IZeroExERC721OrderFilledEventArgs,
 | 
						|
    IZeroExEvents,
 | 
						|
} from '../../src/wrappers';
 | 
						|
import { artifacts } from '../artifacts';
 | 
						|
import { abis } from '../utils/abis';
 | 
						|
import { fullMigrateAsync } from '../utils/migration';
 | 
						|
import { getRandomERC721Order } from '../utils/nft_orders';
 | 
						|
 | 
						|
import {
 | 
						|
    ERC721OrdersFeatureContract,
 | 
						|
    TestFeeRecipientContract,
 | 
						|
    TestMintableERC20TokenContract,
 | 
						|
    TestMintableERC721TokenContract,
 | 
						|
    TestNFTOrderPresignerContract,
 | 
						|
    TestPropertyValidatorContract,
 | 
						|
    TestWethContract,
 | 
						|
} from '../wrappers';
 | 
						|
 | 
						|
blockchainTests.resets('ERC721OrdersFeature', env => {
 | 
						|
    const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT: ZERO } = constants;
 | 
						|
    const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
 | 
						|
 | 
						|
    let owner: string;
 | 
						|
    let maker: string;
 | 
						|
    let taker: string;
 | 
						|
    let otherMaker: string;
 | 
						|
    let otherTaker: string;
 | 
						|
    let matcher: string;
 | 
						|
    let feeRecipient: TestFeeRecipientContract;
 | 
						|
    let zeroEx: IZeroExContract;
 | 
						|
    let weth: TestWethContract;
 | 
						|
    let erc20Token: TestMintableERC20TokenContract;
 | 
						|
    let erc721Token: TestMintableERC721TokenContract;
 | 
						|
 | 
						|
    async function sendEtherAsync(to: string, amount: BigNumber): Promise<void> {
 | 
						|
        await env.web3Wrapper.awaitTransactionSuccessAsync(
 | 
						|
            await env.web3Wrapper.sendTransactionAsync({
 | 
						|
                ...env.txDefaults,
 | 
						|
                to,
 | 
						|
                from: owner,
 | 
						|
                value: amount,
 | 
						|
            }),
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    before(async () => {
 | 
						|
        // Useful for ETH balance accounting
 | 
						|
        const txDefaults = { ...env.txDefaults, gasPrice: 0 };
 | 
						|
        [owner, maker, taker, otherMaker, otherTaker, matcher] = await env.getAccountAddressesAsync();
 | 
						|
 | 
						|
        weth = await TestWethContract.deployFrom0xArtifactAsync(
 | 
						|
            artifacts.TestWeth,
 | 
						|
            env.provider,
 | 
						|
            txDefaults,
 | 
						|
            artifacts,
 | 
						|
        );
 | 
						|
        erc20Token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
 | 
						|
            artifacts.TestMintableERC20Token,
 | 
						|
            env.provider,
 | 
						|
            txDefaults,
 | 
						|
            artifacts,
 | 
						|
        );
 | 
						|
        erc721Token = await TestMintableERC721TokenContract.deployFrom0xArtifactAsync(
 | 
						|
            artifacts.TestMintableERC721Token,
 | 
						|
            env.provider,
 | 
						|
            txDefaults,
 | 
						|
            artifacts,
 | 
						|
        );
 | 
						|
 | 
						|
        zeroEx = await fullMigrateAsync(owner, env.provider, txDefaults, {}, { wethAddress: weth.address });
 | 
						|
        zeroEx = new IZeroExContract(zeroEx.address, env.provider, txDefaults, abis);
 | 
						|
 | 
						|
        const featureImpl = await ERC721OrdersFeatureContract.deployFrom0xArtifactAsync(
 | 
						|
            artifacts.ERC721OrdersFeature,
 | 
						|
            env.provider,
 | 
						|
            txDefaults,
 | 
						|
            artifacts,
 | 
						|
            zeroEx.address,
 | 
						|
            weth.address,
 | 
						|
        );
 | 
						|
        await new IOwnableFeatureContract(zeroEx.address, env.provider, txDefaults, abis)
 | 
						|
            .migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
 | 
						|
            .awaitTransactionSuccessAsync();
 | 
						|
 | 
						|
        await Promise.all([
 | 
						|
            erc20Token.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            }),
 | 
						|
            erc20Token.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: otherMaker,
 | 
						|
            }),
 | 
						|
            erc20Token.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            }),
 | 
						|
            erc20Token.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: otherTaker,
 | 
						|
            }),
 | 
						|
            weth.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            }),
 | 
						|
            weth.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: otherMaker,
 | 
						|
            }),
 | 
						|
            weth.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            }),
 | 
						|
            weth.approve(zeroEx.address, MAX_UINT256).awaitTransactionSuccessAsync({
 | 
						|
                from: otherTaker,
 | 
						|
            }),
 | 
						|
            erc721Token.setApprovalForAll(zeroEx.address, true).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            }),
 | 
						|
            erc721Token.setApprovalForAll(zeroEx.address, true).awaitTransactionSuccessAsync({
 | 
						|
                from: otherMaker,
 | 
						|
            }),
 | 
						|
            erc721Token.setApprovalForAll(zeroEx.address, true).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            }),
 | 
						|
            erc721Token.setApprovalForAll(zeroEx.address, true).awaitTransactionSuccessAsync({
 | 
						|
                from: otherTaker,
 | 
						|
            }),
 | 
						|
        ]);
 | 
						|
 | 
						|
        feeRecipient = await TestFeeRecipientContract.deployFrom0xArtifactAsync(
 | 
						|
            artifacts.TestFeeRecipient,
 | 
						|
            env.provider,
 | 
						|
            txDefaults,
 | 
						|
            artifacts,
 | 
						|
        );
 | 
						|
    });
 | 
						|
 | 
						|
    async function mintAssetsAsync(
 | 
						|
        order: ERC721Order,
 | 
						|
        tokenId: BigNumber = order.erc721TokenId,
 | 
						|
        _taker: string = taker,
 | 
						|
    ): Promise<void> {
 | 
						|
        const totalFeeAmount = order.fees.length > 0 ? BigNumber.sum(...order.fees.map(fee => fee.amount)) : ZERO;
 | 
						|
        if (order.direction === NFTOrder.TradeDirection.SellNFT) {
 | 
						|
            await erc721Token.mint(order.maker, tokenId).awaitTransactionSuccessAsync();
 | 
						|
            if (order.erc20Token !== ETH_TOKEN_ADDRESS) {
 | 
						|
                await erc20Token
 | 
						|
                    .mint(_taker, order.erc20TokenAmount.plus(totalFeeAmount))
 | 
						|
                    .awaitTransactionSuccessAsync();
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            await erc721Token.mint(_taker, tokenId).awaitTransactionSuccessAsync();
 | 
						|
            if (order.erc20Token === weth.address) {
 | 
						|
                await weth.deposit().awaitTransactionSuccessAsync({
 | 
						|
                    from: order.maker,
 | 
						|
                    value: order.erc20TokenAmount.plus(totalFeeAmount),
 | 
						|
                });
 | 
						|
            } else {
 | 
						|
                await erc20Token
 | 
						|
                    .mint(order.maker, order.erc20TokenAmount.plus(totalFeeAmount))
 | 
						|
                    .awaitTransactionSuccessAsync();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    async function assertBalancesAsync(
 | 
						|
        order: ERC721Order,
 | 
						|
        tokenId: BigNumber = order.erc721TokenId,
 | 
						|
        _taker: string = taker,
 | 
						|
    ): Promise<void> {
 | 
						|
        const token = order.erc20Token === weth.address ? weth : erc20Token;
 | 
						|
        if (order.direction === NFTOrder.TradeDirection.SellNFT) {
 | 
						|
            const makerBalance = await token.balanceOf(order.maker).callAsync();
 | 
						|
            expect(makerBalance).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(tokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(_taker);
 | 
						|
        } else {
 | 
						|
            const erc20Balance = await token.balanceOf(_taker).callAsync();
 | 
						|
            expect(erc20Balance).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(tokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(order.maker);
 | 
						|
        }
 | 
						|
        if (order.fees.length > 0) {
 | 
						|
            await Promise.all(
 | 
						|
                order.fees.map(async fee => {
 | 
						|
                    const feeRecipientBalance = await token.balanceOf(fee.recipient).callAsync();
 | 
						|
                    expect(feeRecipientBalance).to.bignumber.equal(fee.amount);
 | 
						|
                }),
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    function getTestERC721Order(fields: Partial<ERC721Order> = {}): ERC721Order {
 | 
						|
        return getRandomERC721Order({
 | 
						|
            maker,
 | 
						|
            verifyingContract: zeroEx.address,
 | 
						|
            chainId: 1337,
 | 
						|
            erc20Token: erc20Token.address,
 | 
						|
            erc721Token: erc721Token.address,
 | 
						|
            taker: NULL_ADDRESS,
 | 
						|
            ...fields,
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    function createERC721OrderFilledEvent(
 | 
						|
        order: ERC721Order,
 | 
						|
        _taker: string = taker,
 | 
						|
        erc721TokenId: BigNumber = order.erc721TokenId,
 | 
						|
    ): IZeroExERC721OrderFilledEventArgs {
 | 
						|
        return {
 | 
						|
            direction: order.direction,
 | 
						|
            maker: order.maker,
 | 
						|
            taker,
 | 
						|
            nonce: order.nonce,
 | 
						|
            erc20Token: order.erc20Token,
 | 
						|
            erc20TokenAmount: order.erc20TokenAmount,
 | 
						|
            erc721Token: order.erc721Token,
 | 
						|
            erc721TokenId,
 | 
						|
            matcher: NULL_ADDRESS,
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    describe('getERC721OrderHash()', () => {
 | 
						|
        it('returns the correct hash for order with no fees or properties', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const hash = await zeroEx.getERC721OrderHash(order).callAsync();
 | 
						|
            expect(hash).to.eq(order.getHash());
 | 
						|
        });
 | 
						|
        it('returns the correct hash for order with null property', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                erc721TokenProperties: [
 | 
						|
                    {
 | 
						|
                        propertyValidator: NULL_ADDRESS,
 | 
						|
                        propertyData: NULL_BYTES,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const hash = await zeroEx.getERC721OrderHash(order).callAsync();
 | 
						|
            expect(hash).to.eq(order.getHash());
 | 
						|
        });
 | 
						|
        it('returns the correct hash for order with 1 fee, 1 property', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: randomAddress(),
 | 
						|
                        amount: getRandomInteger(0, MAX_UINT256),
 | 
						|
                        feeData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
                erc721TokenProperties: [
 | 
						|
                    {
 | 
						|
                        propertyValidator: randomAddress(),
 | 
						|
                        propertyData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const hash = await zeroEx.getERC721OrderHash(order).callAsync();
 | 
						|
            expect(hash).to.eq(order.getHash());
 | 
						|
        });
 | 
						|
        it('returns the correct hash for order with 2 fees, 2 properties', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: randomAddress(),
 | 
						|
                        amount: getRandomInteger(0, MAX_UINT256),
 | 
						|
                        feeData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        recipient: randomAddress(),
 | 
						|
                        amount: getRandomInteger(0, MAX_UINT256),
 | 
						|
                        feeData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
                erc721TokenProperties: [
 | 
						|
                    {
 | 
						|
                        propertyValidator: randomAddress(),
 | 
						|
                        propertyData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        propertyValidator: randomAddress(),
 | 
						|
                        propertyData: hexUtils.random(),
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const hash = await zeroEx.getERC721OrderHash(order).callAsync();
 | 
						|
            expect(hash).to.eq(order.getHash());
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('validateERC721OrderSignature', () => {
 | 
						|
        it('succeeds for a valid EthSign signature', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await zeroEx.validateERC721OrderSignature(order, signature).callAsync();
 | 
						|
        });
 | 
						|
        it('reverts for an invalid EthSign signature', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(
 | 
						|
                env.provider,
 | 
						|
                SignatureType.EthSign,
 | 
						|
                otherMaker,
 | 
						|
            );
 | 
						|
            const tx = zeroEx.validateERC721OrderSignature(order, signature).callAsync();
 | 
						|
            expect(tx).to.revertWith(new RevertErrors.NFTOrders.InvalidSignerError(maker, otherMaker));
 | 
						|
        });
 | 
						|
        it('succeeds for a valid EIP-712 signature', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EIP712);
 | 
						|
            await zeroEx.validateERC721OrderSignature(order, signature).callAsync();
 | 
						|
        });
 | 
						|
        it('reverts for an invalid EIP-712 signature', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EIP712, otherMaker);
 | 
						|
            const tx = zeroEx.validateERC721OrderSignature(order, signature).callAsync();
 | 
						|
            expect(tx).to.revertWith(new RevertErrors.NFTOrders.InvalidSignerError(maker, otherMaker));
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('cancelERC721Order', () => {
 | 
						|
        it('can cancel an order', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            const tx = await zeroEx.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            });
 | 
						|
            verifyEventsFromLogs(tx.logs, [{ maker, nonce: order.nonce }], IZeroExEvents.ERC721OrderCancelled);
 | 
						|
            const orderStatus = await zeroEx.getERC721OrderStatus(order).callAsync();
 | 
						|
            expect(orderStatus).to.equal(NFTOrder.OrderStatus.Unfillable);
 | 
						|
            const bitVector = await zeroEx
 | 
						|
                .getERC721OrderStatusBitVector(maker, order.nonce.dividedToIntegerBy(256))
 | 
						|
                .callAsync();
 | 
						|
            const flag = new BigNumber(2).exponentiatedBy(order.nonce.mod(256));
 | 
						|
            expect(bitVector).to.bignumber.equal(flag);
 | 
						|
        });
 | 
						|
        it('cancelling an order twice silently succeeds', async () => {
 | 
						|
            const order = getTestERC721Order();
 | 
						|
            await zeroEx.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            });
 | 
						|
            const tx = await zeroEx.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            });
 | 
						|
            verifyEventsFromLogs(tx.logs, [{ maker, nonce: order.nonce }], IZeroExEvents.ERC721OrderCancelled);
 | 
						|
            const orderStatus = await zeroEx.getERC721OrderStatus(order).callAsync();
 | 
						|
            expect(orderStatus).to.equal(NFTOrder.OrderStatus.Unfillable);
 | 
						|
            const bitVector = await zeroEx
 | 
						|
                .getERC721OrderStatusBitVector(maker, order.nonce.dividedToIntegerBy(256))
 | 
						|
                .callAsync();
 | 
						|
            const flag = new BigNumber(2).exponentiatedBy(order.nonce.mod(256));
 | 
						|
            expect(bitVector).to.bignumber.equal(flag);
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('sellERC721', () => {
 | 
						|
        it('can fill a ERC721 buy order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = await zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
            verifyEventsFromLogs(tx.logs, [createERC721OrderFilledEvent(order)], IZeroExEvents.ERC721OrderFilled);
 | 
						|
            const bitVector = await zeroEx
 | 
						|
                .getERC721OrderStatusBitVector(maker, order.nonce.dividedToIntegerBy(256))
 | 
						|
                .callAsync();
 | 
						|
            const flag = new BigNumber(2).exponentiatedBy(order.nonce.mod(256));
 | 
						|
            expect(bitVector).to.bignumber.equal(flag);
 | 
						|
        });
 | 
						|
        it('cannot fill the same order twice', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Unfillable),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('can fill two orders from the same maker with different nonces', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                nonce: ZERO,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order1);
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order1, signature1, order1.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                nonce: new BigNumber(1),
 | 
						|
            });
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order2);
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order2, signature2, order2.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            const bitVector = await zeroEx.getERC721OrderStatusBitVector(maker, ZERO).callAsync();
 | 
						|
            expect(bitVector).to.bignumber.equal(3); // 0...00011
 | 
						|
        });
 | 
						|
        it('cannot fill a cancelled order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            });
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Unfillable),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('cannot fill an invalid order (erc20Token == ETH)', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await erc721Token.mint(taker, order.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith('NFTOrders::_validateBuyOrder/NATIVE_TOKEN_NOT_ALLOWED');
 | 
						|
        });
 | 
						|
        it('cannot fill an expired order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                expiry: new BigNumber(Math.floor(Date.now() / 1000 - 1)),
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Expired),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if a sell order is provided', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith('NFTOrders::_validateBuyOrder/WRONG_TRADE_DIRECTION');
 | 
						|
        });
 | 
						|
        it('reverts if the taker is not the taker address specified in the order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                taker,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order, order.erc721TokenId, otherTaker);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: otherTaker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(new RevertErrors.NFTOrders.OnlyTakerError(otherTaker, taker));
 | 
						|
        });
 | 
						|
        it('succeeds if the taker is the taker address specified in the order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                taker,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
        });
 | 
						|
        it('reverts if an invalid signature is provided', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EIP712, otherMaker);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(new RevertErrors.NFTOrders.InvalidSignerError(maker, otherMaker));
 | 
						|
        });
 | 
						|
        it('reverts if `unwrapNativeToken` is true and `erc20Token` is not WETH', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, true, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.ERC20TokenMismatchError(order.erc20Token, weth.address),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('sends ETH to taker if `unwrapNativeToken` is true and `erc20Token` is WETH', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order, signature, order.erc721TokenId, true, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
            expect(takerEthBalanceAfter.minus(takerEthBalanceBefore)).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(order.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(maker);
 | 
						|
        });
 | 
						|
        describe('fees', () => {
 | 
						|
            it('single fee to EOA', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                await zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
            it('single fee, successful callback', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: feeRecipient.address,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: hexUtils.random(),
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                await zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
            it('single fee, callback reverts', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: feeRecipient.address,
 | 
						|
                            amount: new BigNumber(333),
 | 
						|
                            feeData: hexUtils.random(),
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const tx = zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                return expect(tx).to.revertWith('TestFeeRecipient::receiveZeroExFeeCallback/REVERT');
 | 
						|
            });
 | 
						|
            it('single fee, callback returns invalid value', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: feeRecipient.address,
 | 
						|
                            amount: new BigNumber(666),
 | 
						|
                            feeData: hexUtils.random(),
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const tx = zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                return expect(tx).to.revertWith('NFTOrders::_payFees/CALLBACK_FAILED');
 | 
						|
            });
 | 
						|
            it('multiple fees to EOAs', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            recipient: otherTaker,
 | 
						|
                            amount: new BigNumber(222),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                await zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
        });
 | 
						|
        describe('properties', () => {
 | 
						|
            let propertyValidator: TestPropertyValidatorContract;
 | 
						|
 | 
						|
            before(async () => {
 | 
						|
                propertyValidator = await TestPropertyValidatorContract.deployFrom0xArtifactAsync(
 | 
						|
                    artifacts.TestPropertyValidator,
 | 
						|
                    env.provider,
 | 
						|
                    env.txDefaults,
 | 
						|
                    artifacts,
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Checks tokenId if no properties are provided', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order, order.erc721TokenId.plus(1));
 | 
						|
                const tx = zeroEx
 | 
						|
                    .sellERC721(order, signature, order.erc721TokenId.plus(1), false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                return expect(tx).to.revertWith(
 | 
						|
                    new RevertErrors.NFTOrders.TokenIdMismatchError(order.erc721TokenId.plus(1), order.erc721TokenId),
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Null property', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    erc721TokenId: ZERO,
 | 
						|
                    erc721TokenProperties: [
 | 
						|
                        {
 | 
						|
                            propertyValidator: NULL_ADDRESS,
 | 
						|
                            propertyData: NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const tokenId = getRandomInteger(0, MAX_UINT256);
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order, tokenId);
 | 
						|
                await zeroEx.sellERC721(order, signature, tokenId, false, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
                await assertBalancesAsync(order, tokenId);
 | 
						|
            });
 | 
						|
            it('Reverts if property validation fails', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    erc721TokenId: ZERO,
 | 
						|
                    erc721TokenProperties: [
 | 
						|
                        {
 | 
						|
                            propertyValidator: propertyValidator.address,
 | 
						|
                            propertyData: NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const tokenId = getRandomInteger(0, MAX_UINT256);
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order, tokenId);
 | 
						|
                const tx = zeroEx
 | 
						|
                    .sellERC721(order, signature, tokenId, false, NULL_BYTES)
 | 
						|
                    .awaitTransactionSuccessAsync({
 | 
						|
                        from: taker,
 | 
						|
                    });
 | 
						|
                return expect(tx).to.revertWith(
 | 
						|
                    new RevertErrors.NFTOrders.PropertyValidationFailedError(
 | 
						|
                        propertyValidator.address,
 | 
						|
                        order.erc721Token,
 | 
						|
                        tokenId,
 | 
						|
                        NULL_BYTES,
 | 
						|
                        new StringRevertError('TestPropertyValidator::validateProperty/REVERT').encode(),
 | 
						|
                    ),
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('Successful property validation', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                    erc721TokenId: ZERO,
 | 
						|
                    erc721TokenProperties: [
 | 
						|
                        {
 | 
						|
                            propertyValidator: propertyValidator.address,
 | 
						|
                            propertyData: hexUtils.random(),
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const tokenId = getRandomInteger(0, MAX_UINT256);
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order, tokenId);
 | 
						|
                await zeroEx.sellERC721(order, signature, tokenId, false, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
                await assertBalancesAsync(order, tokenId);
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('onERC721Received', () => {
 | 
						|
        let dataEncoder: AbiEncoder.DataType;
 | 
						|
        before(() => {
 | 
						|
            dataEncoder = AbiEncoder.create(
 | 
						|
                [
 | 
						|
                    {
 | 
						|
                        name: 'order',
 | 
						|
                        type: 'tuple',
 | 
						|
                        components: ERC721Order.STRUCT_ABI,
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        name: 'signature',
 | 
						|
                        type: 'tuple',
 | 
						|
                        components: SIGNATURE_ABI,
 | 
						|
                    },
 | 
						|
                    { name: 'unwrapNativeToken', type: 'bool' },
 | 
						|
                ],
 | 
						|
                [
 | 
						|
                    {
 | 
						|
                        name: 'property',
 | 
						|
                        type: 'tuple',
 | 
						|
                        internalType: 'Property',
 | 
						|
                        components: [
 | 
						|
                            {
 | 
						|
                                name: 'propertyValidator',
 | 
						|
                                type: 'address',
 | 
						|
                            },
 | 
						|
                            { name: 'propertyData', type: 'bytes' },
 | 
						|
                        ],
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        name: 'fee',
 | 
						|
                        type: 'tuple',
 | 
						|
                        internalType: 'Fee',
 | 
						|
                        components: [
 | 
						|
                            { name: 'recipient', type: 'address' },
 | 
						|
                            { name: 'amount', type: 'uint256' },
 | 
						|
                            { name: 'feeData', type: 'bytes' },
 | 
						|
                        ],
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('throws if data is not encoded correctly', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = erc721Token
 | 
						|
                .safeTransferFrom2(taker, zeroEx.address, order.erc721TokenId, hexUtils.random())
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.be.rejected();
 | 
						|
        });
 | 
						|
        it('reverts if msg.sender != order.erc721Token', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .onERC721Received(
 | 
						|
                    taker,
 | 
						|
                    taker,
 | 
						|
                    order.erc721TokenId,
 | 
						|
                    dataEncoder.encode({
 | 
						|
                        order,
 | 
						|
                        signature,
 | 
						|
                        unwrapNativeToken: false,
 | 
						|
                    }),
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.ERC721TokenMismatchError(taker, order.erc721Token),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if transferred tokenId does not match order.erc721TokenId', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order, order.erc721TokenId.plus(1));
 | 
						|
 | 
						|
            const tx = erc721Token
 | 
						|
                .safeTransferFrom2(
 | 
						|
                    taker,
 | 
						|
                    zeroEx.address,
 | 
						|
                    order.erc721TokenId.plus(1),
 | 
						|
                    dataEncoder.encode({
 | 
						|
                        order,
 | 
						|
                        signature,
 | 
						|
                        unwrapNativeToken: false,
 | 
						|
                    }),
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.TokenIdMismatchError(order.erc721TokenId.plus(1), order.erc721TokenId),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('can sell ERC721 without approval', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            // revoke approval
 | 
						|
            await erc721Token.setApprovalForAll(zeroEx.address, false).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
 | 
						|
            await erc721Token
 | 
						|
                .safeTransferFrom2(
 | 
						|
                    taker,
 | 
						|
                    zeroEx.address,
 | 
						|
                    order.erc721TokenId,
 | 
						|
                    dataEncoder.encode({
 | 
						|
                        order,
 | 
						|
                        signature,
 | 
						|
                        unwrapNativeToken: false,
 | 
						|
                    }),
 | 
						|
                )
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('buyERC721', () => {
 | 
						|
        it('can fill a ERC721 sell order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
            verifyEventsFromLogs(tx.logs, [createERC721OrderFilledEvent(order)], IZeroExEvents.ERC721OrderFilled);
 | 
						|
            const bitVector = await zeroEx
 | 
						|
                .getERC721OrderStatusBitVector(maker, order.nonce.dividedToIntegerBy(256))
 | 
						|
                .callAsync();
 | 
						|
            const flag = new BigNumber(2).exponentiatedBy(order.nonce.mod(256));
 | 
						|
            expect(bitVector).to.bignumber.equal(flag);
 | 
						|
        });
 | 
						|
        it('cannot fill the same order twice', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Unfillable),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('cannot fill a cancelled order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync({
 | 
						|
                from: maker,
 | 
						|
            });
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Unfillable),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('cannot fill an expired order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                expiry: new BigNumber(Math.floor(Date.now() / 1000 - 1)),
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(maker, order.nonce, NFTOrder.OrderStatus.Expired),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if a buy order is provided', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith('NFTOrders::_validateSellOrder/WRONG_TRADE_DIRECTION');
 | 
						|
        });
 | 
						|
        it('reverts if the taker is not the taker address specified in the order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                taker,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order, order.erc721TokenId, otherTaker);
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: otherTaker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith(new RevertErrors.NFTOrders.OnlyTakerError(otherTaker, taker));
 | 
						|
        });
 | 
						|
        it('succeeds if the taker is the taker address specified in the order', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                taker,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
        });
 | 
						|
        it('reverts if an invalid signature is provided', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EIP712, otherMaker);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith(new RevertErrors.NFTOrders.InvalidSignerError(maker, otherMaker));
 | 
						|
        });
 | 
						|
        describe('ETH', () => {
 | 
						|
            it('can fill an order with ETH (and refunds excess ETH)', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const makerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(maker);
 | 
						|
                const tx = await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount.plus(1),
 | 
						|
                });
 | 
						|
                const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const makerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(maker);
 | 
						|
                expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
                expect(makerEthBalanceAfter.minus(makerEthBalanceBefore)).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
                verifyEventsFromLogs(
 | 
						|
                    tx.logs,
 | 
						|
                    [
 | 
						|
                        {
 | 
						|
                            _from: maker,
 | 
						|
                            _to: taker,
 | 
						|
                            _tokenId: order.erc721TokenId,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                    'Transfer',
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('can fill a WETH order with ETH', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    erc20Token: weth.address,
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await erc721Token.mint(maker, order.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
                const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount,
 | 
						|
                });
 | 
						|
                const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
            it('uses WETH if not enough ETH to fill WETH order', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    erc20Token: weth.address,
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await weth.deposit().awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount,
 | 
						|
                });
 | 
						|
                await erc721Token.mint(maker, order.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
                const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount.minus(1),
 | 
						|
                });
 | 
						|
                const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                expect(takerEthBalanceAfter).to.bignumber.equal(takerEthBalanceBefore);
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
        });
 | 
						|
        describe('fees', () => {
 | 
						|
            it('single fee to EOA', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
            it('pays fees in ETH if erc20Token == ETH', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                    erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const makerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(maker);
 | 
						|
                const feeRecipientEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(otherMaker);
 | 
						|
                await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount.plus(order.fees[0].amount).plus(1),
 | 
						|
                });
 | 
						|
                const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const makerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(maker);
 | 
						|
                const feeRecipientEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(otherMaker);
 | 
						|
                expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(
 | 
						|
                    order.erc20TokenAmount.plus(order.fees[0].amount),
 | 
						|
                );
 | 
						|
                expect(makerEthBalanceAfter.minus(makerEthBalanceBefore)).to.bignumber.equal(order.erc20TokenAmount);
 | 
						|
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
						|
                    order.fees[0].amount,
 | 
						|
                );
 | 
						|
                const erc721Owner = await erc721Token.ownerOf(order.erc721TokenId).callAsync();
 | 
						|
                expect(erc721Owner).to.equal(taker);
 | 
						|
            });
 | 
						|
            it('pays fees in ETH if erc20Token == WETH but taker uses ETH', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                    erc20Token: weth.address,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const feeRecipientEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(otherMaker);
 | 
						|
                const tx = await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount.plus(order.fees[0].amount).plus(1),
 | 
						|
                });
 | 
						|
                const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
                const feeRecipientEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(otherMaker);
 | 
						|
                expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(
 | 
						|
                    order.erc20TokenAmount.plus(order.fees[0].amount),
 | 
						|
                );
 | 
						|
                expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
 | 
						|
                    order.fees[0].amount,
 | 
						|
                );
 | 
						|
                verifyEventsFromLogs(
 | 
						|
                    tx.logs,
 | 
						|
                    [
 | 
						|
                        {
 | 
						|
                            _from: maker,
 | 
						|
                            _to: taker,
 | 
						|
                            _tokenId: order.erc721TokenId,
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            token: weth.address,
 | 
						|
                            from: zeroEx.address,
 | 
						|
                            to: maker,
 | 
						|
                            value: order.erc20TokenAmount,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                    'Transfer',
 | 
						|
                );
 | 
						|
            });
 | 
						|
            it('pays fees in WETH if taker uses WETH', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                    erc20Token: weth.address,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await erc721Token.mint(maker, order.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
                await weth.deposit().awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount.plus(order.fees[0].amount),
 | 
						|
                });
 | 
						|
                await zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
                await assertBalancesAsync(order);
 | 
						|
            });
 | 
						|
            it('reverts if overspent ETH', async () => {
 | 
						|
                const order = getTestERC721Order({
 | 
						|
                    direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                    erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
                    fees: [
 | 
						|
                        {
 | 
						|
                            recipient: otherMaker,
 | 
						|
                            amount: new BigNumber(111),
 | 
						|
                            feeData: constants.NULL_BYTES,
 | 
						|
                        },
 | 
						|
                    ],
 | 
						|
                });
 | 
						|
                await sendEtherAsync(zeroEx.address, order.fees[0].amount);
 | 
						|
                const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
                await mintAssetsAsync(order);
 | 
						|
                const tx = zeroEx.buyERC721(order, signature, NULL_BYTES).awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order.erc20TokenAmount,
 | 
						|
                });
 | 
						|
                return expect(tx).to.revertWith(
 | 
						|
                    new RevertErrors.NFTOrders.OverspentEthError(
 | 
						|
                        order.erc20TokenAmount.plus(order.fees[0].amount),
 | 
						|
                        order.erc20TokenAmount,
 | 
						|
                    ),
 | 
						|
                );
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('batchBuyERC721s', () => {
 | 
						|
        it('reverts if arrays are different lengths', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature = await order.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx.batchBuyERC721s([order], [signature, signature], [], false).awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith('ERC721OrdersFeature::batchBuyERC721s/ARRAY_LENGTH_MISMATCH');
 | 
						|
        });
 | 
						|
        it('successfully fills multiple orders', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order1);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
            });
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await erc721Token.mint(maker, order2.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
            await weth.deposit().sendTransactionAsync({
 | 
						|
                from: taker,
 | 
						|
                value: order2.erc20TokenAmount,
 | 
						|
            });
 | 
						|
            await zeroEx
 | 
						|
                .batchBuyERC721s([order1, order2], [signature1, signature2], [NULL_BYTES, NULL_BYTES], false)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(order1);
 | 
						|
            await assertBalancesAsync(order2);
 | 
						|
        });
 | 
						|
        it('catches revert if one order fails', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order1);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
            });
 | 
						|
            // invalid signature
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(
 | 
						|
                env.provider,
 | 
						|
                SignatureType.EIP712,
 | 
						|
                otherMaker,
 | 
						|
            );
 | 
						|
            await erc721Token.mint(maker, order2.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
            await weth.deposit().sendTransactionAsync({
 | 
						|
                from: taker,
 | 
						|
                value: order2.erc20TokenAmount,
 | 
						|
            });
 | 
						|
            const tx = zeroEx.batchBuyERC721s(
 | 
						|
                [order1, order2],
 | 
						|
                [signature1, signature2],
 | 
						|
                [NULL_BYTES, NULL_BYTES],
 | 
						|
                false,
 | 
						|
            );
 | 
						|
            const successes = await tx.callAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            expect(successes).to.deep.equal([true, false]);
 | 
						|
            await tx.awaitTransactionSuccessAsync({
 | 
						|
                from: taker,
 | 
						|
            });
 | 
						|
            await assertBalancesAsync(order1);
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(order2.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(maker);
 | 
						|
            const takerWethBalance = await weth.balanceOf(taker).callAsync();
 | 
						|
            expect(takerWethBalance).to.bignumber.equal(order2.erc20TokenAmount);
 | 
						|
        });
 | 
						|
        it('bubbles up revert if one order fails and `revertIfIncomplete == true`', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order1);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
            });
 | 
						|
            // invalid signature
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(
 | 
						|
                env.provider,
 | 
						|
                SignatureType.EIP712,
 | 
						|
                otherMaker,
 | 
						|
            );
 | 
						|
            await erc721Token.mint(maker, order2.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
            await weth.deposit().sendTransactionAsync({
 | 
						|
                from: taker,
 | 
						|
                value: order2.erc20TokenAmount,
 | 
						|
            });
 | 
						|
            const tx = zeroEx
 | 
						|
                .batchBuyERC721s([order1, order2], [signature1, signature2], [NULL_BYTES, NULL_BYTES], true)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(new RevertErrors.NFTOrders.InvalidSignerError(order2.maker, otherMaker));
 | 
						|
        });
 | 
						|
        it('can fill multiple orders with ETH, refund excess ETH', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(order1);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
            });
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await erc721Token.mint(maker, order2.erc721TokenId).awaitTransactionSuccessAsync();
 | 
						|
            const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
            await zeroEx
 | 
						|
                .batchBuyERC721s([order1, order2], [signature1, signature2], [NULL_BYTES, NULL_BYTES], true)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                    value: order1.erc20TokenAmount.plus(order2.erc20TokenAmount).plus(1),
 | 
						|
                });
 | 
						|
            const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
 | 
						|
            expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(
 | 
						|
                order1.erc20TokenAmount.plus(order2.erc20TokenAmount),
 | 
						|
            );
 | 
						|
            const erc721Owner1 = await erc721Token.ownerOf(order1.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner1).to.bignumber.equal(taker);
 | 
						|
            const erc721Owner2 = await erc721Token.ownerOf(order2.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner2).to.bignumber.equal(taker);
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('preSignERC721Order', () => {
 | 
						|
        const PRESIGN_SIGNATURE = {
 | 
						|
            signatureType: SignatureType.PreSigned,
 | 
						|
            v: 0,
 | 
						|
            r: constants.NULL_BYTES32,
 | 
						|
            s: constants.NULL_BYTES32,
 | 
						|
        };
 | 
						|
        let contractMaker: TestNFTOrderPresignerContract;
 | 
						|
        before(async () => {
 | 
						|
            contractMaker = await TestNFTOrderPresignerContract.deployFrom0xArtifactAsync(
 | 
						|
                artifacts.TestNFTOrderPresigner,
 | 
						|
                env.provider,
 | 
						|
                env.txDefaults,
 | 
						|
                artifacts,
 | 
						|
                zeroEx.address,
 | 
						|
            );
 | 
						|
            await contractMaker.approveERC20(erc20Token.address).awaitTransactionSuccessAsync();
 | 
						|
            await contractMaker.approveERC721(erc721Token.address).awaitTransactionSuccessAsync();
 | 
						|
        });
 | 
						|
        it('can fill order that has been presigned by the maker', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                maker: contractMaker.address,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await contractMaker.preSignERC721Order(order).awaitTransactionSuccessAsync();
 | 
						|
            await zeroEx
 | 
						|
                .sellERC721(order, PRESIGN_SIGNATURE, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(order);
 | 
						|
        });
 | 
						|
        it('cannot fill order that has not been presigned by the maker', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                maker: contractMaker.address,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, PRESIGN_SIGNATURE, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.InvalidSignerError(contractMaker.address, NULL_ADDRESS),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('cannot fill order that was presigned then cancelled', async () => {
 | 
						|
            const order = getTestERC721Order({
 | 
						|
                maker: contractMaker.address,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            await mintAssetsAsync(order);
 | 
						|
            await contractMaker.preSignERC721Order(order).awaitTransactionSuccessAsync();
 | 
						|
            await contractMaker.cancelERC721Order(order.nonce).awaitTransactionSuccessAsync();
 | 
						|
            const tx = zeroEx
 | 
						|
                .sellERC721(order, PRESIGN_SIGNATURE, order.erc721TokenId, false, NULL_BYTES)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: taker,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.OrderNotFillableError(
 | 
						|
                    contractMaker.address,
 | 
						|
                    order.nonce,
 | 
						|
                    NFTOrder.OrderStatus.Unfillable,
 | 
						|
                ),
 | 
						|
            );
 | 
						|
        });
 | 
						|
    });
 | 
						|
    describe('matchERC721Orders', () => {
 | 
						|
        it('cannot match two sell orders', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const tx = zeroEx.matchERC721Orders(order1, order2, signature1, signature2).awaitTransactionSuccessAsync({
 | 
						|
                from: matcher,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith('NFTOrders::_validateBuyOrder/WRONG_TRADE_DIRECTION');
 | 
						|
        });
 | 
						|
        it('cannot match two buy orders', async () => {
 | 
						|
            const order1 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature1 = await order1.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const order2 = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const signature2 = await order2.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const tx = zeroEx.matchERC721Orders(order1, order2, signature1, signature2).awaitTransactionSuccessAsync({
 | 
						|
                from: matcher,
 | 
						|
            });
 | 
						|
            return expect(tx).to.revertWith('NFTOrders::_validateSellOrder/WRONG_TRADE_DIRECTION');
 | 
						|
        });
 | 
						|
        it('erc721TokenId must match', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.TokenIdMismatchError(sellOrder.erc721TokenId, buyOrder.erc721TokenId),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('erc721Token must match', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc721Token: erc20Token.address,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.ERC721TokenMismatchError(sellOrder.erc721Token, buyOrder.erc721Token),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('erc20Token must match', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.ERC20TokenMismatchError(sellOrder.erc20Token, buyOrder.erc20Token),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if spread is negative', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.minus(1),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.NegativeSpreadError(sellOrder.erc20TokenAmount, buyOrder.erc20TokenAmount),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('matches two orders and sends profit to matcher', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            await zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(sellOrder, sellOrder.erc721TokenId, otherMaker);
 | 
						|
            const matcherBalance = await erc20Token.balanceOf(matcher).callAsync();
 | 
						|
            expect(matcherBalance).to.bignumber.equal(spread);
 | 
						|
        });
 | 
						|
        it('matches two ETH/WETH orders and sends profit to matcher', async () => {
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            const sellerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(sellOrder.maker);
 | 
						|
            const matcherEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(matcher);
 | 
						|
            await zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(sellOrder.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(buyOrder.maker);
 | 
						|
            const sellerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(sellOrder.maker);
 | 
						|
            const matcherEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(matcher);
 | 
						|
            expect(sellerEthBalanceAfter.minus(sellerEthBalanceBefore)).to.bignumber.equal(sellOrder.erc20TokenAmount);
 | 
						|
            expect(matcherEthBalanceAfter.minus(matcherEthBalanceBefore)).to.bignumber.equal(spread);
 | 
						|
        });
 | 
						|
        it('matches two orders (with fees) and sends profit to matcher', async () => {
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: otherTaker,
 | 
						|
                        amount: getRandomInteger(1, spread),
 | 
						|
                        feeData: NULL_BYTES,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            await erc20Token.mint(buyOrder.maker, sellOrder.fees[0].amount).awaitTransactionSuccessAsync();
 | 
						|
            await zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            await assertBalancesAsync(sellOrder, sellOrder.erc721TokenId, otherMaker);
 | 
						|
            const matcherBalance = await erc20Token.balanceOf(matcher).callAsync();
 | 
						|
            expect(matcherBalance).to.bignumber.equal(spread.minus(sellOrder.fees[0].amount));
 | 
						|
        });
 | 
						|
        it('matches two ETH/WETH (with fees) orders and sends profit to matcher', async () => {
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: otherTaker,
 | 
						|
                        amount: getRandomInteger(1, spread),
 | 
						|
                        feeData: NULL_BYTES,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            await weth
 | 
						|
                .deposit()
 | 
						|
                .awaitTransactionSuccessAsync({ from: buyOrder.maker, value: sellOrder.fees[0].amount });
 | 
						|
            const sellerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(sellOrder.maker);
 | 
						|
            const matcherEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(matcher);
 | 
						|
            await zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            const erc721Owner = await erc721Token.ownerOf(sellOrder.erc721TokenId).callAsync();
 | 
						|
            expect(erc721Owner).to.equal(buyOrder.maker);
 | 
						|
            const sellerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(sellOrder.maker);
 | 
						|
            const matcherEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(matcher);
 | 
						|
            expect(sellerEthBalanceAfter.minus(sellerEthBalanceBefore)).to.bignumber.equal(sellOrder.erc20TokenAmount);
 | 
						|
            expect(matcherEthBalanceAfter.minus(matcherEthBalanceBefore)).to.bignumber.equal(
 | 
						|
                spread.minus(sellOrder.fees[0].amount),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if sell order fees exceed spread', async () => {
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: otherTaker,
 | 
						|
                        amount: spread.plus(1),
 | 
						|
                        feeData: NULL_BYTES,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            await erc20Token.mint(buyOrder.maker, sellOrder.fees[0].amount).awaitTransactionSuccessAsync();
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.SellOrderFeesExceedSpreadError(sellOrder.fees[0].amount, spread),
 | 
						|
            );
 | 
						|
        });
 | 
						|
        it('reverts if sell order fees exceed spread (ETH/WETH)', async () => {
 | 
						|
            const spread = getRandomInteger(1, '1e18');
 | 
						|
            const sellOrder = getTestERC721Order({
 | 
						|
                direction: NFTOrder.TradeDirection.SellNFT,
 | 
						|
                erc20Token: ETH_TOKEN_ADDRESS,
 | 
						|
                fees: [
 | 
						|
                    {
 | 
						|
                        recipient: otherTaker,
 | 
						|
                        amount: spread.plus(1),
 | 
						|
                        feeData: NULL_BYTES,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
            });
 | 
						|
            await sendEtherAsync(zeroEx.address, sellOrder.fees[0].amount);
 | 
						|
            const sellSignature = await sellOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            const buyOrder = getTestERC721Order({
 | 
						|
                maker: otherMaker,
 | 
						|
                direction: NFTOrder.TradeDirection.BuyNFT,
 | 
						|
                erc20Token: weth.address,
 | 
						|
                erc721TokenId: sellOrder.erc721TokenId,
 | 
						|
                erc20TokenAmount: sellOrder.erc20TokenAmount.plus(spread),
 | 
						|
            });
 | 
						|
            const buySignature = await buyOrder.getSignatureWithProviderAsync(env.provider);
 | 
						|
            await mintAssetsAsync(buyOrder, sellOrder.erc721TokenId, sellOrder.maker);
 | 
						|
            await weth
 | 
						|
                .deposit()
 | 
						|
                .awaitTransactionSuccessAsync({ from: buyOrder.maker, value: sellOrder.fees[0].amount });
 | 
						|
            const tx = zeroEx
 | 
						|
                .matchERC721Orders(sellOrder, buyOrder, sellSignature, buySignature)
 | 
						|
                .awaitTransactionSuccessAsync({
 | 
						|
                    from: matcher,
 | 
						|
                });
 | 
						|
            return expect(tx).to.revertWith(
 | 
						|
                new RevertErrors.NFTOrders.SellOrderFeesExceedSpreadError(sellOrder.fees[0].amount, spread),
 | 
						|
            );
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 |