惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Blog — PlanetScale
Blog — PlanetScale
B
Blog
GbyAI
GbyAI
爱范儿
爱范儿
月光博客
月光博客
N
Netflix TechBlog - Medium
T
Tailwind CSS Blog
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
腾讯CDC
MyScale Blog
MyScale Blog
V
Visual Studio Blog
The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
A
About on SuperTechFans
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - 幽州散人

SQL优化案例 synchronized 为啥会让虚拟线程卸载失效 主线任务和使命 温州3日之旅 CompletableFuture + 异步Servlet:同步的姿势、异步的效果 Snowflake雪花算法与发号器的应用 windows Java 幽灵转发壳进程 也谈MySQL limit offset深翻页问题 SHOW SESSION STATUS 与 Handler 变量 MySQL 5.7 Profiles 执行性能分析 MySQL临时表与文件排序 MySQL 5.7 MRR多范围读优化 MySQL 5.7 ICP索引条件下推优化 MySQL符合索引与最左前缀原则 从I/O 的物理成本理解回表和索引覆盖 第4个电瓶以及补漆 Flink原理:并行度与keyGroup桶 什么是数字批发银行 Java虚拟线程(三)实现原理 Java虚拟线程(二) Java虚拟线程(一) 大模型推理层服务化架构 传奇调查员之路:理智值-99,但我必须听懂深渊的语言 js里调用智能合约读/写函数的方法的区别 字节与其16进制字符表示转换的bug Redisson分布式锁 交易心得 DexScreener接口初探 某安全软件跑飞了。。 Ed25519算法签名与验签的Java实现
理解Solidity存储布局
幽州散人 · 2025-09-05 · via 博客园 - 幽州散人

最近在看OnchainID这套身份合约,里边可升级特性部分用到了proxy模式,contract IdentityProxy里边有如下的构造方法:

 //_implementationAuthority 存储真正Identity实现合约地址的容器
 //initialManagementKey该身份合约的管理key
constructor(address _implementationAuthority, address initialManagementKey) {
        require(_implementationAuthority != address(0), "invalid argument - zero address");
        require(initialManagementKey != address(0), "invalid argument - zero address");

        // 把_implementationAuthority地址存放在0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc
        assembly {
            sstore(0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc, _implementationAuthority)
        }
        //从容器中得到实现合约的地址
        address logic = IImplementationAuthority(_implementationAuthority).getImplementation();

        //执行delegatecall,业务逻辑用logic也就是实现合约Identity的,数据存储在当前合约IdentityProxy里
        (bool success,) = logic.delegatecall(abi.encodeWithSignature("initialize(address)", initialManagementKey));
        require(success, "Initialization failed.");
 }

里边的内联汇编

assembly {
     sstore(0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc, _implementationAuthority)
}

0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc直接指定这个而且是写死的,一开始有些疑惑,这里有必要复习一下Solidity的存储布局,结合EIP-1967的规定,就明白了。

storage

  • 永久存储(链上持久化)。
  • 每个合约地址下有自己的存储空间。
  • Slot = 32 字节为单位,逻辑上是一个 mapping(uint256 => bytes32)。
  • 所有状态变量、mapping、struct 最终都会映射到某个 slot 里。

memory

  • 临时存储(函数执行时存在,执行完销毁)。
  • 线性字节数组,从 0 地址往上扩展。
  • 用于函数内部的动态数组、string、临时计算缓存等。

calldata

  • 只读、不可修改。
  • 存放函数参数(外部调用时 ABI 编码的数据)。
  • 访问成本比 memory 更低。

storage存储空间

在EVM里,每个合约都会有自己的存储空间,其storageLayout逻辑上是个无限大的mapping,比如:

合约A -> Storage_A:mapping(uint256 => bytes32)
合约B -> Storage_B:mapping(uint256 => bytes32)

其中每一个映射key是uint256,一般也成为一个slot,合约里的每个storage类型的变量按照被定义的顺序依次存在自己这个mapping的slot里,例如:

contract C {
    uint256 a; // slot 0
    bool b;    // slot 1 (可能和别的小变量打包)
    mapping(uint => uint) m; // slot 2 (mapping不直接存值,只存“起点slot”)
}

为什么用0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc这个固定的slot

回到之前的合约代码,sstore(0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc, _implementationAuthority)意思是把 _implementationAuthority这个地址存放在0x821f3e4d...这个slot中;
sload(0x821f3e4d...)意思是从第0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafcslot取32字节的值。那么这个大的key值是怎么来的呢?
这是 EIP-1967 的约定:
为了防止普通状态变量“占用” proxy 的关键 slot(比如 implementation 地址、admin 地址),社区约定用这样一个极难撞上的大 hash 值当 slot。
比如计算规则:

implementation slot = keccak256("eip1967.proxy.implementation") - 1
admin slot = keccak256("eip1967.proxy.admin") - 1

开发者在合约里自己定义的storage变量正常情况下是从0、1、2、3...这样顺序往上走的,只要不是故意很难碰撞到这个key。