ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Foundry Chisel 新特性:用 `$_` 复用上一次求值结果,让 REPL 源码重放保持可执行

Foundry Chisel 新特性:用 `$_` 复用上一次求值结果,让 REPL 源码重放保持可执行 Foundry Chisel 新特性用$_复用上一次求值结果让 REPL 源码重放保持可执行【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry导读Chisel 是 Foundry 内置的 Solidity REPL允许开发者逐行输入表达式与语句并在真实 EVM 环境中即时求值。本篇文章围绕 Foundry changelog 片段 .changelog/chisel-last-result.md 引入的minor级新特性展开Chisel 新增$_占位符用于复用上一次求值last evaluated result的结果且!source、!edit、!save、!export四个命令会使用该结果的 ABI 解码展开形式从而保证被保存、导出或编辑后重放的源码依然是自包含、可直接编译执行的。读完本文你将掌握$_的完整用法、边界行为、底层预处理实现原理以及该特性与 Chisel 会话管理命令的配合方式。特性概述$_是什么该 changelog 片段的完整正文如下Added$_for reusing the last evaluated Chisel result.!source,!edit,!save, and!exportuse its ABI-decoding expansion so replayed source remains executable.拆解成三个要点$_是上一次求值结果的占位符在 Chisel 中输入$_会被替换为上一次成功求值表达式的值替换形式是 ABI 解码展开$_不是被替换成普通的字面量而是一个完整的abi.decode(hex..., (类型))表达式!source、!edit、!save、!export受益于这种展开这些命令会输出或持久化当前会话源码由于$_在进入命令/编译管线前就已经被展开成自包含的可执行表达式重放replay保存的源码时不再依赖 REPL 内部状态依然可以正常编译运行。从版本语义看该条目在 frontmatter 中声明了chisel: minor意味着这是一个向后兼容的新增能力按照 .changelog/README.md 描述的 changelog 约定minor表示该变更会随 Chisel 下一个 minor 版本发布。使用示例从表达式到变量与字符串官方集成测试 crates/chisel/tests/it/repl/mod.rs 中的last_result用例完整展示了$_的三种典型用法➜ type(uint256).max Type: uint256 ├ Decimal: 115792089237316195423570985008687907853269984665640564039457584007913129639935 ➜ uint256 MAX $_ ➜ MAX Type: uint256 ├ Decimal: 115792089237316195423570985008687907853269984665640564039457584007913129639935➜ uint256 value 1 ➜ value 2 ➜ uint256 assigned $_ ➜ assigned Type: uint256 ├ Decimal: 2➜ hello Type: string ├ UTF-8: hello ➜ string memory greeting $_ ➜ greeting Type: string ├ UTF-8: hello从中可以归纳出$_的实用规律覆盖任意可求值表达式无论是type(uint256).max这样的类型常量、value 2这样的赋值语句其求值结果是被赋的值还是字符串字面量hello都能被$_捕获可出现在声明右侧uint256 MAX $_、string memory greeting $_这种取上一次结果做初始化是最典型的用法$_会被展开成带类型的 ABI 解码表达式因此类型信息得以保留类型由上一次表达式推断$_展开后携带上次结果的具体类型如uint256、string后续使用不需要重新声明类型信息。底层原理一preprocess预处理管线$_的替换发生在 Chisel 的输入分发环节。在 crates/chisel/src/dispatcher.rs 中ChiselDispatcher结构体持有last_result: OptionString字段用于跨输入保留上一次求值结果见 dispatcher.rs每次执行 Solidity 输入前dispatch_solidity会调用preprocess(input, self.last_result.as_deref())对输入做预处理见 dispatcher.rspreprocess使用solar::parse::Cursor对输入做词法级扫描见 dispatcher.rs当某个 token 恰好等于$_时将其原地替换为format!(({last_result}))即用一对括号包住上次结果的 ABI 解码表达式。从实现细节可以确认三个行为边界字符串字面量与注释内的$_不会被替换扫描时只匹配独立的$_token$_字面量或注释中的$_属于其他 token 类别原样保留十六进制地址会顺带做 checksum 处理预处理同时会把 42 长度的十六进制字面量转为 checksummed 地址无上一次结果时直接报错当last_result为None时使用$_会得到no previous result错误见 dispatcher.rs。dispatcher 中的单元测试test_last_result_preprocessing见 dispatcher.rs精确验证了上述行为let result abi.decode(hex\2a\, (uint256)); let (_, input) preprocess(uint256 answer $_;, Some(result)).unwrap(); assert_eq!(input, format!(uint256 answer ({result});)); let literal r#string memory value $_; // $_#; let (_, input) preprocess(literal, Some(result)).unwrap(); assert_eq!(input, literal); assert_eq!(preprocess($_, None).unwrap_err().to_string(), no previous result);即uint256 answer $_;会被改写为uint256 answer (abi.decode(hex2a, (uint256)));而字面量与注释中的$_保持原样。底层原理二last_result的 ABI 解码展开如何生成$_展开后得到的abi.decode(...)表达式是在 crates/chisel/src/executor.rs 的表达式检查inspect流程中生成的其生成链路如下求值时输入表达式会被追加进run()函数体并包进一次abi.encode(input)调用对应源码注释中的bytes memory inspectoor abi.encode(...)模式编译成功后从 EVM 返回的栈顶取出inspectoor在内存中的偏移再读取其长度与原始字节数据见 executor.rs通过expr_to_dyn推断该表达式的动态 Solidity 类型DynSolType并执行ty.abi_decode(data)得到解码后的 token 用于格式化输出见 executor.rs最终生成的last_result字符串形如abi.decode(hex{data}, ({ty}))见 executor.rs并写入InspectResult.last_result字段定义见 executor.rs由 dispatcher 存回self.last_result见 dispatcher.rs。这正是 changelog 中所说ABI-decoding expansion的来源$_携带的是被 ABI 编码的原始字节 精确类型而不是文本字符串因此无论后续把它赋给同类型变量、传给函数还是嵌入到表达式中类型与数值都保持严格一致天然规避了字符串拼接导致的精度丢失或类型不匹配问题。与!source/!edit/!save/!export的配合changelog 特别强调$_的展开使以下四个命令导出的源码保持可执行!source打印当前会话的完整 Solidity 源码!edit在编辑器中打开run()函数体供修改!save [id]将会话源码与状态缓存到磁盘对应save_session实现见 dispatcher.rs!export导出会话源码。其核心价值在于由于$_在输入进入编译管线之前即preprocess阶段就已经被展开为abi.decode(hex..., (type))这样的自包含表达式这四个命令读取到的是展开后的完整源码而非带占位符的原始文本。因此当你!save一个会话后重新!load回来或把!export得到的源码复制到独立.sol文件中编译其中引用的上一次结果不需要 REPL 运行时环境提供——它已经以字节数据 类型的形式固化在源码里重放时可直接编译执行。生命周期与边界行为何时$_会失效last_result是会话内的易失状态集成测试last_result_resets_with_session见 crates/chisel/tests/it/repl/mod.rs验证了以下重置规则!clear清空会话后失效clear_source在清空源码的同时会把self.last_result置为None见 dispatcher.rs此后使用$_报no previous result!load加载其他会话后失效加载新会话同样会重置last_result见 dispatcher.rs防止把旧会话的结果误带入新会话也避免加载的会话中残留陈旧的上次结果引用对应 changelog 相关条目chisel-session-load-trusts-stale-embedded-id所关注的会话状态一致性问题会话启动时不存在新启动的 Chisel 进程没有任何上一次结果首次输入使用$_会直接报错。此外需要注意只有成功的求值才会更新last_result。如果上一次输入编译失败、求值被 revert 或仅包含注释/空白triviaInspectResult.last_result会保持None$_将沿用更早的有效结果或直接报错。小结$_是 Chisel REPL 中一个轻量但实用的状态复用机制它在输入预处理阶段被展开为携带精确类型的abi.decode(...)表达式既能在 REPL 会话内无缝复用上一次求值结果uint256 MAX $_这类写法又能通过 ABI 解码展开保证!source、!edit、!save、!export输出的源码脱离 REPL 状态后依然可编译、可重放。其实现横跨 crates/chisel/src/dispatcher.rs占位符替换与生命周期管理与 crates/chisel/src/executor.rsABI 编码求值与类型推断并由 crates/chisel/tests/it/repl/mod.rs 中的集成测试完整覆盖。对频繁在 Chisel 中做数值验证与原型实验的开发者而言善用$_可以显著减少复制上一次输出、手动声明类型的重复操作。【免费下载链接】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),仅供参考
返回列表