Ethereum · Smart contract lifecycle

一份合约,
怎样变成链上程序?

源码不会直接在链上运行。它先被编译成多种产物,再由一笔创建交易运行一次性的初始化程序, 最后只把 runtime code 留在合约地址上。理解这条链,才真正理解“部署、调用与升级”。

  • 从零起步
  • 约 40 分钟
  • 4 个交互实验
  • 2026-07 技术基线
LIFECYCLE / 09.02 DETERMINISTIC PIPELINE
human readable contract Box {
  uint value;
  function set(...);
}
compiler artifacts ABI.json
creation bytecode
runtime bytecode
initcode executes once → RETURN(runtime code)
contract creation to: ∅
input: initcode
constructor: once
on-chain account address
runtime code
storage
stable proxy address + storage
implementation slot
pointer
replaceable target implementation
V1 → V2
01 / COMPILE 编译不是只产出一段字节码

ABI 负责解释接口,creation bytecode 负责安装,runtime bytecode 才是部署后每次调用执行的代码。

02 / DEPLOY 部署先执行,再保存

部署交易运行 initcode 与 constructor;只有它们成功返回的 runtime code 才会被保存到新地址。

03 / UPGRADE 升级通常不是覆盖旧代码

代理地址和 storage 保持不变,只把 implementation 指针从 V1 改到 V2,再用 delegatecall 执行新逻辑。

Mental model

先把七个对象分开

初学者最常见的混乱,不是不懂某个词,而是把源码、ABI、字节码、状态和回执都想成“合约”。 它们其实位于不同阶段,承担完全不同的工作。

01 / LOCAL Solidity 源码

给人阅读、审查、测试与编译。源文件本身不会被 EVM 直接执行。

02 / INTERFACE ABI JSON

描述函数、参数、返回值、事件与错误怎样编码。它通常保存在链下。

03 / INSTALLER Creation bytecode

创建阶段的一次性程序;负责初始化并返回最终 runtime code。

04 / CODE Runtime bytecode

部署成功后保存在地址上的代码;以后每次调用都从它开始执行。

05 / STATE Storage

与合约地址绑定、跨交易持续存在的数据。代码按 slot 读取和写入它。

06 / INPUT Calldata

调用输入:通常由 4 字节 selector 与 ABI 编码参数组成。

07 / RESULT Receipt / logs

交易进入区块后的执行摘要与事件日志,不等同于函数返回值。

最重要的边界

ABI 不是可执行代码,也不会自动成为链上的“接口注册表”。 链上合约账户主要关联 balance、nonce、code 与 storage;人类可读源码和 ABI 要靠发布、验证与工具建立对应关系。

你可以把整个生命周期想成安装软件:Solidity 是源项目,编译器生成“说明书”和“安装包”, 部署交易运行安装包,安装包最后把真正的程序留在一个确定地址。之后,用户不再发送函数名; 钱包把人类意图编码为字节,EVM 依据 runtime code 解释这些字节。

这也提前回答了本课最大的悬念:普通合约的 runtime code 部署后没有编辑按钮; “可升级合约”通常从第一天就部署了一个会转发调用的 proxy。升级时改变的是转发目标,而不是把原地址的字节码覆盖掉。

Source

编写:源码定义状态如何改变

智能合约不是“自动执行的协议条款”。更准确地说,它是一段位于地址上的程序: 收到调用后,按确定规则读取输入、检查条件、计算结果,并可能改变共享状态。

LifecycleBox.sol 贯穿本课的最小示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract LifecycleBox {
    error ZeroValue();
    event ValueChanged(
        uint256 indexed oldValue,
        uint256 indexed newValue,
        address indexed caller
    );

    address public immutable creator;
    uint256 public value;

    constructor(uint256 initialValue) {
        if (initialValue == 0) revert ZeroValue();
        creator = msg.sender;
        value = initialValue;
    }

    function set(uint256 newValue) external {
        if (newValue == 0) revert ZeroValue();
        uint256 oldValue = value;
        value = newValue;
        emit ValueChanged(oldValue, newValue, msg.sender);
    }

    function read() external view returns (uint256) {
        return value;
    }
}
STORAGE

状态变量

value 最终映射到 storage slot,跨交易保留。它不是只活在一次函数调用中的局部变量。

CODE

函数与检查

set 定义谁能提交什么输入,以及成功后状态如何变化;失败条件由 revert 表达。

LOG

事件

event 写入便于链下检索的 log。它不是 storage,合约也不能像读变量一样查询历史日志。

constructor 只属于“出生”阶段

constructor 在创建合约时执行一次。它可以写初始 storage、接收 ETH、调用别的合约、发出事件,也可能失败。 部署完成后,constructor 不会进入最终 runtime code,因此以后不能再次调用它。

creator 使用 Solidity 的 immutable:值在 constructor 中确定,随后被嵌入 runtime code。 它通常不占普通 storage slot。注意,这个语言特性只说明一个变量的设置时机,不能把它与“整个合约是否采用代理升级”混为一谈。

写完不等于可信

编译通过只证明语法与类型满足编译器规则,不证明经济设计正确、权限合理或没有漏洞。 单元测试、模糊测试、静态分析、审计与上线后的监控,是生命周期的一部分,而不是部署前可选的装饰。

Compiler

编译:一份源码,变成多种产物

编译器不仅翻译指令,还生成“如何与合约说话”“怎样部署”“怎样调试与验证”的整套材料。 把这些产物分清,后面的部署和调用就会突然变得具体。

Compiler pipeline human source → machine artifacts
01 Source

Solidity 文件、依赖、编译器版本与设置。

02 AST / IR

语法树与可选中间表示,供分析和优化。

03 Creation code

一次性初始化逻辑与 runtime code 模板。

04 Runtime code

真正留在合约地址、响应调用的代码。

05 ABI + metadata

接口、源码映射、文档、存储布局与验证信息。

编译产物拆包器 点击产物,观察它在生命周期中解决什么问题。示例为教学性节选,不是完整编译结果。

OFFLINE LAB

ABI:编码与解码的共同说明书

钱包、前端和其他工具用它把函数、事件、错误与字节互相翻译。ABI 通常在链下使用,本身不执行。

[ { "type": "constructor", "inputs": [{ "name": "initialValue", "type": "uint256" }] }, { "type": "function", "name": "set", "inputs": [{ "name": "newValue", "type": "uint256" }] }, { "type": "function", "name": "read", "stateMutability": "view", "outputs": [{ "type": "uint256" }] }, { "type": "event", "name": "ValueChanged", "inputs": [ ... ] }, { "type": "error", "name": "ZeroValue", "inputs": [] } ]

构造参数为什么不属于普通函数调用?

部署时没有一个可被 selector 选中的 constructor(...) 函数。Solidity 工具把构造参数按 ABI 编码, 追加到 compiler creation bytecode 后面;creation code 自己从这段尾部数据读取参数。

完整 initcode = creation bytecode + ABI_encode(constructor arguments) initcode 执行结果 = RETURN(runtime bytecode)
源码验证 ≠ 形式化验证

区块浏览器的源码验证,是用相同源码、编译器版本和设置重新编译,再与链上 bytecode 比对。 它证明“公布的源码与运行代码相符”,不证明“代码行为一定正确”。后者属于审计、测试与形式化验证的范围。

Deployment

部署:先运行安装程序,再保存代码

“把 bytecode 上传到链上”是方便但不完整的说法。部署本身是一场 EVM 执行: initcode 必须成功运行,并明确返回一段字节,这段返回值才会成为链上的 runtime code。

Creation transaction atomic execution
01 签名交易

to 为空,input 携带完整 initcode。

02 预定地址

根据创建者、nonce 或 CREATE2 公式确定。

03 执行 initcode

建立创建执行上下文并开始消耗 Gas。

04 运行 constructor

写初始 storage、设置 immutable,可能调用外部合约。

05 RETURN code

initcode 返回最终 runtime bytecode。

顶层创建由一笔没有 to 地址的交易触发。需要精确一点:EOA 并不是“执行了 CREATE opcode”; CREATECREATE2 是合约内部创建时使用的 EVM 指令,顶层创建交易由协议按创建语义处理。

constructor 执行期间,最终 runtime code 还没有安装。若 constructor 外部调用“自己”的地址, 不能假设已经能像部署后那样分派到所有函数。创建代码最终 RETURN 的字节才会被写入该地址的 code。

SUCCESS

全部成功

runtime code 被保存;constructor 写入的 storage 和成功日志被保留;顶层创建回执含 contractAddress

REVERT / OOG

任何关键步骤失败

创建原子回滚:没有 runtime code 留在新地址,创建阶段的状态修改与 logs 也不会成为最终结果,但已消耗的 Gas 仍需支付。

当前主网尺寸边界

截至 2026 年 7 月、Glamsterdam 尚未激活时,主网沿用 EIP-170 的 runtime code 上限 24,576 bytes, 以及 EIP-3860 的 initcode 上限 49,152 bytes。未来升级可能调整它们;部署到任何 EVM 链前,都要核对目标链实际启用的规则。

Address derivation

地址:普通创建与 CREATE2

合约地址不是部署后随机分配的。它可以在执行前被确定:普通创建依赖创建者与 nonce; CREATE2 则把 deployer、salt 与完整 initcode 的哈希放进公式。

普通创建: address = last20bytes(keccak256(RLP([creator, creatorNonce]))) CREATE2: address = last20bytes( keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code)) )

CREATE / CREATE2 地址实验 使用 EIP-1014 官方测试向量。这里只展示决定因素,不在浏览器内重新实现 Keccak。

OFFICIAL VECTOR

改变创建方式或 initcode

creator
0xDeployer…
creator nonce
17
address input
RLP([creator, 17])
关键变量
创建者或 nonce 改变 → 地址改变
普通创建 · 概念结果 last20bytes(keccak256(RLP([creator, 17])))

普通创建不看 salt 或 initcode hash;它取决于实际创建者及其创建 nonce。

CREATE2 的三个常见误解

第一,不是“salt 决定地址”。deployer、32 字节 salt 与完整 initcode 缺一不可。 第二,constructor 参数属于完整 initcode,因此同一 creation bytecode 配上不同构造参数,通常会得到不同地址。 第三,可预计算不等于可以覆盖:目标地址已有非空 code 或非零 nonce 时,创建会按碰撞规则失败。

CREATE2 很适合反事实账户、统一部署与“先知道地址、后部署”的工作流,但它本身不是升级机制。 EIP-6780 之后,也不能把“SELFDESTRUCT 清空旧代码,再用 CREATE2 原址重建”当作当前以太坊的通用升级方案。

Call data

调用:ABI 把意图翻译成字节

用户说“调用 set(42)”,交易里却没有这一行文字。真正进入 EVM 的 input 是一串 calldata: 前 4 字节选函数,后面按 ABI 规则编码参数。

function signature = "set(uint256)" selector = first4bytes(keccak256("set(uint256)")) = 0x60fe47b1 calldata = selector ++ ABI_encode(uint256(42))
4 bytes · selector 60fe47b1
32 bytes · uint256 argument 000000000000000000000000000000000000000000000000000000000000002a

函数签名必须使用规范化类型:uint 在签名中写成 uint256,参数之间没有空格; 返回类型不参与 selector。因为 selector 只有 4 字节,不同签名理论上可能碰撞。

ABI 编码不是自描述格式。单看 000...02a,字节本身不会告诉你它是金额、计数还是地址的一部分; 解码者必须先知道对应 ABI。runtime code 的 dispatcher 读取 calldata 前 4 字节, 跳转到匹配函数;没有匹配时可能进入 fallback,空 calldata 收 ETH 时可能进入 receive

eth_call 是排练,状态交易才是演出

比较项 eth_call 状态交易
签名与广播通常不需要需要私钥授权并进入交易传播
进入区块不会被纳入后成为链上历史
永久修改状态不会,执行完即丢弃成功时可以
用户支付网络 Gas不支付链上费用支付实际执行费用
直接结果returndata 或 RPC 错误先得到 tx hash,再查询 receipt
持久 logs不会留下成功路径发出的 logs 会进入 receipt

排练 / 提交实验 初始链上 value = 7。所有操作都只发生在当前页面,不连接钱包或网络。

LOCAL STATE

同一段 set(42) calldata,在模拟与真实交易路径中得到不同的“持久性”。

等待操作。 calldata: 0x60fe47b1…0000002a
7
持久 storage
slot 0 = 7
模拟画面
receipt
尚未提交交易
logs
0 条
两个反直觉点

eth_call 不只可以模拟 view 函数:它也能模拟会写状态的路径,只是不提交结果。 反过来,view 函数若被另一笔链上交易调用,执行它依然会消耗那笔交易的 Gas。

CALL

切换合约上下文

代码、地址、storage 与 msg.sender 都进入被调用合约的常规上下文。

STATICCALL

禁止状态写入

在静态上下文中执行;试图修改状态会失败。常用于只读的合约间调用。

DELEGATECALL

借代码,不换状态主体

执行目标代码,但保留调用者地址、storage、balance、msg.sendermsg.value 上下文。

Execution result

结果:return、receipt、log 与 revert

“交易结果”不是一个单独对象。正常返回的字节、失败数据、区块回执与事件日志属于不同层; 混在一起,会误读区块浏览器,也会写错 DApp。

01 / INPUT Transaction

发送者授权的输入:to、value、calldata、Gas 参数、nonce 与签名等。

02 / SUCCESS BYTES Returndata

当前调用正常结束后返回给直接调用者的字节,可按 ABI 解码。

03 / ERROR BYTES Revert data

失败原因的编码字节;标准 receipt 本身通常不保存这一段。

04 / RECEIPT 执行摘要

status、gasUsed、block、transactionHash、contractAddress 与 logs 等。

05 / LOGS Events

成功路径发出的可检索日志;地址、topics 与 data 共同描述事件。

为什么交易 receipt 看不到 Solidity return value?

合约 A 在同一笔执行中调用合约 B 时,A 可以同步收到 B 的 returndata。 但 EOA 发起的一笔顶层状态交易,标准 receipt 记录的是执行摘要,不包含顶层 Solidity return value。 前端若想预览返回值,通常先用 eth_call 模拟;真正提交后,再观察 storage、logs 或特定读取函数。

失败会回滚什么?

REVERT 会撤销当前失败调用帧及向上传播范围内的状态修改,相应 logs 也被移除。 已经执行的计算仍消耗 Gas,但 REVERT 本身不会像无效 opcode 或耗尽 Gas 那样必然烧掉所有剩余 Gas。

高级 Solidity 调用通常让异常向上传播;低级 calldelegatecallstaticcall 返回 (success, returndata)。调用方可以捕获失败并继续,所以“一个子调用 revert”不必然意味着顶层交易最终 status = 0

事件不是事实来源的替代品

日志便于索引,但合约代码不能像读取 storage 那样读取历史 logs;失败路径的 logs 也不会留下。 关键安全条件必须由当前状态与代码规则保证,不能只依赖“我们曾发出过某个 event”。

Upgradeability

升级:代码没被覆盖,调用路线变了

普通合约地址上的 runtime code 没有原地编辑机制。若系统要延续同一入口与状态, 最常见做法是让用户始终调用 proxy,再由 proxy 用 delegatecall 执行 implementation 的代码。

一句话模型

Proxy 保存身份与状态,Implementation 提供行为。 升级把 proxy 的 implementation slot 从 I1 改为 I2;proxy 地址、balance 与 storage 并未被搬走。

代理剖面实验 观察同一组原始 storage 在安全升级与危险布局下如何被新代码解释。

DELEGATECALL
Proxy P · stable identity

地址与状态主体

address(this)
0xProxy…0902
msg.sender
0xUser…
slot 0
owner = 0xAdmin…
slot 1
value = 42
slot 2
未使用
impl slot
I1
Implementation I1

读取 V1 布局

代码来自
I1 runtime code
slot 0 解释
owner: address
slot 1 解释
value: uint256
slot 2 解释
读取结果
value() → 42

I1 把 slot 0 当 owner、slot 1 当 value。所有读写都发生在 Proxy P 的 storage。

delegatecall 执行要素 实际来源 为什么重要
正在运行的指令Implementation业务逻辑可以随 implementation 指针变化
address(this)Proxy对外始终表现为 proxy 地址
storage / balanceProxy旧状态延续,新代码必须按兼容布局解释
msg.sender原始调用者权限检查看到的仍是用户或上游合约
msg.value原始调用新逻辑在 proxy 上下文处理 ETH
event emitting addressProxy 上下文日志通常显示 proxy 为发出地址
TRANSPARENT

Transparent Proxy

管理员调用升级接口,普通用户调用总是委托给实现;以调用者身份区分管理与业务分派,避免常见 selector 冲突。

UUPS

UUPS

升级机制主要位于 implementation;新实现必须保留安全升级能力,并严格限制 _authorizeUpgrade

BEACON

Beacon Proxy

多个 proxy 读取同一个 beacon 指向的实现;改 beacon 可同时影响一组实例,效率更高,爆炸半径也更大。

不是所有合约都应该可升级

不采用 proxy 时,也可以部署 V2 到新地址、迁移资产或状态、更新前端与注册表,并保留 V1 作为历史。 这种路线牺牲地址连续性,却减少管理员未来改变规则的能力。

可升级性是一笔“信任预算”:它允许修复漏洞和迭代功能,也意味着某个管理员、多签、时间锁或治理系统拥有改变未来逻辑的权力。 用户不只需要审查当前 implementation,还要审查谁能升级、是否有延迟、能否暂停、是否有链上监控与退出窗口。

Upgrade safety

安全:initializer 与 storage layout

代理系统最危险的地方,往往不是 delegatecall 本身,而是“谁来初始化、谁能升级、旧 storage 会被新代码怎样解释”。

为什么 implementation 的 constructor 不够?

Implementation 部署时运行自己的 constructor,写入的是 implementation 自己的上下文; 未来用户通过 proxy 委托执行时,业务状态位于 proxy。于是可升级业务合约通常把初始化逻辑放进一次性的 initialize(),并在部署 proxy 时原子调用。

准确说法

不要简单记成“代理合约不能有 constructor”。准确说法是: 业务 implementation 不能依靠自己的 constructor 初始化 proxy storage;proxy 本身可以在构造阶段设置 implementation,并 delegatecall initializer。

SLOT 0 owner: address

V1 已存在。V2 不能把它改成别的类型或挪走。

SLOT 1 value: uint256

旧数据仍是原始 32 字节;升级不会自动迁移或转换。

SLOT 2 · NEW label: string

安全 V2 通常把新变量追加到兼容位置,并用工具验证布局。

初学阶段可以先记四条:不删除已有变量、不改变已有类型、不重排已有变量、不在它们前面插入新变量。 但“只在末尾追加”也不是无条件安全:变量 packing、继承顺序和父合约新增字段都可能移动 slot。 应使用编译器 storageLayout 与升级验证工具,而不是凭肉眼。

  • 原子初始化:部署 proxy 时同时执行 initializer,避免未初始化窗口被抢占。
  • 一次性保护:initializer 只能成功一次;新增阶段用版本化 reinitializer。
  • 锁住实现:implementation 自身通常在 constructor 中调用 _disableInitializers()
  • 父级完整:所有父合约 initializer 必须调用,而且顺序与继承线性化一致。
  • 布局验证:保留旧版本 build info,自动比较 storage layout,不依赖记忆。
  • 权限最小化:升级权使用合适的多签、时间锁或治理,并监控实现槽变化事件。
  • 升级原子化:需要新字段时,将 upgrade 与 reinitialize 尽量放在同一受控操作中。
  • 锁定边界:清楚记录 proxy、admin/beacon、implementation 地址和用户应交互的入口。
EIP-1967 的作用

Proxy 把 implementation、admin 或 beacon 地址放到约定的特殊 storage slot, 避免与编译器按常规顺序分配的业务变量冲突,也让区块浏览器与监控工具能识别代理关系。 标准 slot 解决“放在哪里”,不替你解决升级授权、布局兼容或恶意实现。

Deactivation

“销毁”:旧教材最容易过时的地方

Solidity 已弃用 selfdestruct,而且 Cancun 激活 EIP-6780 后, 对既有合约调用它通常不再删除 code 与 storage。把它理解成“链上删除键”已经过时。

场景 ETH balance code / storage 结论
历史:Cancun 之前的旧语义 转给 beneficiary 交易结束时可被删除 只用于理解历史,不应作为新设计依据
Cancun 后:既有合约 转给 beneficiary 不删除 代码与 storage 留在地址上
Cancun 后:同一交易中刚创建 转给 beneficiary 保留旧式删除例外 狭窄例外,不是通用升级方案

具体行为由目标链启用了哪个 fork 决定;编译器的 --evm-version 不能改变链上已经采用的共识规则。 即使历史删除发生过,相关交易与代码仍属于链历史,并不等于从所有节点和归档数据中抹掉。

真正需要停用合约时,常见做法是让关键函数依据内部状态 revert,例如 pause / shutdown 模式, 再明确资产退出、权限撤销与前端提示。停用是业务状态机设计,不应寄希望于一个已弃用 opcode。

Synthesis

把完整生命周期走一遍

现在把所有对象重新串起来。你会发现,“智能合约”不是一个文件,而是一条可验证的转换与执行链。

01 编写

定义状态、函数、权限、事件、错误与 constructor。

02 编译

生成 ABI、creation/runtime code、metadata 与 layout。

03 部署

创建交易携带 initcode,运行 constructor。

04 落链

runtime code 与初始 storage 绑定到新地址。

05 调用

ABI 把意图编码为 selector + 参数。

06 执行

EVM 读取 code/storage,产生 return、log 或 revert。

07 演化

部署新地址,或经预设 proxy 指针采用新实现。

IMMUTABLE ROUTE

普通合约

源码 → 编译 → initcode → 创建交易 → constructor → runtime code + storage → calldata 调用 → 新需求时部署新地址。

PROXY ROUTE

代理系统

部署 I1 → 部署 Proxy 并原子 initialize → 用户调用 Proxy → 部署 I2 → 验证布局 → 授权升级并按需 reinitialize。

最终判断

“升级后,原合约代码被覆盖了。”——通常是错的。 典型代理升级改变的是 proxy 的 implementation 指针;proxy 的 code、地址与旧 storage 没有被覆盖。

Check your model

六道题,检查你是否真的分清了

每题都对应一个高频误区。先作答,再看解释;答错并不重要,能定位错误心智模型才重要。

QUESTION 01

部署成功后,哪一段代码会留在合约地址上并响应日常调用?

请选择一个答案。

QUESTION 02

CREATE2 中固定 deployer 与 salt,只改变 constructor 参数,预测地址通常会怎样?

请选择一个答案。

QUESTION 03

eth_call 模拟执行 set(42) 成功后,链上 storage 会发生什么?

请选择一个答案。

QUESTION 04

哪一项通常不在标准 transaction receipt 中?

请选择一个答案。

QUESTION 05

Proxy 通过 delegatecall 执行 I2 时,状态写入哪里?

请选择一个答案。

QUESTION 06

Cancun 后,对一个早已存在的合约执行 SELFDESTRUCT,当前主网通常会怎样?

请选择一个答案。

术语速查

ABI
合约应用二进制接口;规定函数、参数、返回值、事件与错误的编码方式。
creation bytecode
编译器生成的一次性创建程序,尚未附加具体 constructor 参数。
initcode
创建时真正执行的完整输入,通常是 creation bytecode 加 ABI 编码构造参数。
runtime bytecode
initcode 返回并最终保存在合约地址上的代码。
calldata
外部调用的只读输入字节,ABI 调用通常以 4 字节 selector 开头。
returndata
当前调用向直接调用者返回的字节,与 receipt 是不同对象。
receipt
交易入块后的执行摘要,包含 status、Gas、block 信息与 logs。
proxy
持有稳定入口和业务 storage,并把调用委托给 implementation 的合约。
implementation
在代理模式中提供被 delegatecall 执行的业务代码。
initializer
为 proxy storage 执行业务初始化的一次性函数,替代 implementation constructor 的职责。
storage layout
状态变量对应 slot、offset 与类型的规则;升级兼容性的核心。
source verification
重编译源码并与链上 bytecode 比对,证明公布源码与运行代码相符。

一手资料

本课的协议细节以 Ethereum.org、Solidity 官方文档、EIP 与 OpenZeppelin 官方文档为准。 Ethereum 会持续升级;涉及尺寸、Gas 或 opcode 语义时,应再次核对目标链与当前 fork。

  1. Ethereum.org · Introduction to smart contracts 合约的基本定位、权限开放性、编译与组合性。
  2. Ethereum.org · Deploying smart contracts 创建交易、bytecode、Gas 与部署后地址。
  3. Ethereum.org · Transactions 合约创建与调用交易、input、selector 与参数。
  4. Ethereum.org · Verifying smart contracts 源码验证与形式化验证的区别,以及可复现编译。
  5. Solidity · Contracts 创建、constructor、函数可见性、事件与 immutable。
  6. Solidity · Using the compiler ABI、bytecode、deployedBytecode、metadata、source map 与 storageLayout 输出。
  7. Solidity · Contract ABI specification 函数 selector、参数、返回值、事件与错误的编码。
  8. Solidity · Error handling revert、异常传播与低级调用的返回语义。
  9. EIP-1014 · Skinny CREATE2 CREATE2 地址公式、initcode hash 与碰撞规则。
  10. EIP-170 · Contract code size limit 当前 24,576-byte runtime code 上限的来源。
  11. EIP-3860 · Limit and meter initcode 49,152-byte initcode 上限与按字计费。
  12. ERC-1967 · Proxy storage slots implementation、beacon 与 admin 的标准存储槽。
  13. OpenZeppelin · Proxy upgrade pattern delegatecall、transparent proxy 与 selector 冲突。
  14. OpenZeppelin · Writing upgradeable contracts initializer、implementation 锁定与 storage layout 安全。
  15. EIP-6780 · SELFDESTRUCT only in same transaction Cancun 后 SELFDESTRUCT 的当前语义。
  16. Ethereum.org · Glamsterdam 截至 2026 年 7 月仍属即将到来的 H2 2026 升级及候选变化。

Lesson complete · 09.02

你已经能追踪一份合约的完整生命

从源码到编译产物,从 initcode 到 runtime code,从 calldata 到回执, 再到 proxy 与 implementation 的信任边界——这些对象不再是一团“链上代码”。

返回 LOREWORD