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

Remix部署Solidity Reward合约交易回退问题求助

Remix部署Solidity合约交易回退问题排查

问题背景

  • Solidity初学者,近期重新接触编程
  • 在Remix上部署合约后调用交易持续回退,Remix返回通用报错:

The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.
Debug the transaction to get more information.
报错翻译:交易已回滚至初始状态。注:若调用函数时发送原生链上币,被调用函数需标记payable,且发送金额需小于钱包余额。可通过交易调试获取更多信息。
说明:该提示为Remix通用回退提示,此合约未涉及接收原生币逻辑,无需添加payable修饰符,问题出在合约代码本身的逻辑错误。

合约代码Bug明细

  1. 构造函数数组越界
    构造函数中holderlist初始为空数组(长度为0),直接执行holderlist[0] = msg.sender访问不存在的索引,会直接触发EVM数组越界回退,空数组需通过push()方法添加首个元素。
  2. 新持有人地址赋值错误
    给首次收款的地址创建Holder结构体时,Holderaddress字段错误填入转账人msg.sender,而非实际收款地址to,后续读取该字段会拿到错误值。
  3. 持有人计数未更新
    新增持有人的逻辑分支中,未执行Holdercount +=1操作,导致Holdercount计数和实际持有人数不匹配,首次转账时Holdercount仍为1,触发distribute函数中Holdercount >1的校验回退。
  4. 删除持有人逻辑索引越界+错位
    • 存储的Holder.Number从1开始计数,但Solidity数组索引起始值为0,直接传入removefromlist会导致索引错位
    • 循环中访问holderlist[i+1]时,循环边界设为i < holderlist.length,当i为数组最后一个索引时i+1越界触发回退
    • 元素移位后未更新后续持有人存储的Number值,后续删除操作会出现索引错乱
  5. 函数修饰符缺失
    balancecheck是仅读取链上状态的查询函数,未标记view修饰符,调用时需要发送交易消耗gas,不符合查询函数的使用逻辑。
  6. 分红对账不平
    扣除的手续费总额为value * tax / 100,但分红逻辑未校验总分红金额和手续费总额相等,整数除法产生的尾差会导致代币账实不符。

修复后可正常运行的合约代码

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract Reward {
    uint public Holdercount = 0;
    uint public totalsupply = 1000000000 * 10 **18;
    string public name = "Reward";
    string public symbol = "RW";
    uint public decimals = 18;
    address public creater;
    uint public tax = 10;
    uint public createdtime;

    struct Holder {
        address Holderaddress;
        uint Balance;
        uint Number;
        uint Purchasetime;
        bool Boughtbefore;
    }

    event Transfer(address indexed from, address indexed to, uint value);
    event reward(address receiver, uint amount);

    mapping(address => Holder) public Holders ;
    address[] public holderlist;

    constructor() {
        Holdercount += 1;
        // 首个元素用push添加,避免数组越界
        holderlist.push(msg.sender);
        Holder memory newholder = Holder(msg.sender, totalsupply, 0, block.timestamp, true);
        Holders[msg.sender] = newholder;
        creater = Holders[msg.sender].Holderaddress;
        createdtime = block.timestamp;
    }


    function transfer(address to, uint value) public returns(bool) {
        require (value >= 10000, 'minimum transfer amount 10000');
        require(Holders[msg.sender].Balance >= value, 'balance too low');
        uint taxAmount = (value*tax)/100;
        uint transferamount = value - taxAmount;
        if(Holders[to].Boughtbefore){
            Holders[to].Balance += transferamount;
            Holders[msg.sender].Balance -= value;
        }
        if(!Holders[to].Boughtbefore){
            holderlist.push(to);
            // 修正地址为收款方to,Number取数组最新索引
            Holder memory Newholder = Holder(to, transferamount, holderlist.length-1, block.timestamp, true);
            Holders[to] = Newholder;
            Holders[msg.sender].Balance -= value; 
            // 新增持有人时更新计数
            Holdercount += 1;
        }
        if(Holders[msg.sender].Balance == 0){
            removefromlist(Holders[msg.sender].Number);
            delete Holders[msg.sender];
            Holdercount -= 1;
        }
        emit Transfer(msg.sender, to, value);
        distribute(taxAmount);
        return true;
    }



    function distribute(uint _charge) internal{
        require(Holdercount >1, "unable to distribute");
        uint _reward = _charge/Holdercount;
        // 处理整数除法剩余的尾差,加到第一个持有人地址上避免对账不平
        uint remainder = _charge - _reward * Holdercount;
        for(uint i=0; i<Holdercount; i++){
            uint addAmount = _reward;
            if(i == 0){
                addAmount += remainder;
            }
            Holders[holderlist[i]].Balance += addAmount;
            emit reward(Holders[holderlist[i]].Holderaddress, addAmount);
        }
    }

    function removefromlist (uint index) internal {
        // 修正循环边界,避免i+1越界
        for (uint i = index; i < holderlist.length - 1; i++){
            holderlist[i] = holderlist[i +1];
            // 移位后更新对应持有人的索引值
            Holders[holderlist[i]].Number = i;
        }
        holderlist.pop();
    }

    // 添加view修饰符,支持免费调用查询
    function balancecheck(address checkaddress) public view returns (uint){
        return Holders[checkaddress].Balance;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:15:48