1

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.

2
  • Hi, thanks for using Stack Exchange ETH. This would qualify as a "debug me" type question - which isn't helpful to other users on the stack exchange forum, and usually removed from Stack Exchange. Stack Exchange is meant to be "living documentation," so questions that aren't specific to one thing are excluded. Could you please rephrase your question to one specific thing and ask your additional questions in a new question? Thank you. Dec 17, 2022 at 20:19
  • Just to elaborate on Patrick's point of view: you should consider why you don't understand certain line of code, explain which underlying concept you don't understand and/or tell if something behaves differently than you expected. Narrow the problem down to its most basic elements (such as 'what does command 'doSomething' do in Solidity?'). You'll most likely find out that your question gets a lot more specific and better answerable Dec 17, 2022 at 21:04

4 Answers 4

1
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.

1

I try to describe these lines easiest as I can:

Const newNFTToken = await mintAndListNFT(tokenURI, auctionPrice);

this line mints a Token with a specific auction price and tokenURI then lists it in the auction list.

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

here,buyer sends transaction to buy NFTToken with a specific value "auctionPrice"

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

here we expected the transaction to be reverted with this error ""You are not the owner of the token!", this is a test for the first "require" for the function resellToken.

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 line is the same, here is the testing of the second "require" function resellToken, we send this transaction with specific values : "newNFTToken, auctionPrice, and value" and expected to be reverted.

1
const newNFTToken = await mintAndListNFT(tokenURI, auctionPrice);

This JS function sends a transaction invoking the createToken() Solidity function, and then waits for the transaction to be included in a block. The code expects that the transaction was not reverted and produced at least one event with topic name tokenId. This value is then returned as newNFTToken.

If you're using an emulator, transactions are usually included in a block right away. But if you're testing against a public network, this might take tens of seconds.


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

Sends a transaction from buyerAddress to nftMarket contract, invoking the function createMarketSale(), passing along auctionPrice amount of wei.

It doesn't wait for the transaction to be included in a block - just to be broadcasted. Transactions on local emulators are usually broadcasted and included in a block at the same time, so this line should work locally. But if you were testing against a public network (without automining), any following lines might fail if they're expecting some state that hasn't happened yet (until the transaction is mined/validated).


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

Sends a transaction and expect it to throw an uncaught exception with the predefined error message. Uncaught exceptions cause the transaction to revert.

A reverted transaction is inlcuded in a block, doesn't produce any state changes (e.g. if a value is set during the transaction execution, it's unset when the transaction is reverted), but still costs you gas fees up to the point when it's reverted.

The Solidity code for this function shows that the transaction reverts with the specified message if the address executing the function doesn't equal to the value of idToMarketItem[tokenId].owner.


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

Same as above.

This line tests the case where the msg.value (specified as value: 0 in your JS snippet) doesn't equal listingPrice in Solidity code (hardcoded to 0.001 ether).

1
+50

1-

     // mint and list NFT
    const mintAndListNFT = async (tokenURI, auctionPrice) => {
        // mints new token
        const transaction = await nftMarket.createToken(tokenURI, auctionPrice, {value: listingPrice});
        // wait() waits till the transaction is mined then you will get the receipt
        const receipt = await transaction.wait();
        // once you received the receipt, read the tokenId
        const tokenId = receipt.events[0].args.tokenId;
        return tokenId;
    }

2-

// instead of connecting to metamask we are connecting to an address
// buyerAddress is a signer: ` [contractOwner, buyerAddress] = await ethers.getSigners();`
// A Signer is a class which (usually) in some way directly or indirectly has access to a private key, which can sign messages and transactions to authorize the network to charge your account ether to perform operations.
await nftMarket.connect(buyerAddress).createMarketSale(newNFTToken, {value: auctionPrice});

3-

// in contract resellToken has this `require(idToMarketItem[tokenId].owner == msg.sender`
// that means only owner of the token can call this function. caller is `msg.sender`
// if anyone except owner of the token calls this function, transaction should be reverted with the "You are not the owner of the token!" message
await expect(nftMarket.resellToken(nftMarket, auctionPrice, {value: listingPrice})).to.be.revertedWith("You are not the owner of the token!");

4-

// before we call this function        ` await nftMarket.connect(buyerAddress).createMarketSale(newNFTToken, {value: auctionPrice});`
// that means buyer bought the tokenId and it is the owner
// since it is the owner, it passes the first `require` statement
// now we are testing the second `require` statement
// In cotract we have this requirement `require(msg.value == listingPrice, "The amount sold does not equal the original listing price of the token!");`
// value should be listingPrice but we are passing 0. so transaction should be reverted with the "The amount sold does not equal the original listing price of the token!" message 

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.