BSC网络使用BUSD铸造NFT支付失败问题排查与修复
我正在学习智能合约,目前使用Remix在BSC网络开发NFT铸造功能(该网络与ETH一样采用18位小数)。已实现免费铸造及BNB等原生代币支付的铸造功能,但尝试使用BUSD支付铸造时,虽能通过balanceOf查询到正确余额,执行铸造却触发报错:
Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
Returned error: {"jsonrpc":"2.0","error":"execution reverted","id":5075618818565261}
测试合约代码如下:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyNFT is ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; ERC20 public _token; // Referencja do ERC20 tokenu uint256 public _mintingFee = 1e18; // Opłata za mintowanie NFT address tokenAddress = 0xeB3Eb991D39Dac92616da64b7c6D5af5cCFf1627; struct NFT { string collectionName; } mapping(uint256 => NFT) private _nfts; constructor(uint256 mintingFee) ERC721("MyNFT", "NFT") { _token = ERC20(tokenAddress); _mintingFee = mintingFee * 1e18; } function showBUSD() public view returns (uint256) { return _token.balanceOf(msg.sender); } function mintNFT(address recipient, string memory tokenURI, string memory _collectionName) public onlyOwner returns (uint256) { //require(msg.value >= _mintingFee, "Insufficient BNB amount"); require(_token.balanceOf(msg.sender) >= _mintingFee, "Insufficient balance"); //require(_token.allowance(msg.sender, address(this)) >= _mintingFee, "Insufficient allowance"); _token.transferFrom(msg.sender, address(this), _mintingFee); _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _mint(recipient, newItemId); _nfts[newItemId].collectionName = _collectionName; _setTokenURI(newItemId, tokenURI); /*if (msg.value > _mintingFee) { payable(msg.sender).transfer(msg.value - _mintingFee); }*/ return newItemId; } function getCollectionName(uint256 tokenId) public view returns (string memory) { return _nfts[tokenId].collectionName; } function setMintingFee(uint256 newFee) external onlyOwner { _mintingFee = newFee * 1e18; } }
问题出在以下几个点,对应修复步骤如下:
恢复授权检查逻辑
ERC20的transferFrom要求用户必须先给合约授权足够的代币额度,即使余额充足,没有授权的话transferFrom会直接触发 revert。取消注释require(_token.allowance(msg.sender, address(this)) >= _mintingFee, "Insufficient allowance");这行代码,确保用户已授权合约转移指定数量的BUSD。检查ERC20转账返回值
包括BUSD在内的部分ERC20代币,transferFrom会返回布尔值表示操作成功,但当前代码未检查该返回值。如果转账失败,合约会因返回值不匹配而 revert。将_token.transferFrom(msg.sender, address(this), _mintingFee);替换为:bool success = _token.transferFrom(msg.sender, address(this), _mintingFee); require(success, "BUSD transfer failed");补充前端授权操作
在调用mintNFT之前,必须先调用BUSD合约的approve方法,授权你的NFT合约转移_mintingFee数量的BUSD。比如在Remix中,先切换到BUSD合约地址,调用approve,参数填入NFT合约地址和铸造费用(注意保留18位小数),之后再执行mintNFT。可选:用SafeERC20增强安全性
导入@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol,使用SafeERC20.safeTransferFrom代替原生transferFrom,它会自动检查返回值并处理异常。修改后核心代码如下:import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract MyNFT is ERC721URIStorage, Ownable { using SafeERC20 for ERC20; // ...其他代码 function mintNFT(...) public onlyOwner returns (uint256) { // ...余额和授权检查 _token.safeTransferFrom(msg.sender, address(this), _mintingFee); // ...其他铸造逻辑 } }
内容的提问来源于stack exchange,提问作者Wraith

