如何在Solidity钻石模式(EIP-2535)中计算AppStorage存储位置?
我正尝试计算采用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存储的计算逻辑理解错误,正确的存储位置计算步骤如下:
计算AppStorage的基础槽位
Diamond模式中,DIAMOND_APP_STORAGE_POSITION是结构体的起始锚点槽位(即keccak256("diamond.app.storage")),直接读取这个槽位得到空值是正常的——因为它本身不存储业务数据,只是结构体成员的位置基准。计算映射成员的实际槽位
针对结构体中的petName映射:- 首先,映射在结构体中的相对槽位是
24(从Foundry输出中获取)。 - 映射的锚点槽位 =
keccak256("diamond.app.storage")的整数形式 + 24(这里是整数加法,而非字符串拼接)。 - 对于映射键
myKey=8350,实际存储字符串的位置分为两部分:- 字符串长度存储在
keccak256(abi.encodePacked(myKey, 映射锚点槽位))对应的槽位。 - 字符串内容存储在上述槽位的下一个位置(如果长度小于32字节,内容可能和长度同槽,但分开读取更稳妥)。
- 字符串长度存储在
- 首先,映射在结构体中的相对槽位是
修正后的代码示例
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}")关键注意事项
- 必须用
abi.encodePacked打包整数槽位和键值计算哈希,Solidity存储计算基于二进制数据,而非字符串拼接。 - Diamond模式的AppStorage结构体成员槽位是相对于起始锚点的偏移量,需用整数加法计算,而非哈希拼接。
- Solidity中字符串的存储规则:长度存在映射键对应的槽位,内容存在下一个槽位(长字符串会占用多个槽位,需循环读取)。
- 必须用
内容的提问来源于stack exchange,提问作者Kyranstar

