仅供教育参考,不构成投资建议;投资可能产生损失。
直接答案
当合约以错误的单位指数、价格方向或内部尺度解释原始整数时,就会发生预言机小数位错配。数据源小数位、代币小数位、报价代币小数位、封装或份额兑换率尺度,以及协议的 WAD 或 RAY 约定彼此独立。前端显示正确,不代表消费合约执行了相同运算。
对于基础代币原始数量 A_raw,其小数位为 d_t;正数数据源答案 P_raw 以小数位 d_p 表示“每个基础代币值多少报价代币”。若报价代币小数位为 d_q,其原始数量为 V_raw = round(A_raw * P_raw * 10^d_q / 10^(d_t + d_p))。该公式仅适用于所述报价方向,并须先验证数据源、单位、类型及舍入规则。适配器已归一化的数值不得再次缩放。
ERC-20 的 decimals() 是可选显示元数据,不保证统一为 18 位。数据源的 decimals() 描述该数据源响应,而非代币或协议尺度。检查型运算可通过回滚阻止溢出,但无法发现量纲错误的公式;若中间乘积溢出,还可能使原本可表示的结果变成拒绝服务。
运作方式
- 锁定链、区块、消费方、适配器、数据源代理与聚合器、代币或金库、编译器与数学库版本,以及升级状态。
- 清点每个整数的类型与单位:原始代币数量、代币小数位、有符号数据源答案、数据源小数位、基础与报价方向、封装或份额汇率、协议内部尺度及输出代币小数位。
- 转换前验证来源:地址与方向正确、答案为正、时间戳和状态有效、年龄与范围可接受、回退语义明确,并在适用时检查 L2 排序器宽限规则。
- 从输入原始单位推导到输出原始单位的单一量纲公式。放大与缩小时明确分支,限制每个十次幂指数,每一项只归一化一次。
- 使用经证明的全精度乘除法或有界约分策略。避免先除后乘截断、受检中间值溢出、unchecked 回绕,以及不安全的有符号或窄化转换。
- 为每项经济操作规定向下、向上或最接近舍入。抵押品、债务、借款、铸造、赎回、费用、清算及金库份额转换可能需要不同的保守方向。
- 对极端数量、小数位组合、倒数、复合数据源、零值、负值、过时答案、升级及尘埃值运行黄金向量与性质测试;用独立高精度模型核对链上结果,并限制受影响敞口。
归一化是量纲分析,不是格式化。BTC/USD 与 USD/BTC 需要倒数公式;改标签不会反转价格。复合数据源要求每一项的尺度与时间戳。Solidity 整数除法向零截断,因此代数上等价的重排可能产生不同链上结果。全精度 mulDiv 解决中间位宽问题,但调用方仍须提供正确的分子、分母、单位、边界与舍入方向。
计算示例
- 八位与十八位小数。 BTC/USD 数据源返回
6,000,000,000,000,且d_p = 8,所以价格为60,000 USD/BTC。把原始答案当作 18 位小数会得到0.000006 USD/BTC,低估10^10倍,而非10^9。将价格缩放至 WAD 得到6,000,000,000,000 * 10^(18 - 8) = 60,000,000,000,000,000,000,000。 - 数量、价格与输出单位。
A_raw = 2,500,000代表2.5个代币,其小数位为d_t = 6;P_raw = 200,000,000代表2个报价代币,其小数位为d_p = 8。对于 18 位内部报价数值,2,500,000 * 200,000,000 * 10^18 / 10^(6 + 8) = 5,000,000,000,000,000,000,即5 quote tokens。省略代币分母会高估10^6倍。 - 倒数与截断。 WAD 尺度的 ETH/USD 为
2,000 * 10^18。同尺度的 USD/ETH 为floor(10^36 / (2,000 * 10^18)) = 500,000,000,000,000,即0.0005 ETH/USD。另有A_raw = 999,999、d_t = 6、价格2 * 10^18时,全精度乘除得到1,999,998,000,000,000,000;若先将数量除以10^6,结果为0,全部价值丢失。 - 中间溢出与舍入。 设
x = 2^200、y = 2^100、分母为2^100。精确结果是2^200,可放入uint256,但x * y = 2^300无法放入。检查型乘法回滚,unchecked 乘法回绕;全精度mulDiv返回2^200。整数5 / 2向下为2,向上规则返回3,因此舍入属于经济不变量。
风险
- 链、数据源、代理、适配器、代币、金库或消费方地址错误。
- 基础与报价方向反转而未做倒数转换。
- 假定代币为 18 位,或可选元数据不可用或错误。
- 假定数据源为 8 位,而未读取并锁定。
- 混淆报价代币小数位与协议 WAD、RAY、市场或会计尺度。
- 遗漏封装、份额、指数或兑换率小数位。
- 适配器已归一化后重复应用缩放因子。
- 遗漏必要缩放因子或分母。
- 未检查正值便将有符号答案转为无符号。
- 接受零值、过时、不完整、封顶或无效数据源结果。
- 小数指数或十次幂计算下溢、溢出或越界。
- 最终商可表示,但中间乘积溢出。
- unchecked 运算、位移或显式窄化静默回绕或截断。
- 先除后乘破坏精度或使尘埃值归零。
- 经济操作采用错误的向下、向上或最接近舍入。
- 重复转换累积精度损失或系统性价值泄漏。
- 以不同单位比较价格、上限、比率、百分比、基点、WAD 与 RAY。
- 数据源、代币、代理、适配器或金库升级使缓存的小数位假设失效。
- 前端、钱包、RPC 或索引器格式化掩盖不同的链上运算。
- 错误估值放大借贷、铸造、赎回、清算、上限、坏账或不公平份额发行。
常见误区
- “每个 ERC-20 代币都使用 18 位小数。” ERC-20 元数据方法是可选的,已部署资产采用不同数值与行为。
- “每个美元价格数据源都使用 8 位小数。” 数据源精度是具体部署的接口属性,必须读取并纳入版本管理。
- “原始整数很大就证明被操纵。” 若不知道单位、方向、小数位、时间戳和消费尺度,原始数量没有意义。
- “Solidity 0.8 会保证缩放正确。” 检查型溢出可回滚,但不会修复错误单位、截断、类型转换或舍入政策。
- “先乘后除或增加小数位总会提高精度。” 这可能溢出、重复缩放或保留错误单位;全精度运算仍需要正确公式。
相关主题
来源
- Data Feeds API Reference - Chainlink Documentation(访问日期:2026-08-13)
- Chainlink Data Feeds - Chainlink Documentation(访问日期:2026-08-13)
- ERC-20: Token Standard - Ethereum Improvement Proposals(访问日期:2026-08-13)
- Types - Solidity Documentation(访问日期:2026-08-13)
- Expressions and Control Structures - Solidity Documentation(访问日期:2026-08-13)
- Utils - OpenZeppelin Contracts Documentation(访问日期:2026-08-13)
- Oracles - Aave Protocol Documentation(访问日期:2026-08-13)
- Compound v2 Price Feed - Compound Documentation(访问日期:2026-08-13)