Solidity ERC721调用walletOfOwner报Internal JSON-RPC回滚错误
问题根因
调用回滚的核心原因如下:
- 你的合约仅导入了
IERC721Enumerable接口,没有实际继承ERC721Enumerable的实现逻辑。基础版ERC721、ERC721URIStorage都没有实现tokenOfOwnerByIndex这类持仓枚举方法,你在代码中强行将合约地址转换为IERC721Enumerable类型调用未实现的方法,EVM执行时会直接触发回滚,返回execution reverted错误。 - 自定义的
walletOfOwner函数额外传入了contractAddress参数,一旦传入的地址不是当前NFT合约地址、或者对应地址合约没有实现枚举逻辑,也会直接触发调用回滚。
修复方案
- 调整合约导入与继承关系
把原有导入IERC721Enumerable的代码替换为导入ERC721Enumerable实现:
修改合约声明,加入ERC721Enumerable继承:import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";contract TestNFT is ERC721URIStorage, ERC721Enumerable, Ownable { - 重写多继承冲突的方法
ERC721、ERC721Enumerable、ERC721URIStorage存在同名函数冲突,必须在合约内显式重写才能正常编译:// 重写接口支持查询,兼容所有父合约逻辑 function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } // 重写转账前置钩子,同步更新枚举模块的持仓索引数据 function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId, batchSize); } // 重写tokenURI查询,解决URI存储与基础ERC721的逻辑冲突 function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } // 重写销毁逻辑,同步清理URI存储与枚举模块的对应数据 function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { super._burn(tokenId); } - 简化walletOfOwner实现
移除多余的contractAddress参数,直接调用当前合约继承的枚举方法:function walletOfOwner(address owner_) public view returns (uint256[] memory){ uint256 _tokenCount = balanceOf(owner_); uint256[] memory _tokens = new uint256[](_tokenCount); for(uint256 i=0; i < _tokenCount; i++){ _tokens[i] = tokenOfOwnerByIndex(owner_, i); } return _tokens; }
注意事项
- 修改完成后需要重新编译部署合约,已经部署上线的旧合约无法通过升级补全枚举逻辑:ERC721Enumerable需要在铸造、转账、销毁时同步更新持仓索引映射,旧合约没有存储这部分链上数据,无法追溯历史持仓。
- 新合约部署后,先铸造测试NFT,再调用
walletOfOwner传入对应持仓地址,即可正常返回该地址持有的全部token ID列表,不会再出现JSON-RPC执行回滚错误。
内容的提问来源于stack exchange,提问作者PKS
相关产品推荐
相关产品推荐

