ERC721无效Token ID及NFT铸造后Owner异常问题求助
NFT Marketplace合约异常排查与修复
问题描述
开发NFT Marketplace时,采用两个合约实现功能:调用NFT合约铸造Token后,调用setApprovalForAll()授权市场合约转移该Token。但出现异常:新铸造的Token归属显示为address(0),交易回滚,合约返回错误ERC721: invalid token ID。
核心问题分析
1. NFTMarketplace错误继承ERC721合约
NFTMarketplace合约无需自身作为ERC721代币发行方,继承ERC721会导致关键逻辑混淆:
buyNFT函数中调用ERC721.ownerOf(tokenId)时,实际查询的是Marketplace合约自身的代币所有者,而非用户铸造NFT的合约。由于Marketplace从未铸造过该Token ID,返回address(0),触发invalid token ID错误。
2. Marketplace合约owner变量未初始化
updateListPrice函数中使用owner变量校验权限,但合约构造函数未对owner赋值,导致该函数永远无法正常调用。
3. listToken函数逻辑冗余
listToken中传入的price参数未被使用,直接硬编码为listPrice,与函数参数设计矛盾。
修复方案
1. 移除NFTMarketplace的ERC721继承
Marketplace仅需作为交易平台与NFT合约交互,无需继承ERC721,修改合约定义:
contract NFTMarketplace is ReentrancyGuard { // ... 原有代码 }
2. 修正buyNFT中的ownerOf调用
从目标NFT合约而非Marketplace自身查询Token所有者:
function buyNFT(address nftContract, uint256 itemId) public payable nonReentrant { uint price = idToToken[itemId].price; uint tokenId = idToToken[itemId].tokenId; // 修正:从对应NFT合约获取所有者 address seller = IERC721(nftContract).ownerOf(tokenId); address buyer = msg.sender; require(msg.value > 0, "You need to send some ether"); require(buyer != seller,"You already own this nft"); require(msg.value == price, "Please submit the asking price in order to complete the purchase"); idToToken[itemId].isListed = false; idToToken[itemId].owner = payable(buyer); payable(seller).transfer(price); _itemsSold.increment(); IERC721(nftContract).transferFrom(seller, buyer, tokenId); }
3. 初始化Marketplace的owner变量
在构造函数中设置合约部署者为owner:
constructor() { owner = payable(msg.sender); }
4. 修正listToken的price参数逻辑
若坚持固定listing fee,移除冗余的price参数;若需自定义定价,替换硬编码的listPrice为传入参数。示例(固定fee版本):
function listToken(address nftContract, uint256 currentTokenId, string memory tokenURI, string memory name) public payable { require(msg.value > 0, "Price must be at least 1 wei"); require(msg.value == listPrice, "Price must be equal to listing price"); idToToken[currentTokenId] = Token( currentTokenId, tokenURI, nftContract, name, payable(address(this)), listPrice, true ); emit TokenListedSuccess( currentTokenId, nftContract, msg.sender, listPrice, true ); }
修改后的完整合约代码
NFT合约(无改动)
//SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; import "./@openzeppelin/contracts/utils/Counters.sol"; import "./@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "./@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "./@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract NFT is ERC721URIStorage { using Counters for Counters.Counter; Counters.Counter private _tokenIds; address contractAddress; constructor(address marketplaceAddress) ERC721("NFTMarketplace", "NFTM") { contractAddress = marketplaceAddress; } event TokenMinted ( uint256 indexed tokenId, string tokenURI ); function createToken(string memory tokenURI) public returns (uint) { uint256 currentTokenId = _tokenIds.current(); _safeMint(msg.sender, currentTokenId); _setTokenURI(currentTokenId, tokenURI); setApprovalForAll(contractAddress, true); _tokenIds.increment(); emit TokenMinted(currentTokenId, tokenURI); return currentTokenId; } function getCurrentToken() public view returns (uint256) { return _tokenIds.current(); } }
NFTMarketplace合约(修复后)
//SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; import "./@openzeppelin/contracts/utils/Counters.sol"; import "./@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "./@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract NFTMarketplace is ReentrancyGuard { using Counters for Counters.Counter; Counters.Counter private _itemsSold; address payable owner; uint256 listPrice = 0.01 ether; constructor() { owner = payable(msg.sender); } struct Token { uint256 tokenId; string tokenURI; address nftContract; string name; address payable owner; uint256 price; bool isListed; } event TokenListedSuccess ( uint256 indexed tokenId, address nftContract, address owner, uint256 price, bool isListed ); mapping(uint256 => Token) private idToToken; function updateListPrice(uint256 _listPrice) public payable { require(owner == msg.sender, "Only owner can update listing price"); listPrice = _listPrice; } function listToken(address nftContract, uint256 currentTokenId, string memory tokenURI, string memory name) public payable { require(msg.value > 0, "Price must be at least 1 wei"); require(msg.value == listPrice, "Price must be equal to listing price"); idToToken[currentTokenId] = Token( currentTokenId, tokenURI, nftContract, name, payable(address(this)), listPrice, true ); emit TokenListedSuccess( currentTokenId, nftContract, msg.sender, listPrice, true ); } function buyNFT(address nftContract, uint256 itemId) public payable nonReentrant { uint price = idToToken[itemId].price; uint tokenId = idToToken[itemId].tokenId; address seller = IERC721(nftContract).ownerOf(tokenId); address buyer = msg.sender; require(msg.value > 0, "You need to send some ether"); require(buyer != seller,"You already own this nft"); require(msg.value == price, "Please submit the asking price in order to complete the purchase"); idToToken[itemId].isListed = false; idToToken[itemId].owner = payable(buyer); payable(seller).transfer(price); _itemsSold.increment(); IERC721(nftContract).transferFrom(seller, buyer, tokenId); } /* ...rest of smart contract */ }
内容的提问来源于stack exchange,提问作者user9958772
相关产品推荐
相关产品推荐

