跳到正文

预言机小数位错配

小数位错配会对预言机答案、代币数量、封装汇率或协议数值采用错误单位缩放,导致借贷、铸造、赎回和清算出现数量级错误。

更新于

仅供教育参考,不构成投资建议;投资可能产生损失。

直接答案

当合约以错误的单位指数、价格方向或内部尺度解释原始整数时,就会发生预言机小数位错配。数据源小数位、代币小数位、报价代币小数位、封装或份额兑换率尺度,以及协议的 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() 描述该数据源响应,而非代币或协议尺度。检查型运算可通过回滚阻止溢出,但无法发现量纲错误的公式;若中间乘积溢出,还可能使原本可表示的结果变成拒绝服务。

运作方式

  1. 锁定链、区块、消费方、适配器、数据源代理与聚合器、代币或金库、编译器与数学库版本,以及升级状态。
  2. 清点每个整数的类型与单位:原始代币数量、代币小数位、有符号数据源答案、数据源小数位、基础与报价方向、封装或份额汇率、协议内部尺度及输出代币小数位。
  3. 转换前验证来源:地址与方向正确、答案为正、时间戳和状态有效、年龄与范围可接受、回退语义明确,并在适用时检查 L2 排序器宽限规则。
  4. 从输入原始单位推导到输出原始单位的单一量纲公式。放大与缩小时明确分支,限制每个十次幂指数,每一项只归一化一次。
  5. 使用经证明的全精度乘除法或有界约分策略。避免先除后乘截断、受检中间值溢出、unchecked 回绕,以及不安全的有符号或窄化转换。
  6. 为每项经济操作规定向下、向上或最接近舍入。抵押品、债务、借款、铸造、赎回、费用、清算及金库份额转换可能需要不同的保守方向。
  7. 对极端数量、小数位组合、倒数、复合数据源、零值、负值、过时答案、升级及尘埃值运行黄金向量与性质测试;用独立高精度模型核对链上结果,并限制受影响敞口。

归一化是量纲分析,不是格式化。BTC/USDUSD/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 = 6P_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,999d_t = 6、价格 2 * 10^18 时,全精度乘除得到 1,999,998,000,000,000,000;若先将数量除以 10^6,结果为 0,全部价值丢失。
  • 中间溢出与舍入。x = 2^200y = 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 会保证缩放正确。” 检查型溢出可回滚,但不会修复错误单位、截断、类型转换或舍入政策。
  • “先乘后除或增加小数位总会提高精度。” 这可能溢出、重复缩放或保留错误单位;全精度运算仍需要正确公式。

相关主题

来源

导航

搜索知识库...