调用createMarketItem函数触发ERC721权限错误,求合约问题排查
ERC721上架报错排查:caller is not token owner or approved
问题场景
持有2个NFT,调用以下NFTMarketplace合约的createMarketItem函数上架其中一个时,触发 revert 错误:
Execution reverted: ERC721: caller is not token owner or approved
合约代码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract NFTMarketplace is ReentrancyGuard { using Counters for Counters.Counter; Counters.Counter private _itemIds; Counters.Counter private _itemsSold; address public owner; constructor() { owner == msg.sender; } struct MarketItem { uint256 itemId; address nftContract; uint256 tokenId; address payable seller; address payable owner; uint256 price; bool sold; } mapping(uint256 => MarketItem) private idToMarketItem; event MarketItemCreated( uint256 indexed itemId, address indexed nftContract, uint256 indexed tokenId, address seller, address owner, uint256 price, bool sold ); function createMarketItem( address nftContract, uint256 tokenId, uint256 price ) public payable nonReentrant { require(price > 0, "Price must be greater than 0"); _itemIds.increment(); uint256 itemId = _itemIds.current(); idToMarketItem[itemId] = MarketItem( itemId, nftContract, tokenId, payable(msg.sender), payable(address(0)), price, false ); IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); emit MarketItemCreated( itemId, nftContract, tokenId, msg.sender, address(0), price, false ); } function createMarketSale( address nftContract, uint256 itemId ) public payable nonReentrant { uint256 price = idToMarketItem[itemId].price; uint256 tokenId = idToMarketItem[itemId].tokenId; bool sold = idToMarketItem[itemId].sold; require(msg.value == price, "Please submit the asking price in order to complete the purchase"); require(sold != true, "This Sale has already finished"); idToMarketItem[itemId].seller.transfer(msg.value); IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId); idToMarketItem[itemId].owner = payable(msg.sender); _itemsSold.increment(); idToMarketItem[itemId].sold = true; } function fetchMarketItems() public view returns (MarketItem[] memory) { uint256 itemCount = _itemIds.current(); uint256 unsoldItemCount = _itemIds.current() - _itemsSold.current(); uint256 currentIndex = 0; MarketItem[] memory items = new MarketItem[](unsoldItemCount); for (uint256 i = 0; i < itemCount; i++) { if (idToMarketItem[i + 1].owner == address(0)) { uint256 currentId = i + 1; MarketItem storage currentItem = idToMarketItem[currentId]; items[currentIndex] = currentItem; currentIndex += 1; } } return items; } }
问题分析与修复方案
1. 核心报错原因:未给市场合约授权NFT
合约中直接调用transferFrom将NFT转入市场,但ERC721的transferFrom要求调用者(市场合约)必须是token所有者,或者已获得token所有者的授权。
解决步骤:
在调用createMarketItem之前,先调用对应NFT合约的授权方法:
- 单次授权:调用
approve(marketplaceContractAddress, tokenId),授权市场合约操作该tokenId的NFT - 批量授权:调用
setApprovalForAll(marketplaceContractAddress, true),授权市场合约操作你持有的所有该系列NFT
2. 合约构造函数的低级错误
构造函数中owner == msg.sender;是比较操作,而非赋值,导致owner变量始终是默认的address(0),虽然这和当前报错无关,但会影响后续依赖owner权限的功能,必须修复:
constructor() { owner = msg.sender; // 把 == 改成 = }
3. 可选优化:提前校验所有权
在createMarketItem函数开头添加校验,提前确认调用者是NFT所有者,避免后续transferFrom报错:
function createMarketItem( address nftContract, uint256 tokenId, uint256 price ) public payable nonReentrant { require(price > 0, "Price must be greater than 0"); // 新增:校验调用者是token所有者 require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "Caller is not token owner"); // 后续原有代码... }
内容的提问来源于stack exchange,提问作者Jossif Elefteriadis
相关产品推荐
相关产品推荐

