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

