Foundry 新特性详解:`cast call` 输出解码后的 Revert 数据(Decoded Revert Data)
2026/9/16 1:10:39 网站建设 项目流程

Foundry 新特性详解:cast call输出解码后的 Revert 数据(Decoded Revert Data)

【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry

导读

本文围绕 Foundry 仓库 changelog 记录 cast-call-decode-reverts.md 中的一项 patch 级新特性展开:当cast call发起的调用因目标合约 revert 而失败时,只要错误签名已知,CLI 就会在错误输出中直接解码并展示 revert 数据,而不再只是抛出一段难读的原始十六进制。读完本文,你将掌握该特性的使用场景、错误输出的两种形态(已知签名解码 / 在线签名识别)、底层解码链路(RevertDecoder 与签名缓存),以及如何用测试用例验证这一行为。

变更说明:.changelog/cast-call-decode-reverts.md原文仅一句话——"Decoded revert data incast callerror output when the error signature is known."(当错误签名已知时,在cast call错误输出中解码 revert 数据),归类为cast: patch(补丁级变更,不破坏现有命令行接口)。本文以该行为主骨架,结合仓库源码与测试展开。

一、特性背景:为什么需要解码 revert 数据

cast call本质上是对目标合约执行一次eth_call模拟调用(见 crates/cast/src/cmd/call.rs 中run_with_network_and_opts的 provider 调用逻辑)。当合约执行revert("...")或抛出自定义错误(custom error)时,RPC 节点返回的错误响应里携带一段 ABI 编码的 revert data:

  • Solidity 内置的Error(string)编码为0x08c379a0+ 字符串的 ABI 编码;
  • 内置Panic(uint256)编码为0x4e487b71+ uint256 码值;
  • 自定义错误(如RequestLimitExceeded(uint256,uint256))编码为其 4 字节选择器 + 参数的 ABI 编码。

在没有解码能力之前,cast call只能把这段原始字节原样打印,用户必须自己用cast decode之类的工具二次处理,才能看懂合约到底因为什么原因回滚。本特性正是为了消除这个心智负担:在错误输出阶段直接给出人可读的 revert 原因

二、核心实现:错误输出中的解码逻辑

2.1 捕获 revert data 的入口

在 crates/cast/src/cmd/call.rs 的 provider 调用错误分支中:

let res = match call.await { Ok(res) => res, Err(err) => { let data = err.as_error_resp().and_then(|payload| payload.as_revert_data()); if let Some(data) = data { let decoded = match RevertDecoder::new().maybe_decode_known(&data) { Some(decoded) => Some(decoded), None => crate::tx::decode_custom_error(&data).await.ok().flatten(), }; if let Some(decoded) = decoded { return Err(err).wrap_err(format!("execution reverted: {decoded}")); } } return Err(err.into()); } };

关键点拆解:

  1. 提取 revert dataerr.as_error_resp().and_then(|payload| payload.as_revert_data())从 JSON-RPC 错误响应中取出data字段对应的字节序列;
  2. 两级解码策略:先尝试用RevertDecoder::new().maybe_decode_known(&data)做本地已知签名解码;失败后再调用crate::tx::decode_custom_error(&data)走在线签名识别(见下文第三节);
  3. 错误包装:一旦解码成功,就用execution reverted: {decoded}作为错误上下文信息包裹原始错误,用户最终在 stderr 中看到的就是可读的失败原因。

这正好呼应 changelog 中 "when the error signature is known" 的前提:本地解码与在线识别两条路径共同决定"签名是否已知"。

2.2 本地解码核心:RevertDecoder

RevertDecoder定义在 crates/evm/core/src/decode.rs,是 Foundry EVM 核心中通用的 revert 解码工具。它的核心方法是maybe_decode_known(decode.rs),按优先级依次尝试:

  1. SolidityError(string):通过RevertReason::decode(err)匹配0x08c379a0前缀,成功则直接返回字符串内容(剥离revert:前缀);
  2. SolidityPanic(uint256)Vm的 cheatcode 错误:通过ContractError::<Vm::VmErrors>::abi_decode(err)统一解码;
  3. 自定义错误:取前 4 字节作为选择器,在内部errors: HashMap<Selector, Vec<Error>>索引中查找,命中后用error.abi_decode_input(data)解码参数,输出为ErrorName(arg1, arg2, ...)形式。

注意maybe_decode_knownmaybe_decode(decode.rs)的区别:后者在已知签名失败后还会回退到"非空字符串"解码和"泛化自定义错误"表示(如custom error 0xXXXX: <data>),而前者返回已知签名的解码结果,这正是cast call想要的语义——签名未知就不妄加猜测,留给在线识别路径处理。

RevertDecoder也提供了with_abis/with_abi/push_error方法,可把项目的JsonAbi中的自定义错误批量注册进索引,方便复用同一套解码逻辑(例如 forge 测试结果解码)。

2.3 在线识别兜底:decode_custom_error

当本地maybe_decode_known返回None时,cast call会调用 crates/cast/src/tx.rs 中的decode_custom_error

pub(crate) async fn decode_custom_error(data: &[u8]) -> Result<Option<String>> { let Some(selector) = data.get(..4) else { return Ok(None) }; let Some(known_error) = SignaturesIdentifier::new(false)?.identify_error(selector.try_into().unwrap()).await else { return Ok(None); }; let mut decoded_error = known_error.name.clone(); if !known_error.inputs.is_empty() && let Ok(error) = known_error.decode_error(data) { write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?; } Ok(Some(decoded_error)) }

它的工作方式是:取 revert data 的前 4 字节选择器,交给SignaturesIdentifier(签名识别器,来自 foundry-common 的选择器解析基础设施)从本地签名缓存中识别错误签名;识别到后,再用该签名解码参数并格式化为Name(arg1, arg2)。从源码看,SignaturesIdentifier::new(false)的布尔参数控制是否强制联网——这也是为什么测试中设置FOUNDRY_OFFLINE=true依然能离线解码(见第四节)。

三、签名从哪来:本地缓存与识别链

特性标题里的 "when the error signature is known" 对应的签名来源有两处:

  • 本地 ABI 注册RevertDecoder可以通过with_abis批量注入合约 ABI 中的错误定义,但cast call的默认路径使用RevertDecoder::new()(空解码器),此时内置只认识 Solidity 标准错误(Error(string)Panic(uint256))和Vmcheatcode 错误;
  • 签名缓存(signature cache)decode_custom_error依赖SignaturesIdentifier读取用户主目录下的.foundry/cache/signatures缓存文件,缓存中记录了"选择器 → 签名"的映射,既有函数也有错误。这正是 crates/cast/tests/cli/call.rs 测试里预先写入缓存、再离线验证解码的原因。

从源码结构可以推断,该特性优先保证离线、确定性:只要签名缓存里已有该选择器对应的错误签名,即使 RPC 不可达(FOUNDRY_OFFLINE=true)也能完成解码。

四、验证行为:测试用例如何证明该特性

仓库中的集成测试 crates/cast/tests/cli/call.rs 完整覆盖了这一特性,测试cast_call_decodes_custom_error的核心步骤:

  1. 启动 anvil 测试节点;
  2. 构造自定义错误RequestLimitExceeded(uint256,uint256),取其 4 字节选择器,ABI 编码参数(5, 3),拼出 revert payload;
  3. 手工生成一段运行时代码,把该 payload 拷入内存后revert0xfd),并通过--override-code把它覆盖到目标地址上;
  4. 预置签名缓存:在$HOME/.foundry/cache/signatures中写入{"errors": {"<selector>": "RequestLimitExceeded(uint256,uint256)"}},并设置HOME环境变量隔离缓存目录;
  5. FOUNDRY_OFFLINE=true离线执行cast call <addr> --data 0x --override-code ... --rpc-url ...,断言命令失败且 stderr 输出:
Error: execution reverted: RequestLimitExceeded(5, 3)

该测试同时验证了普通终端输出(非 JSON 模式)与--json模式(错误信息进入errors数组,消息为execution reverted: RequestLimitExceeded(5, 3),并带cast.error.context上下文)两种呈现形态,说明特性对两种输出通道都生效。

五、使用场景与输出对比

5.1 特性生效的典型场景

  • 调用只读函数(如balanceOf)时目标合约因业务逻辑revert,之前只能看到execution reverted, data: "0x08c379a0..."这样的原始串;
  • 合约抛出自定义错误(如RequestLimitExceeded(5, 3)),且签名已被 Foundry 缓存(或能通过签名识别获得)时,错误信息直接可读;
  • 配合--rpc-url--block--from等常规参数使用,不影响cast call原有的所有调用能力(见 CallArgs 定义)。

5.2 修复前后的输出对比(示意)

修复前(仅原始数据):

Error: server returned an error response: error code 3: execution reverted, data: "0x08c379a0...5472616e73616374696f6e20746f6f206f6c64"

修复后(签名已知时):

Error: execution reverted: Transaction too old

对于自定义错误:

Error: execution reverted: RequestLimitExceeded(5, 3)

上述第二个示例的输出文本直接取自仓库测试断言 crates/cast/tests/cli/call.rs;第一个示例的十六进制片段为receipt测试中出现的典型Error(string)编码形态,此处仅作格式示意。

六、边界与限制

  • 签名已知是前提:若maybe_decode_knowndecode_custom_error都识别失败,错误仍会以原始 revert data 形式呈现(return Err(err.into())),不会强行猜测;
  • Error(string)/Panic(uint256)始终可解码:这两类 Solidity 内置错误不需要任何签名缓存,属于"签名必然已知"的情况;
  • 依赖签名缓存目录:自定义错误的在线识别依赖.foundry/cache/signatures;首次使用而未缓存时可能需要联网获取签名(SignaturesIdentifier负责该逻辑),测试中通过预置缓存并设置FOUNDRY_OFFLINE保证了离线确定性;
  • 适用范围:本特性作用于cast calleth_call错误路径;--trace/--debug-trace-call走的是 trace 渲染路径(call.rs),revert 的呈现方式由handle_traces负责,不在本次变更范围内。

七、总结

.changelog/cast-call-decode-reverts.md记录的这一 patch 级特性,为cast call的日常排障体验补上了关键一环:错误签名已知时,revert 原因直接以可读文本输出。实现上,它复用了 EVM 核心的RevertDecoder(内置标准错误解码)与 cast 自身的签名识别器(缓存 + 在线兜底),并由cast_call_decodes_custom_error集成测试锁定行为。对于脚本化调用合约、CI 排障或日常合约交互,这个改动让cast call的失败信息从"需要二次解码的十六进制"变成了"一眼可懂的错误原因"。

相关代码与文档索引

  • 变更记录:.changelog/cast-call-decode-reverts.md
  • cast call命令实现:crates/cast/src/cmd/call.rs
  • revert 解码核心(RevertDecoder/maybe_decode_known):crates/evm/core/src/decode.rs
  • 在线签名识别兜底(decode_custom_error):crates/cast/src/tx.rs
  • 集成测试(离线解码自定义错误):crates/cast/tests/cli/call.rs

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询