Test file:
const {expect} = require("chai");
const {ethers} = require("hardhat");
describe("NFT Marketplace", function () {
let NFTMarket;
let nftMarket;
let listingPrice;
let contractOwner;
let buyerAddress;
let nftMarketAddress
// returns a BigNumber representation of value, parsed with
// digits (if it's a number) or from the unit specified (if it's a string)
const auctionPrice = ethers.utils.parseUnits("100", "ether")
// hooks that perform before each test case
beforeEach(async () => { // get contract that we're targeting, so we can deploy or call the functions in that Contract
NFTMarket = await ethers.getContractFactory("NFTMarketplace");
// create a transaction to deploy the transaction and sends it to the network
// using the contract Signer, and returning a Promise to resolve to a Contract
nftMarket = await NFTMarket.deploy();
// return a Promise which will resolve once the contract is deployed
// or reject if there was an error during deployment
await nftMarket.deployed();
nftMarketAddress = nftMarket.address;
[contractOwner, buyerAddress] = await ethers.getSigners(); // get public address of the user wallet
listingPrice = await nftMarket.getListingPrice();
listingPrice = listingPrice.toString();
})
// mint and list NFT
const mintAndListNFT = async (tokenURI, auctionPrice) => {
const transaction = await nftMarket.createToken(tokenURI, auctionPrice, {value: listingPrice});
const receipt = await transaction.wait();
const tokenId = receipt.events[0].args.tokenId;
return tokenId;
}
describe("Resale of a marketplace item", async () => {
const tokenURI = "https://dummy-token.url/"; // test with a dummy token URI
const newNFTToken = await mintAndListNFT(tokenURI, auctionPrice);
await nftMarket.connect(buyerAddress).createMarketSale(newNFTToken, {value: auctionPrice});
await expect(nftMarket.resellToken(nftMarket, auctionPrice, {value: listingPrice})).to.be.revertedWith("You are not the owner of the token!");
await expect(nftMarket.connect(buyerAddress).resellToken(newNFTToken, auctionPrice, {value: 0})).to.be.rejectedWith("The amount sold does not equal the original listing price of the token!");
})
Contract file:
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract NFTMarketplace is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIDs; // total number of items created
Counters.Counter private _itemsSold; // total number of items sold
uint256 listingPrice = 0.001 ether; // price to list NFT on marketplace
address payable owner; // owner of the smart contract
constructor() ERC721("Metaverse Tokens", "META") {
owner == payable(msg.sender);
}
struct MarketItem {
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
mapping(uint256 => MarketItem) private idToMarketItem;
event MarketItemCreated (uint256 indexed tokenId, address seller, address owner, uint256 price, bool sold);
// return the listing price of the NFT
function getListingPrice() public view returns(uint256) {
return listingPrice;
}
// update the listing price
function updatedListingPrice(uint _listingPrice) public payable{
require(owner == msg.sender, "You are not the owner!");
listingPrice = _listingPrice;
}
// create a NFT in the market
function createMarketItem(uint256 tokenId, uint256 price) private {
require(price > 0, "You must list an item with price more than 0!");
require(msg.value == listingPrice, "The amount of ether sent in the transaction does not equal the listing price!");
// seller is the msg.sender and owner is the address(this)
idToMarketItem[tokenId] = MarketItem(tokenId, payable(msg.sender), payable(address(this)), price, false);
_transfer(msg.sender, address(this), tokenId);
emit MarketItemCreated (tokenId, msg.sender, address(this), price, true);
}
// mints a token and list it in the market
function createToken(string memory tokenURI, uint256 price) public payable returns(uint) {
_tokenIDs.increment();
uint256 newTokenId = _tokenIDs.current();
_mint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
createMarketItem(newTokenId, price);
return newTokenId;
}
// creating the sale of a marketplace item
// transfers ownership of the item and funds between parties
function createMarketSale(uint256 tokenId) public payable {
uint price = idToMarketItem[tokenId].price;
address seller = idToMarketItem[tokenId].seller;
require(msg.value == price, "The amount of ethers sent does not equal to the price of the item!");
idToMarketItem[tokenId].owner = payable(msg.sender); // transfer ownership
idToMarketItem[tokenId].seller = payable(address(0));
idToMarketItem[tokenId].sold = true;
_itemsSold.increment();
_transfer(address(this), msg.sender, tokenId);
payable(owner).transfer(listingPrice);
payable(seller).transfer(msg.value);
}
// allows users to resell a token they have purchased
function resellToken(uint256 tokenId, uint256 price) public payable {
require(idToMarketItem[tokenId].owner == msg.sender, "You are not the owner of the token!");
require(msg.value == listingPrice, "The amount sold does not equal the original listing price of the token!");
idToMarketItem[tokenId].sold = false;
idToMarketItem[tokenId].seller = payable(msg.sender); // msg.sender refers to address where the contract is being called from
idToMarketItem[tokenId].owner = payable(address(this)); // address(this) refers to the address of the instance where the call is being made
idToMarketItem[tokenId].price = price;
_itemsSold.decrement();
_transfer(msg.sender, address(this), tokenId);
}
}
What do these lines of code do specifically under the test case for "Resale of a marketplace item"?
- const newNFTToken = await mintAndListNFT(tokenURI, auctionPrice);
- await nftMarket.connect(buyerAddress).createMarketSale(newNFTToken, {value: auctionPrice});
- await expect(nftMarket.resellToken(nftMarket, auctionPrice, {value: listingPrice})).to.be.revertedWith("You are not the owner of the token!");
- await expect(nftMarket.connect(buyerAddress).resellToken(newNFTToken, auctionPrice, {value: 0})).to.be.rejectedWith("The amount sold does not equal the original listing price of the token!");
Any help is appreciated. Thank you.
Best Answer
This mints a new NFT and lists it for sale at the price of
auctionPrice
. The token ID of this new NFT is stored in thenewNFTToken
variable. At this point the marketplace contract owns the NFT.This makes
buyerAddress
buy the NFT forauctionPrice
wei. At this pointbuyerAddress
owns the NFT.I don't quite understand this one. From the contract it seems like the
resellToken
function should receive a tokenID as parameter:resellToken(uint256 tokenId, uint256 price)
I think it should simulate someone other than
buyerAddress
trying to list the NFT for sale, which should fail because only its ownerbuyerAddress
should be able to list it for sale.This tries to simulate the owner of the NFT (
buyerAddress
) trying to list the NFT for sale without paying the listing price, which should fail.