ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Foundry Chisel 内联汇编(Inline Assembly)表达式求值能力详解:从 REPL 输入到 Yul 结果展示的完整链路

2026/9/16 12:52:04 拓冰建站 浏览量
Foundry Chisel 内联汇编(Inline Assembly)表达式求值能力详解:从 REPL 输入到 Yul 结果展示的完整链路 Foundry Chisel 内联汇编Inline Assembly表达式求值能力详解从 REPL 输入到 Yul 结果展示的完整链路【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry导读本文围绕 Foundry 中 Chisel 交互式 Solidity REPL 的一项核心增强——允许 Chisel 检视内联汇编块中的最终表达式final expression——展开。该能力对应仓库中的 changelog 记录 .changelog/chisel-inline-assembly.md属于chisel: minor级别的功能改进。读完本文你将理解 Chisel 如何把assembly { ... }块末尾的 Yul 表达式当作可求值的结果来展示、它与此前仅能展示 Solidity 语句结果的行为有何差异、底层源码如何实现这一逻辑以及对应的测试用例验证方式从而能够熟练使用 Chisel 调试汇编代码片段。功能背景Chisel 是什么Chisel 是 Foundry 提供的交互式 Solidity 运行环境用户可以在其中逐行输入 Solidity 语句、函数定义、!开头的特殊命令并在每次输入后立即看到求值结果例如Decimal: 2、Hex: 0x...等输出。其核心运行机制是每次输入都会被拼接到一个名为REPL的合约中形成run()函数然后编译、部署并在本地 EVM 上执行最终通过源码映射source map定位run()函数最后一条语句对应的程序计数器PC读取执行结束后的栈与内存状态并格式化输出。Chisel 的源码位于 crates/chisel/src其中 executor.rs 负责输入检视inspection与执行source.rs 负责 REPL 会话源码的生成与编译产物的分析。变更内容检视内联汇编块中的最终表达式变更前的问题在早期版本中Chisel 对输入的常规检视路径是把输入包装为bytes memory inspectoor abi.encode(input);追加到源码中通过 ABI 编码拿到结果。但对于内联汇编块inline assembly block例如assembly { add(3, 4) }这种写法在abi.encode(...)包装下无法直接编译旧版 Chisel 无法把块内最后的 Yul 表达式作为结果展示用户不得不手动改成assembly { let x : add(3, 4) }之类的形式再单独检视变量。变更后的行为本次变更后Chisel 允许把assembly { ... }块中的最后一个表达式final expression作为检视结果。也就是说下面这类输入会直接输出表达式的结果assembly { add(3, 4) }执行后 Chisel 会显示Decimal: 7。这一行为在仓库测试用例中有明确验证见 crates/chisel/tests/it/repl/mod.rs 中的inline_assembly_expression测试对应 Issue #4963repl_test!(inline_assembly_expression, |repl| { repl.sendln(uint256 value 1); repl.sendln(assembly { value : 2 add(value, 0) } // trailing comment); repl.expect(Decimal: 2); repl.sendln(value); repl.expect(Decimal: 2); repl.sendln(assembly { let __chisel_yul_result : 3 add(__chisel_yul_result, 0) }); repl.expect(Decimal: 3); repl.sendln(uint256 __chisel_yul_result_1 0); repl.sendln(assembly { add(3, 4) }); repl.expect(Decimal: 7); });从测试可以看到三种典型场景均被支持赋值表达式作为末尾表达式assembly { value : 2 add(value, 0) }把value更新为2并作为结果展示即使后面有// trailing comment也不影响let声明加末尾表达式assembly { let __chisel_yul_result : 3 add(__chisel_yul_result, 0) }结果展示为3纯表达式assembly { add(3, 4) }结果展示为7。底层实现剖析检视入口与 Yul 分支Chisel 的检视入口是SessionSource::inspect见 executor.rs。其常规流程是先把输入包装成bytes memory inspectoor abi.encode(input);尝试构建新的会话源码如果这条常规路径失败内联汇编块无法通过abi.encode包装则会调用专门为 Yul 设计的yul_inspection函数代码注释也明确说明事件和元组等无法被inspectoor编码时会走其他分支而内联汇编则单独处理。yul_inspection的核心逻辑yul_inspection(input, session_source)见 executor.rs通过 solar 解析器把用户输入解析为单条语句parse_stmt然后校验其类型是否为AstStmtKind::Assembly。其处理步骤可概括为定位末尾表达式取汇编块assembly.block.stmts的最后一条语句要求它是yul::StmtKind::Expr即一个表达式语句而非let声明或其他语句类型提取表达式源码片段通过sess.source_map().span_to_source(expr.span)把表达式的 span 还原为输入中的原始文本expression生成唯一结果变量名从__chisel_yul_result、__chisel_yul_result_1、__chisel_yul_result_2… 依次尝试选出既不与用户输入冲突、也不与会话现有源码冲突的名字构造检视输入把原汇编块中的末尾表达式原位替换为{result_var} : {expression}并包装为uint256 {result_var}; {assembly} bytes memory inspectoor abi.encode({result_var});这样就把一个原本无法 ABI 编码的 Yul 表达式改写为先赋值给临时变量、再编码该变量的形式从而复用常规检视路径构造重放输入把末尾表达式替换为pop({expression})用于把该语句持久化写入会话源码时保持语义等价表达式的值被丢弃但副作用保留。结果展示链路构造出的inspector_input会被clone_with_new_line加入会话源码并执行随后通过abi.encode({result_var})得到编码结果再由类型推断与格式化逻辑输出为Decimal: .../Hex: ...等形式。InspectResult见 executor.rs中的replay_input字段用于在展示结果的同时把经过pop(...)改写后的语句持久化到会话中保证后续输入状态连续。配套能力汇编块作为末尾语句的 PC 定位除了末尾表达式检视Chisel 还处理了内联汇编块作为run()函数末尾语句时如何确定最终 PC 的问题两者共同构成对汇编块完整的 REPL 支持。相关逻辑集中在 source.rsfinal_pcsource.rs查找run()函数体最后一条语句若它是HirStmtKind::AssemblyBlock或Err则调用trailing_assembly_last_stmt_span获取具体的 Yul span再通过源码映射source map把该 span 映射为部署字节码中的程序计数器从而知道执行到哪一步停止trailing_assembly_last_stmt_spansource.rs当run()的最后一条语句是汇编块时从块内倒序查找最后一个非let即非VarDecl的 Yul 语句并返回其 span。这是为了让 Chisel 在汇编块结束时选取一个有意义的结束位置而不是落在变量声明上first_yul_return_spansource.rs查找run()内任意汇编块顶层第一个return(...)调用的 span。当 Yul 的return位置比末尾语句更靠前时以return的位置作为最终 PC对应历史 Issue #4617 的场景assembly { mstore(0x0, 0x1337) return(0x0, 0x20) }后再跟 Solidity 语句repl_run_ast_bodysource.rs从 solar AST 中恢复REPL合约run()函数的函数体为上述 Yul 级分析提供 AST 基础。代码注释明确说明其用途是让内联汇编块可以按 Yul 语句粒度被检视。测试验证仓库测试覆盖了内联汇编相关的多个场景均可作为复现与验证依据全部位于 crates/chisel/tests/it/repl/mod.rs测试名场景期望行为inline_assembly_expressionIssue #4963汇编块末尾表达式赋值、let表达式、纯表达式展示表达式求值结果如Decimal: 2、Decimal: 3、Decimal: 7assembly_returnIssue #4617汇编return之后继续执行 Solidity 语句!md内存转储正常无报错assembly_memory_dumpIssue #4938多行汇编块配合内存/栈转储状态保持正确!md可输出内存区间assembly_return_final汇编块为末尾语句且含return同时命中first_yul_return_span与trailing_assembly_last_stmt_span!md正常assembly_no_return_intermediate无return的汇编块作为中间语句后续 Solidity 语句仍正确求值x变为2uninitialized_variables汇编中引用未初始化变量正确展示Hex: 0x0、Data: 0xFFfF...这些测试印证了本文描述的末尾表达式检视与末尾语句 PC 定位两条实现路径且都标注了对应的历史 Issue 编号方便追溯设计动机。使用示例在 Chisel 中可以直接实践该能力$ chisel ➜ uint256 value 1; ➜ assembly { value : 2 add(value, 0) } // 末尾是赋值表达式 Decimal: 2 ➜ value Decimal: 2 ➜ assembly { let x : 3 add(1, 1) } // let 声明 末尾表达式 Decimal: 3 ➜ assembly { add(3, 4) } // 纯 Yul 表达式 Decimal: 7要点小结只取汇编块的最后一条Yul 表达式作为结果若最后一条是let声明等非表达式语句则无法走此检视路径结果变量的命名冲突会被自动规避__chisel_yul_result、__chisel_yul_result_1…展示结果的同时原汇编语句会以pop(...)形式持久化到会话后续状态连续汇编块中的return(...)若出现在末尾语句之前Chisel 会以return位置作为执行停止点保证内存/栈转储结果可预期。小结本次chisel: minor变更让 Chisel 真正具备了直接检视内联汇编末尾表达式的能力补齐了 REPL 调试汇编代码的最后一块拼图。从源码看其实现分为两条互补的链路yul_inspection负责把末尾表达式改写为可 ABI 编码的临时变量赋值以完成求值展示executor.rsfinal_pc/trailing_assembly_last_stmt_span/first_yul_return_span负责在汇编块收尾时确定有意义的执行停止点source.rs并由 crates/chisel/tests/it/repl/mod.rs 中的一组测试用例持续守护。对日常使用 Chisel 调试 Yul 代码的开发者而言这意味着assembly { expr }可以直接获得求值反馈无需再手工声明中间变量。【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考