import {
    TokenFactory,
    ERC721Mock,
    ERC20Mock,
    EnglishAuction,
    DutchAuction,
    FixedPriceSale,
    ERC721ExchangeV0,
    ERC721RoyaltyMock,
    NFT721V1,
} from "./typechain";

import { domainSeparator, signAsk, signBid } from "./utils/sign-utils";
import { bid1, bid2 } from "./utils/bid_utils";
import { ethers } from "hardhat";
import { BigNumber, BigNumberish, Wallet, Contract } from "ethers";
import { expect, assert } from "chai";
import { defaultAbiCoder } from "ethers/lib/utils";
import { getBlockTimestamp, mine } from "./utils/blocks";

const { constants } = ethers;
const { AddressZero, HashZero } = constants;

ethers.utils.Logger.setLogLevel(ethers.utils.Logger.levels.ERROR); // turn off warnings

const setupTest = async () => {
    const signers = await ethers.getSigners();
    const [deployer, protocolVault, operationalVault, alice, bob, carol] = signers;

    const TokenFactoryContract = await ethers.getContractFactory("TokenFactory");
    const factory = (await TokenFactoryContract.deploy(
        protocolVault.address,
        25,
        operationalVault.address,
        5,
        "https://nft721.sushi.com/",
        "https://nft1155.sushi.com/"
    )) as TokenFactory;

    await factory.setDeployerWhitelisted(AddressZero, true);

    const NFT721Contract = await ethers.getContractFactory("NFT721V1");
    const nft721 = (await NFT721Contract.deploy()) as NFT721V1;

    const FixedPriceSaleContract = await ethers.getContractFactory("FixedPriceSale");
    const fixedPriceSale = (await FixedPriceSaleContract.deploy()) as FixedPriceSale;
    await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

    const EnglishAuctionContract = await ethers.getContractFactory("EnglishAuction");
    const englishAuction = (await EnglishAuctionContract.deploy()) as EnglishAuction;
    await factory.setStrategyWhitelisted(englishAuction.address, true);

    const DutchAuctionContract = await ethers.getContractFactory("DutchAuction");
    const dutchAuction = (await DutchAuctionContract.deploy()) as DutchAuction;
    await factory.setStrategyWhitelisted(dutchAuction.address, true);

    const ERC721ExchangeContract = await ethers.getContractFactory("ERC721ExchangeV0");
    const erc721Exchange = (await ERC721ExchangeContract.deploy(factory.address)) as ERC721ExchangeV0;

    const exchangeName = "ERC721Exchange";

    const ERC721MockContract = await ethers.getContractFactory("ERC721Mock");
    const erc721Mock0 = (await ERC721MockContract.deploy()) as ERC721Mock;
    const erc721Mock1 = (await ERC721MockContract.deploy()) as ERC721Mock;
    const erc721Mock2 = (await ERC721MockContract.deploy()) as ERC721Mock;

    const ERC20MockContract = await ethers.getContractFactory("ERC20Mock");
    const erc20Mock = (await ERC20MockContract.deploy()) as ERC20Mock;

    return {
        deployer,
        protocolVault,
        operationalVault,
        factory,
        erc721Exchange,
        fixedPriceSale,
        englishAuction,
        dutchAuction,
        alice,
        bob,
        carol,
        erc721Mock0,
        erc721Mock1,
        erc721Mock2,
        erc20Mock,
        exchangeName,
        nft721,
    };
};

async function getNFT721(factory: TokenFactory): Promise<NFT721V1> {
    let events: any = await factory.queryFilter(factory.filters.DeployNFT721AndMintBatch(), "latest");
    if (events.length == 0) events = await factory.queryFilter(factory.filters.DeployNFT721AndPark(), "latest");
    const NFT721Contract = await ethers.getContractFactory("NFT721V1");
    return (await NFT721Contract.attach(events[0].args[0])) as NFT721V1;
}

describe("ERC721Exchange", () => {
    beforeEach(async () => {
        await ethers.provider.send("hardhat_reset", []);
    });
    function getWallets() {
        const alice = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/3"
        ).connect(ethers.provider);
        const bob = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/4"
        ).connect(ethers.provider);
        const carol = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/5"
        ).connect(ethers.provider);
        const dan = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/7"
        ).connect(ethers.provider);
        const erin = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/8"
        ).connect(ethers.provider);
        const frank = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/9"
        ).connect(ethers.provider);

        const proxy = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/10"
        ).connect(ethers.provider);

        return { alice, bob, carol, dan, erin, frank, proxy };
    }
    function fees(price: BigNumberish, protocol: number, operator: number, royalty: number): BigNumberish[] {
        assert.isBelow(protocol, 255);
        assert.isBelow(operator, 255);
        assert.isBelow(royalty, 255);

        const fee: BigNumberish[] = [];

        const p = BigNumber.from(price).mul(protocol).div(1000);
        const o = BigNumber.from(price).mul(operator).div(1000);
        const r = BigNumber.from(price).mul(royalty).div(1000);
        const seller = BigNumber.from(price).sub(p.add(o).add(r));

        fee.push(p);
        fee.push(o);
        fee.push(r);
        fee.push(seller);

        return fee;
    }
    async function checkEvent(contract: Contract, eventName: string, args?: any[]) {
        const events: any = await contract.queryFilter(contract.filters[eventName](), "latest");
        expect(events[0].event).to.be.equal(eventName);

        if (args !== undefined) {
            const length = events[0].args.length;
            expect(length).to.be.gt(0);
            for (let i = 0; i < length; i++) {
                assert.isTrue(args[i] == events[0].args[i]);
            }
        }
    }

    it("should be that initial paremeters are set properly", async () => {
        const { factory, erc721Exchange } = await setupTest();

        expect(await erc721Exchange.DOMAIN_SEPARATOR()).to.be.equal(
            await domainSeparator(ethers.provider, "ERC721Exchange", erc721Exchange.address)
        );

        expect(await erc721Exchange.factory()).to.be.equal(factory.address);
    });

    it("should be that the cancel function works well", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol } = getWallets();

        await erc721Mock0.safeMint(alice.address, 0, []);
        await erc721Mock0.safeMint(bob.address, 1, []);
        await erc721Mock0.safeMint(carol.address, 2, []);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await expect(erc721Exchange.connect(bob).cancel(askOrder0.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(erc721Exchange.connect(alice).cancel(askOrder1.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        expect(await erc721Exchange.connect(alice).cancel(askOrder0.order));

        expect(await erc721Exchange.connect(bob).cancel(askOrder1.order));

        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            carol,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        await bid2(erc721Exchange, bob, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder2.hash, bob.address, 1, 100, AddressZero, AddressZero]);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(bob.address);

        await expect(erc721Exchange.connect(carol).cancel(askOrder2.order)).to.be.revertedWith("SHOYU: BID_EXISTS");
    });

    it("should be that the claim function can be called by anyone", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await erc721Mock0.safeMintBatch0([alice.address, bob.address, alice.address], [0, 1, 2], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock0.connect(bob).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.mint(dan.address, 10000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(erc721Exchange, carol, askOrder0.order, 1, 100, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(100);

        await bid2(erc721Exchange, dan, askOrder1.order, 1, 300, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[0]).to.be.equal(dan.address);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[2]).to.be.equal(300);

        await bid2(erc721Exchange, dan, askOrder2.order, 1, 500, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(dan.address);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[2]).to.be.equal(500);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));

        //nft0 : seller-Alice / buyer-Carol. Dan can claim.
        expect(await erc721Exchange.connect(dan).claim(askOrder0.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(9900);

        //nft1 : seller-Bob / buyer-Dan.  Seller Bob can claim.
        expect(await erc721Exchange.connect(bob).claim(askOrder1.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9700);

        //nft2 : seller-Alice / buyer-Dan.  Buyer Dan can claim.
        expect(await erc721Exchange.connect(dan).claim(askOrder2.order)).to.emit(erc721Exchange, "Claim");
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9200);
    });

    it("should be that the claim function will be reverted if BestBid is not exist", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder3.hash))[0]).to.be.equal(AddressZero);

        await expect(erc721Exchange.claim(askOrder3.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder2.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder1.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILURE");
        assert.isFalse(deadline0 < (await getBlockTimestamp()));
        assert.isFalse(await erc721Exchange.isCancelledOrClaimed(askOrder0.hash));
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));
        await expect(erc721Exchange.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILED_TO_TRANSFER_FUNDS");
        assert.isFalse(await erc721Exchange.isCancelledOrClaimed(askOrder0.hash));
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);
    });

    it("should be that fees are transfered properly", async () => {
        const {
            factory,
            erc721Exchange,
            erc721Mock0,
            erc721Mock1,
            exchangeName,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
            protocolVault,
            operationalVault,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline - 90,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [15000, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        const fees0 = fees(12345, 25, 5, 0);
        await expect(() => bid2(erc721Exchange, carol, askOrder1.order, 1, 12345, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, alice],
            [-12345, fees0[0], fees0[1], fees0[3]]
        );

        await erc20Mock.connect(dan).approve(proxy.address, 10000000);
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder2.hash,
            dan,
            1,
            31313,
            dan.address,
            AddressZero
        );
        const fees1 = fees(31313, 25, 5, 0);
        await expect(() => bid1(erc721Exchange, proxy, askOrder2.order, bidOrder2.order)).to.changeTokenBalances(
            erc20Mock,
            [dan, protocolVault, operationalVault, alice, frank, proxy],
            [-31313, fees1[0], fees1[1], fees1[3], 0, 0]
        );

        await factory.setProtocolFeeRecipient(erin.address);
        await factory.setOperationalFeeRecipient(frank.address);
        await factory.setOperationalFee(17);

        //erin 25/1000 frank 17/1000
        const fees2 = fees(15000, 25, 17, 0);
        await expect(() => bid2(erc721Exchange, dan, askOrder3.order, 1, 15000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-15000, fees2[0], fees2[1], fees2[3], 0, 0, 0]
        );

        await mine(100);

        const fees3 = fees(100, 25, 17, 0);
        assert.isTrue(deadline < (await getBlockTimestamp()));
        await expect(() => erc721Exchange.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, erin, frank, alice, protocolVault, operationalVault],
            [-100, fees3[0], fees3[1], fees3[3], 0, 0, 0]
        );

        await erc721Mock1.safeMint(alice.address, 0, []);
        await erc721Mock1.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline + 1000,
            defaultAbiCoder.encode(["uint256", "uint256"], [11000, currentTime])
        );

        //erin 25/1000 frank 17/1000
        const fees4 = fees(11000, 25, 17, 0);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await expect(() => bid2(erc721Exchange, dan, askOrder4.order, 1, 11000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-11000, fees4[0], fees4[1], fees4[3], 0, 0]
        );
    });

    it("should be that NFT721 tokens can't be traded on ERC721Exchange but the other ERC721 tokens can", async () => {
        const {
            factory,
            nft721,
            erc721Exchange,
            erc721Mock0,
            erc721Mock1,
            erc721Mock2,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], carol.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.deployNFT721AndMintBatch(alice.address, "Name2", "Symbol2", [0, 1, 2, 3], carol.address, 10);
        const nft721_1 = await getNFT721(factory);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            nft721_1.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isFalse(await erc721Exchange.canTrade(nft721_0.address));
        assert.isFalse(await erc721Exchange.canTrade(nft721_1.address));

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );
        await expect(bid2(erc721Exchange, bob, askOrder1.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );

        await erc721Mock0.safeMint(bob.address, 3, []);
        await erc721Mock1.safeMint(bob.address, 4, []);
        await erc721Mock2.safeMint(bob.address, 5, []);
        await erc721Mock0.connect(bob).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock1.connect(bob).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock2.connect(bob).setApprovalForAll(erc721Exchange.address, true);

        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);

        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock1.address,
            4,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder5 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock2.address,
            5,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isTrue(await erc721Exchange.canTrade(erc721Mock0.address));
        assert.isTrue(await erc721Exchange.canTrade(erc721Mock1.address));
        assert.isTrue(await erc721Exchange.canTrade(erc721Mock2.address));

        await bid2(erc721Exchange, carol, askOrder3.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder3.hash, carol.address, 1, 50, carol.address, AddressZero]);
        await bid2(erc721Exchange, carol, askOrder4.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder4.hash, carol.address, 1, 50, carol.address, AddressZero]);
        await bid2(erc721Exchange, carol, askOrder5.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder5.hash, carol.address, 1, 50, carol.address, AddressZero]);
    });

    it("should be that claimed orders can't be used again even if it's back to the initial owner", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            3,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 5,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        await bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder1.hash, carol.address, 1, 999, carol.address, AddressZero]);

        await bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);

        await erc20Mock.connect(dan).approve(proxy.address, 10000);
        const bidOrder3 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder3.hash,
            dan,
            1,
            101,
            dan.address,
            AddressZero
        );
        await bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder3.hash, dan.address, 1, 101, dan.address, AddressZero]);

        await expect(bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
        await expect(bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        const bidOrder3_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder3.hash,
            dan,
            1,
            101,
            dan.address,
            AddressZero
        );
        await expect(bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await erc721Mock0.connect(carol).transferFrom(carol.address, alice.address, 1);
        await erc721Mock0.connect(dan).transferFrom(dan.address, alice.address, 2);
        await erc721Mock0.connect(dan).transferFrom(dan.address, alice.address, 3);

        await expect(bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await expect(bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await expect(bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await mine(100);
        await erc721Exchange.claim(askOrder0.order);

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await erc721Mock0.connect(bob).transferFrom(bob.address, alice.address, 0);

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
    });

    it("should be that BestBid is replaced if someone bid with higher price", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await erc721Mock0.safeMint(alice.address, 0, []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(100);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await bid2(erc721Exchange, carol, askOrder0.order, 1, 110, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(110);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await expect(bid2(erc721Exchange, dan, askOrder0.order, 1, 110, AddressZero)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000);

        await mine(100);
        expect(await erc721Exchange.claim(askOrder0.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(10000 - 110);
    });

    it("should be that bid(Orders.Ask memory askOrder, Orders.Bid memory bidOrder) function works well", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            bob,
            1,
            101,
            AddressZero,
            AddressZero
        );
        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            carol,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder2.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, frank, askOrder1.order, bidOrder1.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder1.hash, carol.address, 1, 990, carol.address, AddressZero]);
        await bid1(erc721Exchange, bob, askOrder2.order, bidOrder2.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);
        await bid1(erc721Exchange, alice, askOrder0.order, bidOrder0.order);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 101, AddressZero, AddressZero]);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(101);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(15);

        const bidOrder0_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            carol,
            1,
            111,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, alice, askOrder0.order, bidOrder0_.order);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, carol.address, 1, 111, AddressZero, AddressZero]);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(111);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());
    });

    it("should be that fees and nft go to recipients if they are set in orders", async () => {
        const {
            operationalVault,
            protocolVault,
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            erin.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            frank.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, dan.address);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, dan.address, AddressZero]);

        const fees0 = fees(12345, 25, 5, 0);
        await expect(() => bid2(erc721Exchange, carol, askOrder1.order, 1, 12345, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, frank, alice],
            [-12345, fees0[0], fees0[1], fees0[3], 0]
        );
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(bob.address);

        await mine(100);

        const fees1 = fees(100, 25, 5, 0);
        await expect(() => erc721Exchange.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, erin, alice],
            [-100, fees1[0], fees1[1], fees1[3], 0]
        );
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(dan.address);
    });

    it("should be that token implementing EIP2981 give royalty to recipient when auction is finished", async () => {
        const {
            deployer,
            operationalVault,
            protocolVault,
            erc721Exchange,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();

        const ERC721RoyaltyMockContract = await ethers.getContractFactory("ERC721RoyaltyMock");
        const erc721RoyaltyMock0 = (await ERC721RoyaltyMockContract.deploy()) as ERC721RoyaltyMock;

        await erc721RoyaltyMock0.safeMintBatch1(alice.address, [0, 1, 20], []);
        await erc721RoyaltyMock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);

        expect((await erc721RoyaltyMock0.royaltyInfo(1, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc721RoyaltyMock0.royaltyInfo(1, 1000))[1]).to.be.equal(10);
        expect((await erc721RoyaltyMock0.royaltyInfo(20, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc721RoyaltyMock0.royaltyInfo(20, 1000))[1]).to.be.equal(100);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721RoyaltyMock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        const fees0 = fees(12345, 25, 5, 10);
        await expect(() =>
            bid2(erc721Exchange, carol, askOrder0.order, 1, 12345, carol.address)
        ).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, deployer, alice],
            [-12345, fees0[0], fees0[1], fees0[2], fees0[3]]
        );
        expect(await erc721RoyaltyMock0.ownerOf(1)).to.be.equal(carol.address);

        //protocol 25 operator 5 royalty 100
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721RoyaltyMock0.address,
            20,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [54321, currentTime])
        );

        const fees1 = fees(54321, 25, 5, 100);
        await expect(() => bid2(erc721Exchange, bob, askOrder1.order, 1, 54321, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, deployer, alice],
            [-54321, fees1[0], fees1[1], fees1[2], fees1[3]]
        );
        expect(await erc721RoyaltyMock0.ownerOf(20)).to.be.equal(bob.address);
    });

    it("should be that bid and claim functions work properly with proxy", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrderEwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderEwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderFwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            5,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderEwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderEwithP.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderEwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderEwithoutP.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await expect(bid1(erc721Exchange, proxy, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await bid1(erc721Exchange, bob, askOrderEwithoutP.order, bidOrderEwithoutP.order);
        await checkEvent(erc721Exchange, "Bid", [
            askOrderEwithoutP.hash,
            dan.address,
            1,
            101,
            AddressZero,
            AddressZero,
        ]);

        await mine(30);
        await expect(erc721Exchange.connect(carol).claim(askOrderEwithP.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(erc721Exchange, proxy, askOrderEwithP.order, bidOrderEwithP.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderEwithP.hash, dan.address, 1, 100, dan.address, AddressZero]);

        expect(await erc721Exchange.connect(carol).claim(askOrderEwithoutP.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(dan.address);
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderFwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithP.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithoutP.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderFwithP.order, bidOrderFwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc721Exchange, proxy, askOrderFwithP.order, bidOrderFwithP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithP.hash,
            erin.address,
            1,
            200,
            erin.address,
            AddressZero,
        ]);

        await bid1(erc721Exchange, bob, askOrderFwithoutP.order, bidOrderFwithoutP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithoutP.hash,
            erin.address,
            1,
            201,
            erin.address,
            AddressZero,
        ]);
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(erin.address);
        expect(await erc721Mock0.ownerOf(3)).to.be.equal(erin.address);

        const bidOrderDwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithP.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithoutP.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderDwithP.order, bidOrderDwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc721Exchange, proxy, askOrderDwithP.order, bidOrderDwithP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithP.hash,
            frank.address,
            1,
            990,
            frank.address,
            AddressZero,
        ]);

        await bid1(erc721Exchange, bob, askOrderDwithoutP.order, bidOrderDwithoutP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithoutP.hash,
            frank.address,
            1,
            980,
            frank.address,
            AddressZero,
        ]);

        expect(await erc721Mock0.ownerOf(4)).to.be.equal(frank.address);
        expect(await erc721Mock0.ownerOf(5)).to.be.equal(frank.address);

        const askOrderFwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            6,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            7,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            8,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            9,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        await mine(100);
        expect(await getBlockTimestamp()).gt(deadline);
        const bidOrderFwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithP1.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithoutP1.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, proxy, askOrderFwithP1.order, bidOrderFwithP1.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithP1.hash,
            erin.address,
            1,
            200,
            erin.address,
            AddressZero,
        ]);
        await expect(bid1(erc721Exchange, bob, askOrderFwithoutP1.order, bidOrderFwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        const bidOrderDwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithP1.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithoutP1.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, proxy, askOrderDwithP1.order, bidOrderDwithP1.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithP1.hash,
            frank.address,
            1,
            990,
            frank.address,
            AddressZero,
        ]);
        await expect(bid1(erc721Exchange, bob, askOrderDwithoutP1.order, bidOrderDwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    });

    it("should be that bid and claim functions work properly with _bidHashes", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrderE0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderE1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderF0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );

        const askOrderD0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderE0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE0.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderE1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);
        await expect(erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderE0.hash, dan.address, bidOrderE0.hash))
            .to.emit(erc721Exchange, "UpdateApprovedBidHash")
            .withArgs(proxy.address, askOrderE0.hash, dan.address, bidOrderE0.hash);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(
            bidOrderE0.hash
        );

        await expect(
            bid2(
                erc721Exchange,
                dan,
                askOrderE0.order,
                bidOrderE0.order.amount,
                bidOrderE0.order.price,
                bidOrderE0.order.recipient
            )
        ).to.be.revertedWith("SHOYU: FORBIDDEN");

        await mine(30);
        await expect(erc721Exchange.connect(carol).claim(askOrderE0.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(erc721Exchange, frank, askOrderE0.order, bidOrderE0.order); //frank can call
        await checkEvent(erc721Exchange, "Claim", [askOrderE0.hash, dan.address, 1, 100, dan.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);

        const bidOrderE1_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        );
        await erc721Exchange.connect(dan).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1_.hash); //make fake hash for abusing
        expect(await erc721Exchange.approvedBidHash(dan.address, askOrderE1.hash, dan.address)).to.be.equal(
            bidOrderE1_.hash
        );
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        await expect(bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1_.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        expect(await getBlockTimestamp()).to.be.gt(askOrderE1.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(
            bidOrderE1.hash
        );

        const bidOrderE1__ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        ); //change conditions after hash approved
        await expect(bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1__.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        await bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderE1.hash, dan.address, 1, 101, dan.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderF0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderF0.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await mine(100);
        expect(await getBlockTimestamp()).to.be.gt(askOrderF0.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderF0.hash, erin.address, bidOrderF0.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            bidOrderF0.hash
        );

        await bid1(erc721Exchange, erin, askOrderF0.order, bidOrderF0.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderF0.hash, erin.address, 1, 200, erin.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            HashZero
        );
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(erin.address);

        const bidOrderD0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderD0.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        expect(await getBlockTimestamp()).to.be.gt(askOrderD0.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderD0.hash, frank.address, bidOrderD0.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            bidOrderD0.hash
        );

        await bid1(erc721Exchange, frank, askOrderD0.order, bidOrderD0.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderD0.hash, frank.address, 1, 990, frank.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            HashZero
        );
        expect(await erc721Mock0.ownerOf(4)).to.be.equal(frank.address);
    });

    it("should be that an auction with fixed price sale allows bids only between startTime and deadline", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, dan } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 50])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 80])
        );

        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    })

    it("should be that an auction with english auction allows bids only between startTime and deadline", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
        } = await setupTest();

        const { alice, dan } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 50])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 80])
        );

        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    })
});