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

Solidity删除存储未获Gas退款,如何验证Gas退款功能?

如何通过Solidity合约验证Gas退款功能?

我编写了一个简单的Solidity合约来验证删除存储时的Gas退款机制,合约代码如下:

pragma solidity >=0.7.0 <0.9.0;    
contract Storage {

    bool[] private data = [true, true, true, true, true, true, true, true, true, true,
                           true, true, true, true, true, true, true, true, true, true,
                           true, true, true, true, true, true, true, true, true, true,
                           true, true, true, true, true, true, true, true, true, true,
                           true, true, true, true, true, true, true, true, true, true
                           ];

    event Count(uint i);
    function removeRange(bool isDelete) public returns(bool[] memory) {
        for (uint j = 0; j < data.length; j++) {  
            emit Count(j);
            if(!isDelete) {
                continue;
            }
            delete data[j];
        }
        return data;
    }
}

我在方法中添加了计数器来执行耗Gas的操作。当不执行删除操作时,Remix日志返回的Gas消耗为:

gas 151499 gas
transaction cost 131738 gas
execution cost 131738 gas

当调用执行存储删除的函数时,不仅没有获得Gas退款,Gas消耗反而更高:

gas 186013 gas
transaction cost 152150 gas
execution cost 152150 gas


问题原因

你当前的合约无法触发Gas退款的核心原因:

  • bool类型仅占存储槽的1位,delete data[j]只是将该位设为默认值false,并没有释放整个存储槽。Ethereum的Gas退款仅针对释放完整存储槽的操作——即把曾经写入非默认值的存储槽重置为默认值时才会触发。
  • 执行delete本身有基础Gas开销(约5000 Gas/操作),这个开销暂时盖过了单一位修改带来的微小退款,导致总消耗上升。

正确验证方案

1. 优化合约设计

使用占满完整存储槽的类型(如uint256),并分开基准操作和删除操作,让退款效果更清晰:

pragma solidity >=0.7.0 <0.9.0;

contract GasRefundValidator {
    // 每个元素占1个完整存储槽的数组
    uint256[] private storageSlots;

    // 初始化写入非默认值,为后续退款做准备
    constructor() {
        for (uint256 i = 0; i < 50; i++) {
            storageSlots.push(i + 1); // 写入非0值
        }
    }

    // 仅执行循环(基准消耗,无存储操作)
    function baselineLoop() public {
        for (uint256 j = 0; j < storageSlots.length; j++) {
            // 空循环,模拟遍历开销
        }
    }

    // 循环+删除存储(触发退款)
    function loopWithDelete() public {
        for (uint256 j = 0; j < storageSlots.length; j++) {
            delete storageSlots[j];
        }
    }

    // 批量清空数组(更高效的存储释放)
    function clearEntireArray() public {
        delete storageSlots; // 一次性释放所有数组元素的存储槽
    }
}

2. 验证步骤

  • 部署合约后,先调用baselineLoop(),记录execution cost(记为baseCost)。
  • 再调用loopWithDelete(),记录execution cost(记为deleteCost)。
  • 对比两者:单个delete的基础开销约5000 Gas,而释放存储槽的退款约48000 Gas,所以每个delete的净收益约43000 Gas。批量操作后,deleteCost会远低于baseCost + 50*5000的理论值,证明退款生效。
  • 额外测试clearEntireArray():它的Gas消耗会远低于循环删除,因为EVM对数组清空有优化,一次性释放所有存储槽,退款效果更显著。

3. 关键注意事项

  • 退款上限:单交易的Gas退款不能超过总消耗的20%,若交易总消耗过低,退款会被限制。
  • 存储槽状态:只有存储槽之前写入过非默认值时,重置为默认值才会触发退款。
  • 关注execution cost:Remix中的gas包含交易固定开销(如签名验证),execution cost才是合约执行的实际消耗,用于对比更准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 20:35:29