0

I have a function that lists an NFT on a marketplace for sale. In the process, it keeps records by saving data in a struct inside nested maps represented by the NFT address and token ID. I am running into the Stack too deep error on the following line in the below piece of code.

s_listings[nftAddress][tokenId] = Listing(price, msg.sender, _isPhysicalItem, Status.AWAITING_PURCHASER);

The relevant code in the smart contract is as follows

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

error PriceNotMet(address nftAddress, uint256 tokenId, uint256 price); error ItemNotForSale(address nftAddress, uint256 tokenId); error NotListed(address nftAddress, uint256 tokenId); error Reserved(address nftAddress, uint256 tokenId); error AlreadyListed(address nftAddress, uint256 tokenId); error NoProceeds(); error NotOwner(); error NotApprovedForMarketplace(); error PriceMustBeAboveZero();

contract NftMarketplace is ReentrancyGuard, IERC721Receiver {

enum Status { AWAITING_PURCHASER, AWAITING_DELIVERY, COMPLETE }

struct Listing {
    uint256 price;
    address seller;
    bool isPhysicalItem;
    Status currStatus;
}

struct escrowRecords {
    address nftAddress;
    uint256 tokenId;
    address purchaser;
    address seller;
    uint256 price;
}

// Escrow configuratio



mapping(address => mapping(uint256 => escrowRecords)) private escrowDebt;

// Events

event ItemListed(
    address indexed seller,
    address indexed nftAddress,
    uint256 indexed tokenId,
    uint256 price
);

event ItemCanceled(
    address indexed seller,
    address indexed nftAddress,
    uint256 indexed tokenId
);

event ItemBought(
    address indexed buyer,
    address indexed nftAddress,
    uint256 indexed tokenId,
    uint256 price
);

// State Variables
mapping(address => mapping(uint256 => Listing)) private s_listings;
mapping(address => uint256) private s_proceeds;

// Function modifiers
modifier notListed(
    address nftAddress,
    uint256 tokenId,
    address owner
) {
    Listing memory listing = s_listings[nftAddress][tokenId];
    if (listing.price > 0) {
        revert AlreadyListed(nftAddress, tokenId);
    }
    _;
}

modifier isOwner(
    address nftAddress,
    uint256 tokenId,
    address spender
) {
    IERC721 nft = IERC721(nftAddress);
    address owner = nft.ownerOf(tokenId);
    if (spender != owner) {
        revert NotOwner();
    }
    _;
}

modifier isListed(address nftAddress, uint256 tokenId) {
    Listing memory listing = s_listings[nftAddress][tokenId];
    if (listing.price <= 0) {
        revert NotListed(nftAddress, tokenId);
    }
    _;
}

modifier isReserved(address nftAddress, uint256 tokenId) {
    Listing memory listing = s_listings[nftAddress][tokenId];
    if (listing.currStatus != Status.AWAITING_PURCHASER) {
        revert Reserved(nftAddress, tokenId);
    }
    _;
}

// main functions

function listItem(
    address nftAddress,
    uint256 tokenId,
    uint256 price,
    bool _isPhysicalItem
)
    external
    notListed(nftAddress, tokenId, msg.sender)
    isOwner(nftAddress, tokenId, msg.sender)
{
    if (price <= 0) {
        revert PriceMustBeAboveZero();
    }
    IERC721 nft = IERC721(nftAddress);
    if (nft.getApproved(tokenId) != address(this)) {
        revert NotApprovedForMarketplace();
    }
    s_listings[nftAddress][tokenId] = Listing(price, msg.sender, _isPhysicalItem, Status.AWAITING_PURCHASER);
    emit ItemListed(msg.sender, nftAddress, tokenId, price);
}

function cancelListing(address nftAddress, uint256 tokenId)
    external
    isOwner(nftAddress, tokenId, msg.sender)
    isListed(nftAddress, tokenId)
    isReserved(nftAddress, tokenId)
{

    delete (s_listings[nftAddress][tokenId]);
    emit ItemCanceled(msg.sender, nftAddress, tokenId);
}

// function required to send ERC721 to this smart contract

function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) 
public
override 
returns (bytes4) {
        return this.onERC721Received.selector;
    }

function buyItem(address _nftAddress, uint256 _tokenId, address _owner)
    external
    payable
    isListed(_nftAddress, _tokenId)
    isReserved(_nftAddress, _tokenId)
    nonReentrant
{
    Listing memory listedItem = s_listings[_nftAddress][_tokenId];
    if (msg.value < listedItem.price) {
        revert PriceNotMet(_nftAddress, _tokenId, listedItem.price);
    }
    if (listedItem.isPhysicalItem == true) {
        IERC721 nft = IERC721(_nftAddress);

        listedItem.currStatus = Status.AWAITING_DELIVERY;
        address _seller = listedItem.seller;

        escrowDebt[_owner][_tokenId] = escrowRecords(_nftAddress, _tokenId, msg.sender, _seller, msg.value);
        // as function is set payable i should not need the below line
        // address(this) += msg.value;
        IERC721(_nftAddress).safeTransferFrom(listedItem.seller, address(this), _tokenId);
    } else {
        s_proceeds[listedItem.seller] += msg.value;
        delete (s_listings[_nftAddress][_tokenId]);
        IERC721(_nftAddress).safeTransferFrom(listedItem.seller, msg.sender, _tokenId);
        emit ItemBought(msg.sender, _nftAddress, _tokenId, listedItem.price);
    }


}

function confirmDelivery(address _nftAddress, uint256 _tokenId) 
    external
    isListed(_nftAddress, _tokenId)
    isReserved(_nftAddress, _tokenId)
    nonReentrant
{
    escrowRecords memory escrowItem = escrowDebt[_nftAddress][_tokenId];
    require(msg.sender == escrowItem.purchaser);
    s_proceeds[escrowItem.seller] += escrowItem.price;
    delete (s_listings[_nftAddress][_tokenId]);
    IERC721(_nftAddress).safeTransferFrom(address(this), escrowItem.purchaser, _tokenId);
    emit ItemBought(escrowItem.purchaser, _nftAddress, _tokenId, escrowItem.price);
    delete (escrowDebt[_nftAddress][_tokenId]);

}


function updateListing(
    address nftAddress,
    uint256 tokenId,
    uint256 newPrice
)
    external
    isListed(nftAddress, tokenId)
    nonReentrant
    isOwner(nftAddress, tokenId, msg.sender)
    isReserved(nftAddress, tokenId)
{
    if (newPrice == 0) {
        revert PriceMustBeAboveZero();
    }

    s_listings[nftAddress][tokenId].price = newPrice;
    emit ItemListed(msg.sender, nftAddress, tokenId, newPrice);
}

function withdrawProceeds() external {
    uint256 proceeds = s_proceeds[msg.sender];
    if (proceeds <= 0) {
        revert NoProceeds();
    }
    s_proceeds[msg.sender] = 0;

    (bool success, ) = payable(msg.sender).call{value: proceeds}("");
    require(success, "Transfer failed");
}

function getListing(address nftAddress, uint256 tokenId)
    external
    view
    returns (Listing memory)
{
    return s_listings[nftAddress][tokenId];
}

function getProceeds(address seller) external view returns (uint256) {
    return s_proceeds[seller];
}

}

Any help on this issue would be great

*** Edit ***

I have added the full block of code as Ismael eluded that the snippet compiled fine and the error bust be somewhere else

Weivy
  • 1
  • 1
  • The contract in the question compiles just fine. It is likely the error is in some part of the code not included in the question. – Ismael Jun 18 '22 at 22:44
  • @PaulRazvanBerg Unfortunately not. I somewhat understand what the error means but finding it hard to relate other solutions to my code – Weivy Jun 19 '22 at 08:28
  • Hi @Ismael thanks for checking. I have edited my question to include the full contract. Maybe it is somewhere else as you suggested. Both Hardhat and Remix compiling attempts pointed to the line of code I separated above – Weivy Jun 19 '22 at 08:31
  • @Weivy The compiler is right you have too many local variables (some of them are created by the modifiers). You could fix by inlining one of the modifiers. Other solution is enclosing IERC721 nft; in its own scope, for example surrounding it in a {} block. – Ismael Jun 20 '22 at 05:03
  • @Ismael thank you, the scoping method worked! – Weivy Jun 21 '22 at 14:27
  • I didn't know about this case, I'll try to creating a small example with several modifiers and add them to the original "Stack too deep" so it can help othres. – Ismael Jun 21 '22 at 14:52

0 Answers0