ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Foundry 符号执行改进:无分支饱和乘法与 checked-multiply guard 的形式化证明

Foundry 符号执行改进:无分支饱和乘法与 checked-multiply guard 的形式化证明 Foundry 符号执行改进无分支饱和乘法与 checked-multiply guard 的形式化证明【免费下载链接】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/symbolic-saturating-mul.md变更记录深入解析 Foundry 符号执行symbolic execution模块对无分支branchless饱和乘法与checked-multiply 溢出守卫的形式化证明能力改进。读者将理解这类 EVM 汇编模式在符号执行中的难点、Foundry 如何通过表达式重写与求解器规范化消除多余 SMT 查询以及如何用forge test --symbolic在真实合约上验证这些模式。变更背景一次针对乘法溢出的符号证明 patch.changelog/symbolic-saturating-mul.md记录了该变更的核心内容forge: patch — Improved symbolic proofs for branchless saturating multiplication and checked-multiply guards.即改进了符号执行对无分支饱和乘法和checked-multiply 守卫的证明能力属于forge组件的一个补丁级patch变更。要理解这个 patch 的价值先要明白这两类模式在真实 Solidity 生态中的出现场景饱和乘法saturating multiplication当乘积溢出时返回type(uint).max而非回滚常用于 Uniswap 类 AMM 的价格计算、预言机聚合等宁可钳制也不失败的场景。标准写法通常是带分支的 if-else而无分支写法branchless使用位掩码技巧在assembly块中实现避免条件跳转以降低 gas。checked-multiply 守卫checked-mul guardSolidity 0.8.x 编译器的内置溢出检查即x 0 || (x * y) / x y用于判断x * y是否溢出。它大量出现在经 solc 编译的字节码中是符号执行器必须高频处理的约束形态。问题在于符号执行器会把每条汇编指令翻译成符号表达式树。无分支写法与守卫约束会产生乘法-除法-或-掩码等深嵌套表达式若不做规范化SMT 求解器需要处理高非线性算术导致查询超时或返回错误反例。本次 patch 正是围绕如何把这类表达式化简到可判定形式展开。无分支饱和乘法的符号表达一个经典习语在 Foundry 符号执行器的测试中无分支饱和乘法被构造为如下表达式结构见 crates/evm/symbolic/src/tests.rs 的expression_simplifies_saturating_select_idiomproduct x * y quotient product / x exact (quotient y) safe exact || (x 0) guard (x 0 ? 1 : 0) | (exact ? 1 : 0) # 布尔词形 sub_actual (guard - 1) | product # 溢出时掩码为全 1钳制到 UINT_MAX add_actual (guard UINT_MAX) | product # 另一种掩码形态 expected safe ? product : UINT_MAX # 语义等价的 if-then-else这里展示了两种等价的掩码实现(guard - 1) | product与(guard max) | product。二者在溢出时都让掩码变为全 1、再与乘积做按位或从而把结果钳制到UINT_MAX。该测试断言这两种掩码形态都能被化简为标准的ite(safe, product, max)——即符号表达式树被折叠成一个三元的条件选择节点。对符号执行而言这个折叠至关重要求解器面对ite远比面对乘法、除法、加减、或的组合更容易处理也方便后续路径分支的合并与反例构造。重写保持边界语义极端输入下的正确性保证表达式重写最大的风险是化简出错。无分支饱和乘法的语义边界集中在几个极端输入组合上saturating_mul_rewrite_preserves_boundary_valuescrates/evm/symbolic/src/tests.rs专门覆盖了这些边界(0, UINT_MAX)、(UINT_MAX, 0)一个操作数为 0乘积必为 0不应饱和(UINT_MAX, 1)、(1, UINT_MAX)乘以 1乘积不溢出(UINT_MAX, 2)、(2, UINT_MAX)严格溢出结果必须钳制到UINT_MAX(2^255, 2)、(2, 2^255)恰好跨越 256 位边界的中等溢出。该测试对原始掩码表达式和化简后表达式同时求值并以x_value.checked_mul(y_value).unwrap_or(UINT_MAX)作为期望值逐一比对确保重写前后在所有边界点结果完全一致。这印证了 Foundry 符号执行器对表达式重写采取先验证语义等价、再应用到求解流程的严谨态度。checked-mul guard 的求解器规范化Solidity 的乘法溢出守卫x 0 || (x * y) / x y在符号执行中会翻译成布尔词boolean word上的约束。Foundry 在求解器侧对它做了专门的规范化处理crates/evm/symbolic/src/runtime/solver/hard_arith_fallback.rs 的文档注释明确写出该守卫的语义形态。有界操作数的恒真判定solver_normalizes_checked_mul_guard_for_bounded_operandscrates/evm/symbolic/src/tests.rs构造了两个被 u64::MAX掩码限界的操作数a、b。两个u64的乘积不可能超过 128 位因而永远不会溢出 256 位字。此时规范化函数把guard 0即守卫为假直接化简为常量false——求解器无需任何 SMT 查询即可判定该分支不可达这大幅减少了非线性算术的求解压力。测试辅助函数checked_mul_guard_wordcrates/evm/symbolic/src/tests.rs给出了守卫的完整符号构造方式先判断零操作数再检查(x * y) / x y两者以布尔词形式取或与 solc 生成的字节码语义一一对应。反例分支的本地短路saturating_mul_counterexample_branches_short_circuit_locallycrates/evm/symbolic/src/tests.rs验证了反例构造的本地化对于guard false分支断言饱和结果必须等于UINT_MAX对于guard true分支断言结果必须等于乘积。测试表明这两个约束组合都不可满足is_sat_branch返回 false并且整个过程中smt_queries 0、heuristic_witnesses 0——即纯靠表达式规范化和算术重写就完成了证明完全没有调用底层的 SMT 求解器z3。这意味着当符号执行器走到饱和乘法相关断言时溢出路径的不可达性可以本地判定而不必把复杂的乘除约束抛给求解器从而显著提升证明速度与稳定性。构造性模型为守卫分支直接给出可行赋值对于无法静态判定、必须二分求解的守卫分支Foundry 还提供了构造性模型constructive model机制。checked_mul_guard_branch_modelcrates/evm/symbolic/src/runtime/solver/hard_arith_fallback.rs直接按守卫的三个语义情形分配具体值零析取项x 0为真此时乘积为 0非零精确乘积(x * y) / x y成立结果取乘积本身包装乘积乘积溢出wrapping结果钳制到UINT_MAX两个操作数顺序均可覆盖。模型构造遵循先补全简单的支持约束support constraints原则保证路径上已有的精确操作数值不被语义默认值覆盖且只有在满足全部原始约束时才返回模型。为防御病态表达式该函数还设置了支持访问预算MAX_CHECKED_MUL_SUPPORT_VISITS 256同文件第 115 行超过预算即放弃构造、退回通用求解路径。配套测试覆盖了模型构造的边界情形checked_mul_guard_branch_model_preserves_exact_operand_constraints保留精确操作数约束、checked_mul_guard_branch_model_matches_nested_boolean_guard嵌套布尔守卫、checked_mul_guard_branch_model_completes_original_model_symbols补全原模型符号以及若干拒绝测试——当存在符号哈希赋值、gasleft赋值或非可重放non-replayable变量时构造性模型必须拒绝返回避免生成不可重放的错误反例见 crates/evm/symbolic/src/runtime/solver/hard_arith_fallback.rs 附近的测试模块。端到端验证forge test --symbolic实战上述底层能力最终通过 forge 的符号执行测试入口暴露给用户。仓库集成测试symbolic_proves_branchless_operation_statecrates/forge/tests/cli/test_cmd/symbolic.rs演示了完整流程定义合约SymbolicOperationState其中original函数用带分支的 Solidity描述状态机packed 0返回 0、奇数位返回 3、时间比较返回 1/2在assembly中用一条mul指令实现无分支等价计算assembly { optimized : mul( iszero(iszero(packed)), // packed ! 0 ? 1 : 0 add(and(packed, 1), sub(2, lt(time, shr(1, packed)))) ) }断言optimized original(packed, time)即无分支汇编实现与语义等价的带分支版本完全一致。随后以如下命令运行forge test --symbolic --json --optimize --match-test checkOperationState--symbolic启用符号执行模式--optimize开启表达式优化重写本次 patch 的表达式折叠正是在该路径生效--json输出结构化测试结果--match-test只运行目标测试。该测试要求环境装有 z3z3_available()检查否则跳过。这个用例说明无分支优化的正确性证明正是本次 patch 改进的表达式重写能力在真实场景中的落地——开发者可以放心地把手写汇编优化与原始 Solidity 语义做形式化等价验证而不必依赖手工审查。适用前提与限制本文所述的饱和乘法/checked-mul 规范化能力位于符号执行模块crates/evm/symbolic仅在forge test --symbolic模式下生效普通 EVM 执行路径不涉及这些重写。部分求解器优化依赖 z3 等外部 SMT 求解器无 z3 环境时相关测试与证明路径会跳过如集成测试中的显式检查所示。构造性模型只对守卫类约束形态生效且受支持预算与可重放性约束限制超出预算或遇到不透明变量时会安全地退回通用求解流程不会生成错误反例。重写正确性通过边界值测试保障覆盖零操作数、乘以 1、全量溢出与 2^255 边界等关键输入组合但符号证明的完备性仍取决于具体合约路径的约束复杂度。总结本次forge: patch变更从三个层面提升了 Foundry 符号执行对乘法溢出模式的处理能力表达式层将无分支饱和乘法的掩码习语(guard-1)|product、(guardmax)|product折叠为ite语义并经边界值测试保证重写前后等价求解器层对 checked-mul guard 做有界规范化让两个 u64 相乘不会溢出这类事实不触发任何 SMT 查询同时为守卫分支提供构造性模型以加速反例生成用户层通过forge test --symbolic --optimize提供端到端验证入口使开发者能用符号证明替代人工审查验证无分支汇编优化与带分支语义实现的等价性。对于在 gas 敏感合约中广泛使用位运算与内联汇编的开发者这些能力意味着复杂掩码技巧的正确性不再依赖目测而是可以交由 Foundry 的符号执行器进行自动化、可重复的形式化验证。【免费下载链接】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),仅供参考
返回列表