Skip to content

Instantly share code, notes, and snippets.

@minhquanym
Last active June 17, 2022 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save minhquanym/a95c8652de8431c5d1d24aa4076a1878 to your computer and use it in GitHub Desktop.
Save minhquanym/a95c8652de8431c5d1d24aa4076a1878 to your computer and use it in GitHub Desktop.
// 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');
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;
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment