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

如何在Solidity钻石模式(EIP-2535)中计算AppStorage存储位置?

如何正确访问Diamond模式Solidity合约的存储?

我正尝试计算采用Diamond模式的Solidity智能合约的存储位置,以便用w3.getStorageAt读取存储,但始终找不到正确位置。

目标合约地址为0x0e22B5f3E11944578b37ED04F5312Dfc246f443C,我编写了镜像LibAppStorage.sol结构体的合约,通过Foundry获取到了存储位置信息,示例如下:

{
      "astId": 91,
      "contract": "LibAppStorage.sol:MyContract",
      "label": "petName",
      "offset": 0,
      "slot": "24",
      "type": "t_mapping(t_uint256,t_string_storage)"
},

我知道钻石存储的定位符是DIAMOND_APP_STORAGE_POSITION = keccak256("diamond.app.storage"),但调用getStorageAt始终返回空值。以下是我的测试代码及输出:

DIAMOND_APP_STORAGE_POSITION = Web3.keccak(text="diamond.app.storage")

offsets = {
    "petName": 24,
}

myKey = 8350
print("Diamond app storage position: ")
print(DIAMOND_APP_STORAGE_POSITION)
print(w3.eth.get_storage_at("0x0e22B5f3E11944578b37ED04F5312Dfc246f443C", DIAMOND_APP_STORAGE_POSITION))
print("Petname offset: ")
petNameOffset = Web3.to_hex(Web3.to_bytes(offsets["petName"]))
print(petNameOffset)
print("Petname position: ")
print(w3.eth.get_storage_at("0x0e22B5f3E11944578b37ED04F5312Dfc246f443C", Web3.keccak(text=petNameOffset + "diamond.app.storage")))
print(w3.eth.get_storage_at("0x0e22B5f3E11944578b37ED04F5312Dfc246f443C", Web3.keccak(text=petNameOffset + "diamond.app.storage")))
print("My Petname position: ")
print(w3.eth.get_storage_at("0x0e22B5f3E11944578b37ED04F5312Dfc246f443C", Web3.keccak(text=str(myKey) + str(offsets["petName"]) + "diamond.app.storage")))

输出结果:

Diamond app storage position:
b'1\xc1\xf8@#\x97\xe5\xb1\xe3\xd7_=\xa7 |\xe6\xf5EV\xb2z\xfer7Bv\x8cNK\xe5\x0c\xe0'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Petname offset:
0x18
Petname position:
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
My Petname position:
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

请问如何通过客户端正确访问Diamond合约的存储?


解决方法

你的核心问题是对Diamond存储的计算逻辑理解错误,正确的存储位置计算步骤如下:

  1. 计算AppStorage的基础槽位
    Diamond模式中,DIAMOND_APP_STORAGE_POSITION是结构体的起始锚点槽位(即keccak256("diamond.app.storage")),直接读取这个槽位得到空值是正常的——因为它本身不存储业务数据,只是结构体成员的位置基准。

  2. 计算映射成员的实际槽位
    针对结构体中的petName映射:

    • 首先,映射在结构体中的相对槽位是24(从Foundry输出中获取)。
    • 映射的锚点槽位 = keccak256("diamond.app.storage")的整数形式 + 24(这里是整数加法,而非字符串拼接)。
    • 对于映射键myKey=8350,实际存储字符串的位置分为两部分:
      • 字符串长度存储在keccak256(abi.encodePacked(myKey, 映射锚点槽位))对应的槽位。
      • 字符串内容存储在上述槽位的下一个位置(如果长度小于32字节,内容可能和长度同槽,但分开读取更稳妥)。
  3. 修正后的代码示例

    from web3 import Web3
    
    w3 = Web3(Web3.HTTPProvider("你的RPC节点地址"))
    CONTRACT_ADDR = "0x0e22B5f3E11944578b37ED04F5312Dfc246f443C"
    
    # 1. 计算AppStorage的起始锚点(转成整数)
    diamond_storage_pos = int.from_bytes(w3.keccak(text="diamond.app.storage"), byteorder='big')
    # 2. petName在结构体中的相对槽位
    petname_relative_slot = 24
    # 3. 计算petName映射的锚点槽位
    petname_anchor_slot = diamond_storage_pos + petname_relative_slot
    # 4. 目标映射键
    my_key = 8350
    
    # 5. 计算存储字符串长度的槽位
    length_slot_bytes = w3.keccak(abi.encodePacked(my_key, petname_anchor_slot))
    length_slot = int.from_bytes(length_slot_bytes, byteorder='big')
    
    # 读取字符串长度
    length_bytes = w3.eth.get_storage_at(CONTRACT_ADDR, length_slot)
    str_length = int.from_bytes(length_bytes, byteorder='big')
    
    # 读取字符串内容
    if str_length == 0:
        print("该键对应的petName为空")
    else:
        content_slot = length_slot + 1
        content_bytes = w3.eth.get_storage_at(CONTRACT_ADDR, content_slot)
        pet_name = content_bytes[:str_length].decode('utf-8')
        print(f"读取到的petName: {pet_name}")
    
  4. 关键注意事项

    • 必须用abi.encodePacked打包整数槽位和键值计算哈希,Solidity存储计算基于二进制数据,而非字符串拼接。
    • Diamond模式的AppStorage结构体成员槽位是相对于起始锚点的偏移量,需用整数加法计算,而非哈希拼接。
    • Solidity中字符串的存储规则:长度存在映射键对应的槽位,内容存在下一个槽位(长字符串会占用多个槽位,需循环读取)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:45:01