Foundry 对 Tempo 浏览器钱包不完整估算提示的保守处理:实现与调用链解析
【免费下载链接】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 条目(.changelog/tempo-browser-estimation-hints.md)展开,讲解 Foundry 如何在 Tempo 网络上,面对浏览器钱包给出的**不完整 gas 估算提示(key_type / key_data)**时采取保守兜底策略:完整提示原样保留,缺失或残缺的提示一律回退到固定尺寸的 WebAuthn 签名占位值。读完本文,你将理解这条foundry-common: patch变更背后的源码实现、四种提示组合的处理规则、它在cast、forge、script中的实际调用位置,以及仓库内配套的单元测试与集成测试验证。
一、变更背景:浏览器钱包估算提示为什么需要"保守处理"
1.1 changelog 条目本身
仓库根目录下.changelog/tempo-browser-estimation-hints.md全文如下:
--- foundry-common: patch --- Handled incomplete Tempo browser-wallet estimation hints conservatively.按照.changelog/README.md中定义的版本契约,每个 changelog 片段用 YAML frontmatter 把工作区包名映射到patch/minor/major,正文则是一条非空的发布说明。这里将foundry-common标记为patch,表示该 crate 获得一次补丁级版本提升,变更内容为"以保守方式处理不完整的 Tempo 浏览器钱包估算提示"。
1.2 为什么要关心估算提示
Tempo 是一个支持账户抽象(AA)与浏览器钱包签名的网络。当用户通过浏览器钱包(例如浏览器扩展配合 WebAuthn 签名)发起交易时,最终签名的类型和长度会直接影响交易的 gas 用量:不同类型签名(Secp256k1 / P256 / WebAuthn)在 calldata 中的长度不同,签名越长,交易的 calldata 与执行开销越大。
在 gas 估算阶段,交易往往尚未真正签名,此时估算请求只能依赖keyType(签名类型)与keyData(签名密钥数据)这两个"提示"字段来估算未来签名占用的字节数。如果提示缺失或不完整,Tempo RPC 端无法准确计算签名部分的大小,可能给出偏小的 gas 估值,最终导致真实交易因 gas 不足而失败。这正是本条变更要解决的问题:在提示不完整时,用保守(偏大)的占位值补全,宁可高估也不低估。
二、源码实现:browser_wallet_gas_estimation_request的补全逻辑
2.1 统一的 trait 接口
crates/common/src/transactions/builder.rs中定义了FoundryTransactionBuildertrait,其中的默认方法即为此功能预留了扩展点:
/// Clone this request and prepare it for browser-wallet gas estimation. /// /// Complete signer hints are preserved. Missing or incomplete hints default to a conservative /// WebAuthn signature size. fn browser_wallet_gas_estimation_request(&self) -> Self where Self: Clone, { self.clone() }非 Tempo 网络直接走默认实现(原样克隆),只有 Tempo 网络覆盖了该方法。
2.2 保守占位值的定义
crates/common/src/transactions/builder.rs第 399~403 行定义了占位常量:
/// Viem's Tempo formatter uses a 1,400-byte WebAuthn placeholder when the signature is not yet /// available. Tempo RPC encodes that size as a two-byte big-endian `keyData` value (`0x0578`). const TEMPO_BROWSER_WEBAUTHN_DATA_SIZE: u16 = 1_400;1_400转成两字节大端序正是0x0578(0x0578 = 1400)。也就是说,Tempo RPC 用keyData编码"签名数据字节数",而浏览器钱包生态(代码注释引用了 viem 的 Tempo Formatter)在签名尚未产生时使用 1400 字节的 WebAuthn 占位。Foundry 直接复用同一尺寸,保证估算请求与真实签名后的交易结构一致。
2.3 四种情况的处理规则
crates/common/src/transactions/builder.rs第 452~466 行是 Tempo 网络的覆盖实现:
fn browser_wallet_gas_estimation_request(&self) -> Self { let mut request = self.clone(); if request.key_type.is_none() { request.key_type = Some(SignatureType::WebAuthn); request.key_data = Some(Bytes::copy_from_slice(&TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes())); } else if matches!(request.key_type, Some(SignatureType::WebAuthn)) && request.key_data.is_none() { request.key_data = Some(Bytes::copy_from_slice(&TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes())); } request.convert_create_to_call(); request }结合crates/common/src/tempo/tests.rs中的四个测试用例,规则可以归纳为下表:
| 估算请求中的提示状态 | 处理行为 | 对应测试 |
|---|---|---|
key_type与key_data均缺失 | 回退为WebAuthn+keyData = 0x0578 | browser_gas_estimation_uses_conservative_webauthn_hint |
key_type为WebAuthn但key_data缺失 | 保留WebAuthn,补填keyData = 0x0578 | browser_gas_estimation_fills_missing_webauthn_key_data |
只有key_data、没有key_type | 视为不完整,整体替换为WebAuthn+0x0578 | browser_gas_estimation_replaces_key_data_without_key_type |
key_type与key_data完整(如 Secp256k1 / P256 / WebAuthn) | 原样保留,不做任何修改 | browser_gas_estimation_preserves_complete_signer_hints |
值得注意的细节:
- 完整提示绝不覆盖。只要
key_type和key_data都齐全,即使是WebAuthn之外的签名类型,也保持原值,避免干扰用户明确指定的签名方案。 key_data无法单独成立。仅有key_data而缺少key_type被判定为"不完整",会连同key_type一起被替换为保守的 WebAuthn 占位。- CREATE 转换同步进行。方法末尾调用
convert_create_to_call(),把合约创建转换为 AA 兼容的calls列表条目,确保估算请求的结构与真实 Tempo AA 交易一致(详见 trait 中该方法的注释)。
2.4 单元测试验证
crates/common/src/tempo/tests.rs的第 28~89 行集中验证了上述规则。以"空提示"用例为例:
#[test] fn browser_gas_estimation_uses_conservative_webauthn_hint() { let request = TempoTransactionRequest { inner: TransactionRequest { /* ... */ }, ..Default::default() }; let estimate_request = request.browser_wallet_gas_estimation_request(); assert_eq!(estimate_request.key_type, Some(SignatureType::WebAuthn)); assert_eq!(estimate_request.key_data, Some(Bytes::from_static(&[0x05, 0x78]))); let json = serde_json::to_value(&estimate_request).unwrap(); assert_eq!(json["keyType"], "webAuthn"); assert_eq!(json["keyData"], "0x0578"); // ... }该测试还断言了 JSON 序列化结果为keyType: "webAuthn"、keyData: "0x0578",从侧面确认了补全后的请求在网络传输中的实际形态;同时断言calls列表正确承载了原to/value/input,而inner.to等字段被清空,验证了 CREATE 转calls的副作用没有破坏原始请求(原始request的key_type、key_data仍为None)。
三、调用链:这条兜底逻辑在哪些命令中生效
通过搜索browser_wallet_gas_estimation_request的调用点,可以确认该逻辑覆盖了 Foundry 的四大交易入口,全部遵循"浏览器签名器 + Tempo 链"的双重条件:
| 入口 | 调用位置 | 触发条件 |
|---|---|---|
cast estimate | crates/cast/src/cmd/estimate.rs#L157 | is_browser为真 |
cast send等走CastTxBuilder的命令 | crates/cast/src/tx.rs#L718 | self.browser && self.chain.is_tempo() |
forge create | crates/forge/src/cmd/create.rs#L551 | browser_signer.is_some() && chain.is_tempo() |
forge script广播阶段 | crates/script/src/broadcast.rs#L1351 | 函数参数tempo_browser为真 |
例如在CastTxBuilder中:
if fill && self.tx.gas_limit().is_none() { let request = if self.browser && self.chain.is_tempo() { self.tx.browser_wallet_gas_estimation_request() } else { self.tx.clone() }; let estimated = Self::estimate_gas(&self.provider, request, self.gas_estimate_multiplier).await?; self.tx.set_gas_limit(estimated); }也就是说:只有 gas limit 尚未设置、且当前是 Tempo 链上的浏览器钱包交易时,才走保守补全路径;其余场景一律使用原始请求,避免引入无谓的占位开销。forge script的estimate_gas还会在估算前reset_gas_limit(),防止 RPC 直接返回请求中已设置的 gas 值而跳过真实估算(见 crates/script/src/broadcast.rs#L1346-L1357)。
此外,crates/anvil/tests/it/tempo.rs#L5888 的 Anvil 集成测试同样使用provider.estimate_gas(request.browser_wallet_gas_estimation_request())来模拟真实 RPC 估算流程,进一步印证了该请求结构是 Tempo 网络上的标准估算形态。
四、为什么要"保守":设计与使用要点
4.1 保守 = 宁高勿低
WebAuthn 在三种签名类型中占位最大(1400 字节),当提示不完整时选择它作为兜底,能确保估算出的 gas 足以覆盖后续任何一种真实签名。这与"精确但可能偏低"相比,牺牲了少量精度,换来了估算结果一定可用的确定性——对浏览器钱包这类"先估算、后签名"的流程而言,这是更安全的设计取向。
4.2 与 viem 生态对齐
占位常量直接对齐了 viem Tempo Formatter 的行为(代码注释中给出了对应源码引用的具体行号)。这意味着无论用户侧用 viem 构造请求、还是用 Foundry 的cast/forge构造请求,在"未签名 → 估算"阶段看到的keyData都是同一份 1400 字节占位,RPC 端无需区分请求来源。
4.3 使用前提与限制
- 该逻辑仅在Tempo 网络(
chain.is_tempo())上生效,其他网络走默认的克隆实现; - 仅当交易尚未设置 gas limit时才会触发估算补全;
- 补全后的请求只用于
estimate_gas调用,不会污染用户原始的tx对象——方法返回的是克隆副本; - 用户如果明确提供了完整的签名提示,则不受此逻辑影响。
五、总结
这条foundry-common: patch变更虽然只有一句话,背后却是一套完整的"补全 + 保留 + 转换"机制:在FoundryTransactionBuilder::browser_wallet_gas_estimation_request中,通过TEMPO_BROWSER_WEBAUTHN_DATA_SIZE = 1400(编码为0x0578)对不完整的浏览器钱包估算提示做保守回退,同时由 crates/common/src/tempo/tests.rs 的四个用例覆盖全部输入组合,并贯通cast estimate、cast send、forge create、forge script四大调用链。对于在 Tempo 网络上开发浏览器钱包集成、或排查"估算 gas 偏低导致交易失败"的开发者来说,这条兜底路径值得重点了解。
【免费下载链接】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),仅供参考