Skip to content

Instantly share code, notes, and snippets.

@minhquanym
Last active June 17, 2022 15:03

Revisions

  1. minhquanym revised this gist Jun 17, 2022. 1 changed file with 11 additions and 0 deletions.
    11 changes: 11 additions & 0 deletions matchOrders.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,14 @@
    // buy order
    // nft: [1, 2, 3]
    // constraints[0] = 2 => minimum tokens want to buy

    // sell order
    // nft: [1, 2, 3]
    // constraints[0] = 2 => maximum tokens want to sell

    // should not able to match
    // because seller only want to sell maximum 2 tokens

    const { expect } = require('chai');
    const { ethers, network } = require('hardhat');
    const { deployContract, nowSeconds, NULL_ADDRESS } = require('../tasks/utils');
  2. minhquanym created this gist Jun 17, 2022.
    254 changes: 254 additions & 0 deletions matchOrders.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,254 @@
    const { expect } = require('chai');
    const { ethers, network } = require('hardhat');
    const { deployContract, nowSeconds, NULL_ADDRESS } = require('../tasks/utils');
    const { prepareOBOrder, getCurrentSignedOrderPrice, approveERC20, getCurrentOrderPrice } = require('../helpers/orders');
    const { erc721Abi } = require('../abi/erc721');

    describe('check matchOrders construct', async () => {
    let signers,
    signer1,
    signer2,
    signer3,
    token,
    infinityExchange,
    mock721Contract1,
    mock721Contract2,
    mock721Contract3,
    obComplication;

    const buyOrders = [];
    const sellOrders = [];

    let signer1Balance = toBN(0);
    let signer2Balance = toBN(0);
    let totalProtocolFees = toBN(0);
    let orderNonce = 0;

    const FEE_BPS = 250;
    const UNIT = toBN(1e18);
    const INITIAL_SUPPLY = toBN(1_000_000).mul(UNIT);

    const numNFTsToTransfer = 50;

    function toBN(val) {
    return ethers.BigNumber.from(val.toString());
    }

    before("setup", async () => {
    // signers
    signers = await ethers.getSigners();
    signer1 = signers[0];
    signer2 = signers[1];
    signer3 = signers[2];

    // token
    token = await deployContract('MockERC20', await ethers.getContractFactory('MockERC20'), signers[0]);

    // NFT contracts
    mock721Contract1 = await deployContract('MockERC721', await ethers.getContractFactory('MockERC721'), signer1, [
    'Mock NFT 1',
    'MCKNFT1'
    ]);
    mock721Contract2 = await deployContract('MockERC721', await ethers.getContractFactory('MockERC721'), signer1, [
    'Mock NFT 2',
    'MCKNFT2'
    ]);
    mock721Contract3 = await deployContract('MockERC721', await ethers.getContractFactory('MockERC721'), signer1, [
    'Mock NFT 3',
    'MCKNFT3'
    ]);

    // Exchange
    infinityExchange = await deployContract(
    'InfinityExchange',
    await ethers.getContractFactory('InfinityExchange'),
    signer1,
    [token.address, signer3.address]
    );

    // OB complication
    obComplication = await deployContract(
    'InfinityOrderBookComplication',
    await ethers.getContractFactory('InfinityOrderBookComplication'),
    signer1
    );

    // add currencies to registry
    await infinityExchange.addCurrency(token.address);
    await infinityExchange.addCurrency(NULL_ADDRESS);

    // add complications to registry
    await infinityExchange.addComplication(obComplication.address);

    // send assets
    await token.transfer(signer2.address, INITIAL_SUPPLY.div(2).toString());
    for (let i = 0; i < numNFTsToTransfer; i++) {
    await mock721Contract1.transferFrom(signer1.address, signer2.address, i);
    await mock721Contract2.transferFrom(signer1.address, signer2.address, i);
    await mock721Contract3.transferFrom(signer1.address, signer2.address, i);
    }
    });

    // buy order
    // nft: [1, 2, 3]
    // constraints[0] = 2 => minimum tokens want to buy
    it('create buy order', async () => {
    const user = {
    address: signer1.address
    };
    const chainId = network.config.chainId ?? 31337;
    const nfts = [
    {
    collection: mock721Contract1.address,
    tokens: [
    { tokenId: 1, numTokens: 1 },
    { tokenId: 2, numTokens: 1 },
    { tokenId: 3, numTokens: 1 }
    ]
    }
    ];
    const execParams = { complicationAddress: obComplication.address, currencyAddress: token.address };
    const extraParams = {};
    const nonce = ++orderNonce;
    const orderId = ethers.utils.solidityKeccak256(['address', 'uint256', 'uint256'], [user.address, nonce, chainId]);
    const order = {
    id: orderId,
    chainId,
    isSellOrder: false,
    signerAddress: user.address,
    numItems: toBN(2),
    startPrice: ethers.utils.parseEther('1'),
    endPrice: ethers.utils.parseEther('1'),
    startTime: nowSeconds(),
    endTime: nowSeconds().add(24 * 60 * 60),
    nonce,
    nfts,
    execParams,
    extraParams
    };
    const signedOrder = await prepareOBOrder(user, chainId, signer1, order, infinityExchange);
    expect(signedOrder).to.not.be.undefined;
    buyOrders.push(signedOrder);
    });

    // sell order
    // nft: [1, 2, 3]
    // constraints[0] = 2 => maximum tokens want to sell
    it('create sell order', async function () {
    const user = {
    address: signer2.address
    };
    const chainId = network.config.chainId ?? 31337;
    const nfts = [
    {
    collection: mock721Contract1.address,
    tokens: [
    { tokenId: 1, numTokens: 1 },
    { tokenId: 2, numTokens: 1 },
    { tokenId: 3, numTokens: 1 }
    ]
    }
    ];
    const execParams = { complicationAddress: obComplication.address, currencyAddress: token.address };
    const extraParams = {};
    const nonce = ++orderNonce;
    const orderId = ethers.utils.solidityKeccak256(['address', 'uint256', 'uint256'], [user.address, nonce, chainId]);
    const order = {
    id: orderId,
    chainId,
    isSellOrder: true,
    signerAddress: user.address,
    numItems: toBN(2),
    startPrice: ethers.utils.parseEther('1'),
    endPrice: ethers.utils.parseEther('1'),
    startTime: nowSeconds(),
    endTime: nowSeconds().add(24 * 60 * 60),
    nonce,
    nfts,
    execParams,
    extraParams
    };

    // approve currency (required for automatic execution)
    const salePrice = getCurrentOrderPrice(order);
    await approveERC20(user.address, execParams.currencyAddress, salePrice, signer2, infinityExchange.address);

    const signedOrder = await prepareOBOrder(user, chainId, signer2, order, infinityExchange);
    expect(signedOrder).to.not.be.undefined;
    sellOrders.push(signedOrder);
    });

    // should not able to match
    // because seller only want to sell maximum 2 tokens
    it('match orders', async function () {
    const buyOrder = buyOrders[0];
    const sellOrder = sellOrders[0];
    const constructedOrder = sellOrder;
    const nfts = constructedOrder.nfts;

    // owners before sale
    for (const item of nfts) {
    const collection = item.collection;
    const contract = new ethers.Contract(collection, erc721Abi, signer1);
    for (const token of item.tokens) {
    const tokenId = token.tokenId;
    expect(await contract.ownerOf(tokenId)).to.equal(signer2.address);
    }
    }

    // sale price
    const salePrice = getCurrentSignedOrderPrice(constructedOrder);

    // balance before sale
    expect(await token.balanceOf(signer1.address)).to.equal(INITIAL_SUPPLY.div(2));
    expect(await token.balanceOf(signer2.address)).to.equal(INITIAL_SUPPLY.div(2));

    // estimate gas
    const numTokens = constructedOrder.nfts.reduce((acc, nft) => {
    return (
    acc +
    nft.tokens.reduce((acc, token) => {
    return acc + token.numTokens;
    }, 0)
    );
    }, 0);
    console.log('total numTokens in order', numTokens);
    const gasEstimate = await infinityExchange
    .connect(signer3)
    .estimateGas.matchOrders([sellOrder], [buyOrder], [constructedOrder.nfts]);
    console.log('gasEstimate', gasEstimate.toNumber());
    console.log('gasEstimate per token', gasEstimate / numTokens);

    // initiate exchange by 3rd party
    await infinityExchange.connect(signer3).matchOrders([sellOrder], [buyOrder], [constructedOrder.nfts]);

    // owners after sale
    for (const item of nfts) {
    const collection = item.collection;
    const contract = new ethers.Contract(collection, erc721Abi, signer1);
    for (const token of item.tokens) {
    const tokenId = token.tokenId;
    expect(await contract.ownerOf(tokenId)).to.equal(signer1.address);
    }
    }

    // balance after sale
    const fee = salePrice.mul(FEE_BPS).div(10000);
    signer1Balance = INITIAL_SUPPLY.div(2).sub(salePrice);
    signer2Balance = INITIAL_SUPPLY.div(2).add(salePrice.sub(fee));
    expect(await token.balanceOf(signer2.address)).to.equal(signer2Balance);

    const signer1TokenBalance = await token.balanceOf(signer1.address);
    const gasRefund = signer1Balance.sub(signer1TokenBalance);

    totalProtocolFees = totalProtocolFees.add(fee).add(gasRefund);
    expect(await token.balanceOf(infinityExchange.address)).to.equal(totalProtocolFees);

    const buyerBalance1 = parseFloat(ethers.utils.formatEther(signer1TokenBalance));
    const buyerBalance2 = parseFloat(ethers.utils.formatEther(signer1Balance));
    expect(buyerBalance1).to.be.lessThan(buyerBalance2); // less than because of the gas refund

    // update
    signer1Balance = signer1TokenBalance;
    });
    });