ARTICLE DETAIL

资讯详情

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

Foundry 规则深度解析:divide-before-multiply 先除后乘精度丢失检测

Foundry 规则深度解析:divide-before-multiply 先除后乘精度丢失检测 Foundry 规则深度解析divide-before-multiply 先除后乘精度丢失检测【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundrydivide-before-multiply是 Foundry 内置 Solidity 静态检查器forge lint中的一条**中危Med**规则用于拦截(a / b) * c这类先整数除法、再乘法的表达式。由于 Solidity 的整数除法向零截断这种写法会在放大之前就丢弃余数是 DeFi 合约中手续费、份额、收益计算精度偏差的常见来源。读完本文你将理解该规则的触发原理、修复写法与溢出权衡并通过仓库源码与测试用例掌握其检测边界能在foundry.toml中正确启用并落地到自己的合约工程中。规则概览它检测什么该规则的完整定义为见 crates/lint/src/sol/med/div_mul.rs属性值规则 IDdivide-before-multiply严重级别Med中危触发形态(a / b) * c及其等价写法警告信息division before multiplication may lose precision从源码实现看declare_forge_lint!宏将该规则注册为LateLintPass在语义分析阶段之后运行入口为check_function——即对每个函数的函数体进行逐语句、逐表达式的检查而不是简单的正则或文本匹配。这意味着它理解 Solidity 的语法与类型能识别括号、复合表达式、赋值乃至内联汇编中的真实除法/乘法语义。为什么这是坏的整数除法的精度陷阱Solidity 的整数除法向零截断。对表达式(a / b) * c求值顺序是先执行a / b得到截断后的商丢弃余数再乘以c放大而(a * c) / b先在整数域内完成缩放再做除法保留了更多精度。以文档中的示例为例// 先除后乘截断发生在缩放之前 uint256 share (amount / total) * weight; // 先乘后除保留精度 uint256 share (amount * weight) / total;取amount 100、total 3、weight 2(100 / 3) * 2 33 * 2 66——100 / 3先截断为 33损失了1/3(100 * 2) / 3 200 / 3 66——仍会截断但截断发生在最终一步中间没有二次精度损失。两种写法在数值上可能恰好相等也可能相差巨大取决于操作数关系。仓库测试用例 DivideBeforeMultiply.sol 中覆盖了多种形态例如(1 / 2) * 3、3 * (1 / 2)、4 * ((1 2) / 3)以及分母中出现先乘后除的1 / ((2 / 3) * 3)也会被警告——因为被乘数/除数本身就是除法结果时同样存在精度损失。在实际业务中该模式常体现为手续费计提、代币份额分配、收益率换算等场景的系统性小数偏差日积月累可被利用或导致账目失衡。修复写法与溢出权衡文档明确指出先乘后除并不总是安全的。(a * c) / b中的a * c可能溢出即使最终结果恰好适配当前整数类型。因此选择重写时需要明确两点确认乘积不溢出当a * c的中间乘积能安全放入该整数类型如uint256时直接改写为先乘后除否则使用全精度辅助函数改用检查式乘法/除法checked 乘法或专门的全精度乘法-除法库函数如 OpenZeppelin 风格的mulDiv实现显式决定舍入行为计算需要向零舍入、向上舍入还是四舍五入应根据业务语义显式选择而不是依赖求值顺序偶然决定的截断。也就是说规则的意图不是机械地要求一律先乘后除而是提醒开发者对除法后的乘法保持警惕并显式决策精度与舍入策略。从源码看检测能力污点数据流分析该规则的实现远比匹配(a / b) * c文本强大。核心是一个污点分析taint analysis以Tainted HashSetVariableId记录当前值来自除法结果的局部变量集合然后沿控制流传播见 div_mul.rs。污点的设置与传播变量声明uint256 q a / b;后q被标记为污点q a b;清除污点div_mul.rs。普通赋值右值如果是除法或污点变量左值继承污点元组解构赋值(x, y) (a / b, c)会逐元素判定而不是整体污染rhs_taints函数见 div_mul.rs。复合赋值q * c若q带污点则直接告警q / b会重新设置污点而q 1、q等会清除污点见 div_mul.rs。测试函数compoundClearsTaint、incrementClearsTaint验证了这一点。别名与复制uint256 copy q; return c * copy;污点会随变量复制继续传播。控制流感知污点并非简单地沾上就不掉而是按分支、循环做保守合并if 分支任一可达分支落下时合并各分支的污点check_branches见 div_mul.rs。测试中branchPropagated条件分支内赋值后使用会告警。return / revert 分支不泄漏若某分支以return或revert终止则该分支内的污点不会污染后续代码——对应测试returningBranchDoesNotLeak、revertingBranchDoesNotLeak均不告警。循环循环体内产生的污点会传播到循环后使用loopPropagated告警。try/switch同样按互斥分支合并污点。函数调用参数调用并不会把普通参数宽泛地视为除法结果noBroadRhsTaint测试验证了helper(a / b)不会污染helper(q)之后的乘法。这种设计使规则既能发现跨语句、跨分支的精度问题又避免了对无关代码的误报。内联汇编Yul检测通过is_yul_call识别 Yul 内建函数规则同样覆盖assembly块见 div_mul.rsassembly { result : mul(div(a, b), c) // 告警div 后 mul result : mul(c, sdiv(a, b)) // 告警sdiv有符号除法后 mul let q : div(a, b) result : mul(q, c) // 告警污点变量参与 mul }有符号除法sdiv与无符号div都会被跟踪测试文件通过--only-lint divide-before-multiply custom-errors inline-assembly编译标志同时开启了inline-assembly规则以验证这一路径见 DivideBeforeMultiply.sol。如何启用与使用forge lint会在forge build的编译后阶段自动运行lint_on_build见 crates/forge/src/cmd/build.rs。针对本项目可执行# 仅运行本规则配合其他规则过滤参考测试用例的编译标志 forge lint --only-lint divide-before-multiply # 跳过编译后的 lint 步骤相当于 foundry.toml 中 [lint] lint_on_build false forge build --no-lint在foundry.toml中可通过[lint]段控制对应 build.rs 中severity、exclude_lints、lint_specific等配置的解析[lint] lint_on_build true severity [warn] # 至少以警告级别展示 exclude_lints [] # 例如 [divide-before-multiply] 可排除本规则注意当前 lint 引擎Solar仅支持 Solidity 0.8.0的源码见 build.rs低于该版本的合约不会被此规则分析。参考与深入阅读规则文档原文crates/lint/docs/divide-before-multiply.md规则实现源码crates/lint/src/sol/med/div_mul.rs规则注册med级别模块crates/lint/src/sol/med/mod.rs测试用例正反例全集crates/lint/testdata/DivideBeforeMultiply.sol测试期望输出stderr 快照crates/lint/testdata/DivideBeforeMultiply.stderr规则清单与说明crates/lint/README.mdlint 引擎接入点build 阶段crates/forge/src/cmd/build.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),仅供参考
返回列表