Solidity 版本演进与编译器技术变革全景解读:从 Changelog 看智能合约语言的十年发展脉络
Solidity 版本演进与编译器技术变革全景解读:从 Changelog 看智能合约语言的十年发展脉络
导读
本文以 Solidity 官方仓库根目录的 Changelog.md(共 2980 行,覆盖 0.1.0 至 0.8.37 全部版本记录)为绝对主体,系统梳理该智能合约编程语言在语言特性、编译器架构、EVM 版本支持、形式化验证与构建系统等方面的完整演进脉络。读完本文,你将掌握 Solidity 各里程碑版本的核心变化、via-ir 管线的成熟过程、Yul 优化器与 SMTChecker 的能力边界,以及如何把 Changelog 中的每一条记录与仓库源码一一对应,用于实际的合约开发、编译器排查与升级决策。
一、Changelog 的结构与定位:版本史即技术史
Solidity 的 Changelog 采用"倒序"组织:最新版本(当前为 0.8.37 (unreleased))在最顶部,最早的 0.1.0 (2015-07-10) 在最底部。每个版本条目内部按固定分类排列,这套分类本身就是编译器工程实践的浓缩:
- Important Bugfixes:会导致链上数据损坏或错误行为的严重缺陷,需要优先升级;
- Language Features:Solidity 语言本身的语法与语义扩展;
- Compiler Features:命令行接口(CLI)、Standard JSON 接口、代码生成器、优化器、SMTChecker、LSP 等编译器侧能力;
- AST Changes:JSON AST 输出结构的变化,影响所有依赖 AST 的工具链;
- Build System / Solc-Js:构建系统、依赖与 JavaScript 封装(
solc-js)的变化。
当前仓库的版本号在 CMakeLists.txt 中定义为 set(PROJECT_VERSION "0.8.37"),版本字符串由 libsolidity/interface/Version.h 中的 VersionString / VersionStringStrict / VersionCompactBytes 等变量承载,并会被嵌入到编译产物与元数据中(Changelog 0.5.9 条目即记载了"Assembler: Encode the compiler version in the deployed bytecode")。
二、语言特性演进主线:从脚本雏形到现代智能合约语言
将 Changelog 中的 Language Features 纵向串联,可以清晰看到 Solidity 语言能力的三条主线。
2.1 类型系统与数据位置模型(0.1.x → 0.5.0)
早期版本(0.1.0~0.4.x)逐步引入映射键、动态数组 push()、元组表达式与多重赋值、bytes/string 互转、枚举与结构体等基础能力。0.4.0 引入 payable 修饰符与版本 pragma;0.5.0 是一次系统性收紧:要求所有引用类型显式声明数据位置(storage/memory/calldata)、区分 address 与 address payable、引入 abi.decode、禁止 var 关键字与 throw 语句。0.6.0 进一步引入 fallback()/receive() 拆分、virtual/override 关键字与 try/catch;0.7.0 删除 now(改用 block.timestamp)、finney/szabo 单位,并引入 unicode"..." 字符串字面量。
2.2 存储与链上数据能力(0.6.0 → 0.8.37)
- 不可变变量:0.6.5 引入
immutable,值直接内联进代码;0.8.21 放宽初始化限制——构造期任意时刻可读写,不再强制显式初始化。 - 瞬态存储(transient storage):0.8.24 引入 Yul 内建
tload()/tstore();0.8.27 接受transient数据位置的声明(仅解析);0.8.28 完全支持值类型瞬态存储状态变量;0.8.34 修复了一个可能导致清除普通存储变量而非瞬态变量的严重缺陷。对应文档见 docs/transient-storage.rst。 - 自定义存储布局:0.8.29 允许将合约存储重定位到任意位置;0.8.31 允许在基槽表达式中使用
constant状态变量;0.8.35 新增基于 ERC-7201 公式计算存储命名空间基槽的内建函数,配套文档见 docs/custom-storage-layout.rst。 - EIP 能力跟随:
block.basefee(0.8.7)、blobhash/block.blobbasefee/mcopy(0.8.24)、clz内建(0.8.31)等,始终与以太坊硬分叉节奏同步。
2.3 错误处理与 ABI 编码
0.8.4 引入 error 自定义错误与 revert 语句;0.8.26 增加 require(bool, Error) 重载(0.8.27 起在 legacy 管线也可用);0.8.13 修复 abi.encodeCall 中字面量编码问题,0.8.12 起 ContractName.functionName 也可直接用于 abi.encodeCall。ABI 编码器方面,0.8.0 默认启用 ABI coder v2,0.8.31 起对 ABI coder v1 发出弃用警告。
三、编译器架构演进:legacy 与 via-IR 双管线
Changelog 中最具工程价值的主线是代码生成架构从"直接生成 EVM 汇编"到"经由 Yul 中间表示(IR)"的迁移:
- 0.5.0 起:
--yul/--strict-assembly支持独立汇编输入,Yul 优化器逐步成型; - 0.7.5:新增
--experimental-via-ir实验开关,Standard JSON 对应settings.viaIR; - 0.8.13:
--via-ir取代实验命名,经 Yul IR 编译"不再标记为实验性"; - 0.8.21:默认优化序列下,堆栈到内存搬移(stack-to-memory mover)在可行时默认启用,显著缓解 stack-too-deep;
- 0.8.35:引入实验性 SSA CFG codegen(
ssa-cfg),0.8.36 起该模式可将栈值溢出到内存以避免 stack-too-deep 错误。
Yul 优化器在仓库中对应 libyul/optimiser/ 目录,包含 Suite.cpp(优化步骤序列编排)、FullInliner、CommonSubexpressionEliminator、ExpressionSimplifier、UnusedStoreEliminator 等数十个步骤。Changelog 中大量条目直接指向这些步骤的修复,例如 0.8.36 修复"调用图环检测未能将部分互递归函数识别为递归",对应 CallGraphGenerator.cpp;0.8.31 修复 ExpressionSimplifier 将越界变量代入产生非法 Yul 的问题。用户可通过 CLI 的 --yul-optimizations(0.6.7 引入)或 Standard JSON 的 optimizer.details.yulDetails.optimizerSteps 自定义步骤序列,0.8.18 起可用冒号 : 分隔并替换内置的清理序列。
另一个贯穿多版本的焦点是确定性(determinism):0.8.22、0.8.26、0.8.27 多次修复"字节码依赖文件发现顺序/AST ID"的问题,确保同一源码无论附带多少无关文件都能产出语义一致(甚至完全一致)的字节码——这对可复现构建至关重要。
四、EVM 版本支持与默认值变迁
Changelog 完整记录了 Solidity 对以太坊 EVM 版本的跟随节奏(每条均同时更新 CLI 的 --evm-version 与 Standard JSON 的 settings.evmVersion):
| 版本 | 支持的 EVM 版本 | 默认值变更 |
|---|---|---|
| 0.4.21 | homestead / byzantium | byzantium |
| 0.5.5 | petersburg | petersburg |
| 0.5.14 | istanbul | istanbul |
| 0.6.0 | — | istanbul |
| 0.8.5 | berlin | berlin |
| 0.8.7 | london | london |
| 0.8.18 | paris | paris(引入 block.prevrandao,弃用 block.difficulty) |
| 0.8.20 | shanghai | shanghai(PUSH0 优化,见 0.8.27 的 Constant Optimizer / Peephole 条目) |
| 0.8.25 | cancun | cancun(MCOPY 复制字节数组、tload/tstore) |
| 0.8.30 | — | prague |
| 0.8.31 | — | osaka |
| 0.8.35/0.8.36 | @future 实验版本、Amsterdam |
— |
0.8.22 起正式弃用 homestead、tangerineWhistle、spuriousDragon、byzantium 四个早期版本。EVM 版本解析逻辑位于 liblangutil/EVMVersion.cpp,0.8.36 的 "Amsterdam" 支持与 0.8.35 的实验性 @future 版本均在此落地。
五、SMTChecker:从实验插件到默认关闭的正式能力
Changelog 显示 SMTChecker 的演进轨迹:
- 0.4.16:首次出现实验性 Z3 集成;0.5.x 逐步支持消息/交易/区块变量、映射、数组、数学与密码学函数;
- 0.6.0~0.6.x:支持继承、构造函数、修饰符、循环(CHC 引擎)等复杂结构;
- 0.7.4:CLI 引入
--model-checker-engine(all/bmc/chc/none);0.8.1 增加--model-checker-targets(underflow,overflow,assert等); - 0.8.4:弃用
pragma experimental SMTChecker;,默认引擎改为none;0.8.20 增加--model-checker-bmc-loop-iterations与--model-checker-show-proved-safe;0.8.18 起z3成为 BMC/CHC 默认求解器并支持 Eldarica 作为 CHC 的 Horn 求解器; - 0.8.29:Z3 从构建依赖改为运行时依赖;0.8.37(未发布):对 BMC 引擎发出弃用警告,标志着 CHC(约束满足 + 归纳不变量)成为推荐方向。
仓库侧对应实现集中在 libsolidity/formal/(ModelChecker.cpp、BMC.cpp、CHC.cpp、SMTEncoder.cpp)与 libsmtutil/(求解器抽象层)。0.8.30 修复了"仅选择部分合约分析时 --model-checker-contracts 导致错误分析"的问题,说明其支持按合约粒度控制分析范围。
六、六次重大破坏性版本:升级决策的锚点
Changelog 中明确标注 Breaking Changes 的版本是升级规划的关键节点:
- 0.4.0:强制版本 pragma、
payable修饰符、修饰符必须含_、除零/创建失败抛异常; - 0.5.0:要求显式数据位置与可见性、
address/address payable分离、abi.encodePacked取代裸keccak256(a,b,c)、移除var/throw/sha3/suicide别名(条目附有 "How to update your code" 迁移清单); - 0.6.0:
fallback()/receive()拆分、virtual/override、try/catch、状态变量遮蔽禁止、push(element)不再返回长度; - 0.7.0:移除
now、finney/szabo、pc(),内联汇编.使用规则收紧; - 0.8.0:默认启用 ABI coder v2 与 checked arithmetic(可用
unchecked { ... }关闭)、断言失败改用Panic(uint256)revert、幂运算改右结合、移除log0~log4等。
0.8.0 之后的 0.8.x 系列均为非破坏性演进,因此若你的合约停留在 0.7.x,迁移到 0.8.0 是最关键的一步;此后可放心跟随 0.8.x 补丁升级以获取 Important Bugfixes(如 0.8.32 修复存储数组跨界清除、0.8.16 修复 calldata 元组 ABI 编码数据损坏、0.8.4 修复 ABI 解码器 v2 内存数据依赖问题)。
七、近期 0.8.3x 特性聚焦:实验模式与新编译后端
0.8.35(2026-04-29)是一个密集的功能版本,值得重点解读:
- 实验模式(experimental mode):新增
--experimentalCLI 标志与settings.experimentalStandard JSON 设置,将generic-solidity、lsp、ethdebug、eof、evm、ast-import、evmasm-import、ir-ast、ssa-cfg等实验特性统一收敛到实验模式下,并在 JSON/CBOR 元数据中记录实验模式状态; - ethdebug 输出重构:以
--ethdebug-resources、--ethdebug-compilation、--ethdebug-program、--ethdebug-program-runtime取代旧的--ethdebug/--ethdebug-runtime,且请求资源格式不再强制触发完整字节码编译(相关实现见 libevmasm/Ethdebug.cpp); - 标识符保留警告:
at、error、layout、leave、super、transient、this等标识符未来可能升级为关键字,提前给出警告,方便社区逐步规避。
0.8.36 则移除了实验性 EOF(EVM Object Format)后端,并新增 Amsterdam EVM 版本支持——再次印证 Solidity"跟随以太坊路线图、及时清理实验积压"的发布哲学。
八、构建系统与发布渠道:deps 目录背后的工程决策
Build System 类条目记录了仓库本身的工程化演进:
- 依赖管理:0.8.26 用 nlohmann::json 替换 jsoncpp(对应 libsolutil/JSON.cpp);0.8.27 起改用 git submodules 管理 nlohmann-json、fmtlib、range-v3(见仓库根目录 deps/ 下的三个子模块目录与 cmake/submodules.cmake);
- C++ 标准与工具链:0.8.29 从 C++17 切换到 C++20,0.8.31 提高 Boost(≥1.83.0)、GCC(≥13.3)、Clang(≥18.1.3)的最低版本要求,并修复 GCC<14 + Boost<1.75 下
boost::rational比较的无限递归; - 发布渠道:0.8.31 停用 Ubuntu PPA 二进制分发渠道,并启用 Linux arm64 测试与发布二进制;0.8.29 起 Linux 发布构建恢复完全静态链接(不依赖 glibc);
- 前端打包:solc-js 在 0.8.12 移植到 TypeScript(要求 Node.js ≥10,0.8.29 起要求 ≥12),emscripten 构建在 0.8.12 起将内嵌 wasm 用 LZ4 压缩。
九、如何用仓库源码验证 Changelog 记录
每条 Changelog 记录都可以在源码中找到落点,这里给出几条可复现的验证路径:
- 版本号一致性:
PROJECT_VERSION "0.8.37"定义于 CMakeLists.txt,与 Changelog 顶部0.8.37 (unreleased)对应,构建信息模板见 cmake/templates/BuildInfo.h.in; - 0.8.36 的存储布局警告修复:条目指向
PostTypeContractLevelChecker,对应源码 libsolidity/analysis/PostTypeContractLevelChecker.cpp,警告涉及"基槽过于接近存储末尾"; - Yul 优化器步骤:所有
Yul Optimizer条目(如 0.8.34 精简默认步骤序列、0.8.26 更快的新默认序列)可在 libyul/optimiser/Suite.cpp 中看到步骤注册与排序实现; - SMTChecker 引擎:BMC/CHC 引擎与验证目标定义见 libsolidity/formal/BMC.cpp、libsolidity/formal/CHC.cpp 与 libsolidity/formal/ModelCheckerSettings.h;
- 实验模式:
settings.experimental与--experimental的解析逻辑位于 solc/CommandLineParser.cpp 与 libsolidity/interface/StandardCompiler.cpp。
十、实践建议:把 Changelog 当作升级与排障手册
- 升级优先级:优先阅读每个版本的 Important Bugfixes 小节,其中 0.8.16、0.8.32、0.8.34、0.8.36 均涉及可能造成链上数据损坏或语义错误的缺陷,处于这些版本之前的项目应尽快升级;
- 特性启用:如需瞬态存储、自定义存储布局、ERC-7201 命名空间等能力,注意其引入版本(分别为 0.8.28、0.8.29、0.8.35)与配套文档 docs/transient-storage.rst、docs/custom-storage-layout.rst;
- 实验特性隔离:0.8.35 之后,SSA CFG、ethdebug 等实验特性需要显式
--experimental开关,生产环境不应依赖实验模式; - 字节码确定性:涉及部署与可复现构建的团队,应关注 0.8.21~0.8.27 期间关于字节码确定性的一系列修复,并在 CI 中使用固定编译器版本。
结语
一份 Changelog 的本质是编译器设计决策的时间线。通过本文梳理可以发现,Solidity 的每一次"语言特性新增"背后都有对应的"编译器能力重构"(via-IR、Yul 优化器、SSA CFG),每一次"EVM 版本支持"都与以太坊升级路线图严格对齐,而 SMTChecker、LSP、Standard JSON 等开发者基础设施则在十余年间从实验走向稳定。当你在合约开发中遇到难以解释的行为差异时,回到 Changelog.md 按版本号定位对应条目,再顺着上述源码路径追到实现层,往往能最快找到答案。