-
-
Save minhquanym/a95c8652de8431c5d1d24aa4076a1878 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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