Solidity多铸币NFT合约付费safeMintNft函数执行失败排查
问题:付费铸币函数
safeMintNft执行失败 问题背景
开发了一个Solidity NFT集合合约,包含两种铸币方式:需Soulbound NFT白名单的铸币,以及MATIC/ETH付费的公开销售铸币。当前Soulbound铸币功能正常,但付费铸币的safeMintNft函数无法正常工作。
合约核心代码(safeMintNft函数)
function safeMintNft() public payable whenNotPaused { require( nftCost > 0, "NFT cost is not set" ); require( keccak256(bytes(whitelistedAddresses[msg.sender].discordUser)) != keccak256(""), "Address is not whitelisted" ); require( keccak256(bytes(whitelistedAddresses[msg.sender].user_type)) == keccak256(bytes("buyer")), "Only buyers can mint the NFT" ); uint256 minterBalance = address(msg.sender).balance; require( minterBalance >= nftCost, "not enough tokens to pay for this NFT" ); uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); tokenIdAddresses[tokenId] = msg.sender; address payable contractAddress = payable(address(this)); contractAddress.transfer(nftCost); _safeMint(msg.sender, tokenId); }
测试代码(Chai)
describe("User has enough eth to mint", async function() { beforeEach(async function() { await this.nft.setNftCost(ethers.utils.parseEther("0.1")); }); it("should be able to mint", async function() { const tx = await this.nft.connect(this.normalUser).safeMintNft(); const waitedTx = await tx.wait(); expect(waitedTx.status).to.equal(1); }); });
报错信息
Error: Transaction reverted: function call failed to execute
at MelkExp.safeMintNft (contracts/MelkExp.sol:107)
报错行指向代码中的contractAddress.transfer(nftCost);
问题原因与解决思路
核心原因
- 无效的转账逻辑:尝试让合约给自己转ETH,这是完全没必要的操作,Solidity中合约无法向自身转账,直接触发revert。
- 未验证用户实际发送的ETH金额:函数声明了
payable,但仅检查了用户余额,未验证msg.value(用户实际发送的ETH)是否等于nftCost,且测试代码调用时也未附带ETH,导致后续逻辑出错。
解决步骤
- 移除多余的转账代码:直接删除以下两行,合约会自动接收用户发送的ETH:
address payable contractAddress = payable(address(this)); contractAddress.transfer(nftCost); - 添加
msg.value验证:在余额检查后新增一行,确保用户发送的ETH金额正确:require(msg.value == nftCost, "Incorrect ETH amount sent"); - 修正测试代码:调用
safeMintNft时必须附带指定金额的ETH:const tx = await this.nft.connect(this.normalUser).safeMintNft({ value: ethers.utils.parseEther("0.1") });
额外优化建议
- 避免使用
transfer/send:这类函数有2300gas的限制,后续若需给其他地址转ETH,推荐使用call{value: amount}("")。 - 优化白名单检查逻辑:直接存储布尔值标记是否为白名单买家,避免重复执行
keccak256计算,节省gas消耗。
内容的提问来源于stack exchange,提问作者Lorenzo Battistela
相关产品推荐
相关产品推荐

