You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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, "");
    }
}

修复说明

  1. 移除冗余余额维护:删除自定义_balances映射,直接使用ERC1155内置的balanceOf获取余额,避免状态不一致。
  2. 修正资金流向:将买家支付的ETH直接转给代币持有者(卖家),而非转回买家。
  3. 修正代币转移逻辑:从指定卖家地址转移代币给买家,而非从零地址无效转出。
  4. 明确ETH校验:要求msg.value必须等于总价格,避免金额不符。
  5. 同步维护token列表:重写_safeTransferFrom函数,自动更新地址的token持有列表,确保数据准确。

调用注意事项

调用buyToken时:

  • 必须在交易的Value字段填入amount * tokenPrice[tokenId]对应的ETH金额(例如tokenPrice为10wei、amount为10时,填入100wei)。
  • 需要传入seller参数(即持有该代币的目标账户地址),让合约明确代币来源。

内容的提问来源于stack exchange,提问作者0391

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:01:19