跳转至

其余重要 EIPs

600 个字 33 行代码 预计阅读时间 2 分钟

Abstract

除了 ERC 标准中提到的 EIP 以外一些比较有用、需要注意的 EIP

EIP-2929

Gas cost increases for state access opcodes

包含在柏林硬分叉当中,修改了几个和状态读取、地址访问、状态修改相关的字节码的 gas 消耗规则(降低受 DoS 攻击风险

在执行交易时,维护两个集合 accessed_addresses: Set[Address]accessed_storage_keys: Set[Tuple[Address, Bytes32]],分别记录访问过的地址和访问过的存储键。

交易执行开始时初始化:

  • accessed_addresses 包括 tx.sender、tx.to(或将要创建合约的地址)和其它预编译的地址
  • accessed_storage_keys 为空

读取 storage

对于 CALL 系列、BALANCE 以及 EXT 系列字节码(原 gas 700

  • 如果访问的目的地址不在 accessed_addresses 中,则
    • 消耗 COLD_ACCOUNT_ACCESS_COST = 2600 gas(冷读取)
    • 将地址添加到 accessed_addresses 中(预热)
  • 如果访问的目的地址在其中,则:
    • 消耗 WARM_STORAGE_READ_COST = 100 gas(热读取)

对于 SLOAD(原 gas 800

  • 如果 (address, storage_key) 对不在 accessed_storage_keys 中,则
    • 消耗 COLD_SLOAD_COST = 2100 gas
    • (address, storage_key) 对添加到 accessed_storage_keys
    • * 其中 address 表示要读取 storage 的合约地址,storage_key 表示要读取的 storage key(slot)
  • 如果 (address, storage_key) 对在其中,则
    • 消耗 WARM_STORAGE_READ_COST = 100 gas

写入 storage

对于 SSTORE(原 gas 5000

  • 如果 (address, storage_key) 对不在 accessed_storage_keys 中,则
    • 消耗 COLD_SSTORE_COST = 2100 gas
    • (address, storage_key) 对添加到 accessed_storage_keys
    • 消耗 5000 - COLD_SSTORE_COST = 2900 gas 进行写入
    • * 总计仍为 5000 gas
  • 如果 (address, storage_key) 对在其中,则
    • 只消耗 5000 - COLD_SSTORE_COST = 2900 gas 进行写入

selfdestruct

对于 SELFDESTRUCT(原 gas 5000)也有更改,因为其自毁转移余额时会访问其它地址,但规则和前面稍有不同:

  • 如果接收者地址不在 accessed_addresses 中,则
    • 消耗 COLD_ACCOUNT_ACCESS_COST = 2600 gas
    • 将地址添加到 accessed_addresses
    • 消耗 5000 gas 进行自毁
  • 如果接收者地址在其中,则
    • 只消耗 5000 gas 进行自毁
    • * 会再消耗 WARM_STORAGE_READ_COST = 100 gas

用例

hackergame2022 的链上预言家第三问要使用这一特性,因为它需要在不修改状态的情况下在一次交易中记下一个值并输出:

题目合约
pragma solidity =0.8.17;

interface MemoryMaster {
    function memorize(uint256 n) external view;
    function recall() external view returns (uint256);
}

contract Challenge {
    function test(MemoryMaster m, uint256 n) external returns (bool) {
        m.memorize(n);
        uint256 recalled = m.recall();
        return recalled == n;
    }
}

解法就是利用插槽冷热读取 gas 消耗的较大差异,来逐位记录:

exp
contract Exploit {
    function memorize(uint256 n) external view {
        for (uint i = 0; i < 256; ++i) {
            if ((n >> i) & 1 == 1) check(1218+i); // 第一次 check,是冷的
        }
    }
    function recall() external view returns (uint256) {
        uint256 res;
        for (uint i = 0; i < 256; ++i) {
            if (check(1218+i)) res |= 1 << i; // 如果前面 check 过了,就是热的,gas 消耗少于 1k,会返回 true
        }
        return res;
    }
    function check(uint256 x) internal view returns (bool) {
        uint gas = gasleft();
        uint b = address(uint160(x)).balance; // 利用 BALANCE 字节码
        return gas - gasleft() < 1000;
    }
}

最后更新: 2022年12月1日 21:44:15
创建日期: 2022年12月1日 21:44:15
回到页面顶部