Ethers.js – Understanding the Function of Test Code Lines in Hardhat

ethers.jshardhatunittesting

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

const newNFTToken = await mintAndListNFT(tokenURI, auctionPrice);

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 the newNFTToken variable. At this point the marketplace contract owns the NFT.

await nftMarket.connect(buyerAddress).createMarketSale(newNFTToken, {value: auctionPrice});

This makes buyerAddress buy the NFT for auctionPrice wei. At this point buyerAddress owns the NFT.

await expect(nftMarket.resellToken(nftMarket, auctionPrice, {value: listingPrice})).to.be.revertedWith("You are not the owner of the token!");

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 owner buyerAddress should be able to list it for sale.

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!");

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.

Related Topic