Remix中Solidity ERC1155合约buyToken报Not Enough Fund问题求助
问题:ERC-1155合约buyToken函数提示"Not Enough Fund",msg.value始终为0
我是区块链新手,正在开发一个简单的ERC-1155合约用于铸币和设置代币价格。当用其他地址调用buyToken函数时,一直触发"Not Enough Fund"错误;调试发现msg.value为0,尽管测试账户有100ETH余额,只有将代币数量设为0时交易才能成功。
合约代码
// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc1155 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; /** * @title NFTMinter * @dev NFT Contract Minter * @custom:dev-run-script ./scripts/deploy_with_ethers.ts */ contract NFTMinter is ERC1155 { constructor() ERC1155("https://raw.githubusercontent.com/noopmood/TutorialNFTInGo/main/metadata/{id}.json") payable {} // Define the mapping of addresses to balances mapping(address => mapping(uint256 => uint256)) public _balances; // Define the mapping of address to tokenIds owned mapping(address => uint256[]) public _tokenIds; // Define the mapping of tokenId to price mapping(uint256 => uint256) public tokenPrice; // Define the sender to address payable type address payable public sender; struct Token { uint256 tokenId; uint256 balance; } function mintCaller(uint256 tokenId, uint256 amount) public { _mint(msg.sender, tokenId, amount, ""); } // Mints new tokens and sets the price for each token. function mintAddress(uint256 tokenId, uint256 amount, address addr, uint256 price) public{ _mint(addr, tokenId, amount, ""); // Update the balance of the recipient _balances[addr][tokenId] += amount; // Add the tokenId to the address _tokenIds[addr].push(tokenId); // Set the price of the token tokenPrice[tokenId] = price; } // Get all tokenIds from its owner address function getTokenIdsByAddress(address addr) public view returns (uint[] memory) { return _tokenIds[addr]; } // Get the balance / amount of the tokenId function getTokenByIdAndAddress(address addr, uint256 tokenId) public view returns (Token memory) { Token memory result; result.tokenId = tokenId; result.balance = _balances[addr][tokenId]; return result; } // Get the tokenIds along with its corresponding balances/amount function getAllTokenByAddress(address holder) public view returns (Token[] memory) { Token[] memory result = new Token[](_tokenIds[holder].length); for (uint i = 0; i < _tokenIds[holder].length; i++) { result[i].tokenId = _tokenIds[holder][i]; result[i].balance = _balances[holder][_tokenIds[holder][i]]; } return result; } // Transfers the tokens from one address to another. function transfer(address addr, uint256 tokenId, uint256 amount) public { require(_balances[msg.sender][tokenId] >= amount, "Not enough balance"); // Transfer the token _safeTransferFrom(msg.sender, addr, tokenId, amount, ""); // Update the sender's balance _balances[msg.sender][tokenId] -= amount; // Update the recipient's balance _balances[addr][tokenId] += amount; } // Allows a buyer to purchase a token by sending the required amount to the contract and updating the balance of the buyer. function buyToken(uint256 tokenId, uint256 amount) public payable { require(msg.value >= amount * tokenPrice[tokenId], "Not enough funds"); // Deduct the amount from the buyer sender = payable(msg.sender); sender.transfer(amount * tokenPrice[tokenId]); // Transfer the token to the buyer _safeTransferFrom(address(0), msg.sender, tokenId, amount, ""); // Update the buyer's balance _balances[msg.sender][tokenId] += amount; } }
复现步骤
- 传入20ETH部署合约
- 调用
mintAddress,参数:tokenId=1、amount=10、price=10、addr=某账户地址 - 调用
transfer,参数:addr=目标账户地址、tokenId=1、amount=10 - 切换账户到目标账户
- 调用
buyToken,参数tokenId=1、amount=10,触发"Not Enough Fund"提示
已尝试操作
- 部署合约时传入ETH,问题仍存在
- 确认Remix中账户有100ETH余额,但交易仍失败
问题分析与解决方案
1. msg.value为0的直接原因
调用buyToken(payable函数)时,没有在交易中手动指定发送的ETH金额。测试账户有余额不代表交易会自动转钱,必须在调用函数时明确设置要发送的ETH数量(比如在Remix的"Value"输入框中填入对应金额:10*10=100 wei,或者根据你的单位调整)。
2. 合约逻辑的核心错误
- 错误的代币转移方向:
_safeTransferFrom(address(0), msg.sender, tokenId, amount, "")是从零地址转代币,但零地址没有持有该代币,应该从当前持有该代币的卖家地址(即你的"目标账户地址")转出。 - 资金流向完全错误:
sender.transfer(amount * tokenPrice[tokenId])是把买家转来的钱又转回给买家,正确逻辑应该是把钱转给代币的持有者(卖家)。 - 重复维护余额状态:ERC1155已经内置了
balanceOf函数和相关状态管理,自己维护_balances和_tokenIds会导致状态不同步(比如transfer函数中调用了_safeTransferFrom但自己维护的余额和合约实际余额不一致)。
修复后的合约代码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract NFTMinter is ERC1155 { mapping(uint256 => uint256) public tokenPrice; mapping(address => uint256[]) public _tokenIds; constructor() ERC1155("https://raw.githubusercontent.com/noopmood/TutorialNFTInGo/main/metadata/{id}.json") {} // 铸币并设置价格 function mintAddress(uint256 tokenId, uint256 amount, address addr, uint256 price) public { require(tokenPrice[tokenId] == 0, "Price already set"); _mint(addr, tokenId, amount, ""); tokenPrice[tokenId] = price; // 初始化卖家的tokenIds列表 bool exists = false; for (uint i = 0; i < _tokenIds[addr].length; i++) { if (_tokenIds[addr][i] == tokenId) { exists = true; break; } } if (!exists) { _tokenIds[addr].push(tokenId); } } // 获取地址持有的所有tokenId function getTokenIdsByAddress(address addr) public view returns (uint[] memory) { return _tokenIds[addr]; } // 重写_transfer函数,同步维护tokenIds列表 function _safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) internal override { super._safeTransferFrom(from, to, id, amount, data); // 移除转出方已无余额的tokenId if (balanceOf(from, id) == 0) { for (uint i = 0; i < _tokenIds[from].length; i++) { if (_tokenIds[from][i] == id) { _tokenIds[from][i] = _tokenIds[from][_tokenIds[from].length - 1]; _tokenIds[from].pop(); break; } } } // 给转入方添加新的tokenId(如果之前没有) bool exists = false; for (uint i = 0; i < _tokenIds[to].length; i++) { if (_tokenIds[to][i] == id) { exists = true; break; } } if (!exists) { _tokenIds[to].push(id); } } // 购买代币函数 function buyToken(uint256 tokenId, uint256 amount, address seller) public payable { uint256 totalPrice = amount * tokenPrice[tokenId]; require(msg.value == totalPrice, "Incorrect ETH amount sent"); require(balanceOf(seller, tokenId) >= amount, "Seller has insufficient tokens"); // 转ETH给卖家 payable(seller).transfer(totalPrice); // 转代币给买家 _safeTransferFrom(seller, msg.sender, tokenId, amount, ""); } }
修复说明
- 移除冗余余额维护:删除自定义
_balances映射,直接使用ERC1155内置的balanceOf获取余额,避免状态不一致。 - 修正资金流向:将买家支付的ETH直接转给代币持有者(卖家),而非转回买家。
- 修正代币转移逻辑:从指定卖家地址转移代币给买家,而非从零地址无效转出。
- 明确ETH校验:要求
msg.value必须等于总价格,避免金额不符。 - 同步维护token列表:重写
_safeTransferFrom函数,自动更新地址的token持有列表,确保数据准确。
调用注意事项
调用buyToken时:
- 必须在交易的
Value字段填入amount * tokenPrice[tokenId]对应的ETH金额(例如tokenPrice为10wei、amount为10时,填入100wei)。 - 需要传入
seller参数(即持有该代币的目标账户地址),让合约明确代币来源。
内容的提问来源于stack exchange,提问作者0391
相关产品推荐
相关产品推荐

