核验清单
- 同一区块读取喂价的 decimals()、latestRoundData 的 answer,以及代币的 decimals()。不得用代币精度代替喂价刻度。
- 可读价格等于 answer 除以 10 的喂价 decimals 次方。answer 小于或等于 0 时停止,不要写成价格。
- 若笔记用代币 decimals 做除数,且两个整数不同,价格记为不可用。updatedAt 新鲜、宽限期已过、断路器未触发,都不改变这一判定。
- 两个 decimals 碰巧相等,仍要分别记账。相等不是省略喂价 decimals() 的理由。
1. 两套 decimals 问的不是同一件事
研究笔记里常见的句子是「updatedAt 只有十几秒,价格可用」。在定点喂价上,这句话还缺一口单位。Chainlink 的数据喂价入门写明,latestRoundData 返回的 answer 是整数,小数点不在这个整数里。文中的 BTC/USD 示例使用 8 位小数:answer 为 3030914000000 时,可读价格是 30309.14。同一段写明,每个喂价使用的小数位数可以不同。接口参考把 decimals() 定义成所存 answer 的定点精度,也就是响应里的小数位数。
这和代币精度不是同一口单位。ERC-20 的 decimals规定如何把代币余额换成可读数量:返回 8,表示余额要除以 100000000 才是用户看到的枚数。它回答「这一枚代币有多少」,不回答「这一口价格的小数点在哪」。本篇不重复喂价延迟、排序器宽限期或断路器。那些检查都假定刻度已经正确。这里假定轮次很新,问除数有没有用错。也和池子储备的精度换算不同:那是先把两种代币余额换成数量,再谈隐含价格;这里是价格 answer 本身还没换成可读价格。
2. 教学案例:同一个整数,两个除数,两个数量级
先按文档把示例算对。3030914000000 除以 10 的 8 次方,等于 30309.14。若笔记沿用抵押代币的 decimals,而该代币返回 18,同一整数除以 10 的 18 次方,得到 0.000003030914。倍数是 10 的 (18 减 8) 次方,即 100 亿。价格被写成另一个数量级,不是四舍五入能解释的差距。下表第一行是文档示例,第二行是把该示例除以 18 位代币精度的教学推演。
| 字段 | 数值 | 判定 |
|---|---|---|
| 文档示例 answer | 3030914000000 | 定点整数,小数点不在其中 |
| 喂价 decimals | 8 | 该 BTC/USD 示例的刻度 |
| 正确可读价格 | 30309.14 | answer 除以 10 的 8 次方 |
| 代币 decimals | 18 | 教学假设,不是喂价的返回值 |
| 误用后的价格 | 0.000003030914 | answer 除以 10 的 18 次方 |
| 误差倍数 | 100 亿 | 10 的 (18 减 8) 次方,价格被缩小 |
方向可以反过来。另一组教学假设不指向任何真实喂价:喂价 decimals 为 8,代币 decimals 为 6,answer 为 99990000。除以 10 的 8 次方得 0.9999;除以 10 的 6 次方得 99.99。倍数是 100,价格被放大。规则因此要写成方向,而不是一句「小数位搞错了会偏一点」:代币精度大于喂价刻度时,误用代币精度会缩小价格;代币精度小于喂价刻度时,会放大价格。
最容易漏掉的是两者都返回 8。这时误用代币精度,可读价格碰巧相同。笔记若把这次碰巧写成模板,下一对变成 18 对 8,或 6 对 8,模板照跑,updatedAt 照样很新。抵押估值、健康因子和净值会跟着差同一个 10 的幂。1 个已经按代币精度换算好的单位,乘上正确价格约等于 30309.14 个报价单位;乘上错误价格约等于 0.000003030914。差的不是滑点,是另一口市场。
3. 刻度在这一口喂价上,不在另一页的 8
入门说明强调每个喂价的小数位可以不同。把 BTC/USD 示例的 8 抄到别的交易对,或抄到一次新部署,这一列还没核验。decimals() 必须和 latestRoundData 落在同一区块。代理后面的聚合器可以更换。description 能帮助核对交易对名称,但不能代替这次读到的整数。有人把「USD 报价一般都是 8 位」写成常量,下一口以另一种资产报价的喂价就可能不是 8。常量一旦写死,新鲜的 updatedAt 会给错误刻度盖章。
answer 的类型是有符号整数。小于或等于 0 时停止,不要把非正数除完再写成价格。正确的刻度也不代替新鲜度:心跳、宽限期、断路器仍按各自的笔记做。反过来,那些检查全部通过,也修不好一个错的 10 的幂。面板上的可读价格是另一层风险。把已经换成 30309.14 的数字再除以 10 的 8 次方,是二次缩放;把原始 answer 直接当成美元,则是把价格放大了 10 的 8 次方。记录必须写明手上的整数是原始 answer,还是已经缩放的结果。两个长得像价格的数字,可以一个是这口喂价的可读结果,一个是另一口喂价的原始整数。
4. 筛选线:除数用错,新鲜 updatedAt 不得写成可用
教学筛选按这个顺序,不能跳步。第一,answer 小于或等于 0:不是可用价格。第二,可读价格只允许用喂价 decimals 作除数。若笔记用了代币 decimals,且两个整数不同:不可用。第三,两个整数相同:两个都记下来,然后继续。相同是这一对的结果,不是省略喂价读取的理由。第四,前三步通过之后,才把 updatedAt、宽限期和偏离交给其他筛选。前两步失败时,心跳再漂亮也不改判定。
误差倍数写为 10 的 (代币精度减喂价刻度) 次方。差为 0 时倍数是 1,但前提是两个整数都在同一区块读过。只看到面板上的人类可读价格、没有原始 answer 和 decimals(),这一列记为未核验,而不是记为通过。该筛选不回答轮次有多旧,不回答排序器宽限期是否结束,也不回答偏离会不会触发断路器。它只回答:这个可读价格的小数点,是不是按这口喂价自己的刻度放上去的。通过一次也不等于下一笔仍是同一刻度。聚合器更换后要重读 decimals(),不能沿用上次笔记里的 8。
5. 一份别人能复现的记录
最低字段包括:链 ID、区块号与哈希、喂价地址、代币地址、喂价 decimals、代币 decimals、answer、按喂价刻度得到的可读价格、若有人用代币精度除过则记下那个数和倍数、answer 是否为正、updatedAt。updatedAt 留档,不代替刻度。若有人把 8 写死,记下这个 8 来自哪一页、哪一口喂价。两次 RPC 相差一个区块就要分开记账,避免用新的 answer 配旧的 decimals。
对账顺序固定为:先读两个 decimals,再看 answer 的符号,再除以喂价刻度,再对照任何使用代币精度的算法,最后才是新鲜度、宽限期和断路器。未读喂价 decimals() 的笔记,结论停在「刻度缺失」,不得写成「价格已核验」。需要持有少量该代币才能复现一笔仓位估值时,把兑换记在费用附注里,不要写进刻度是否正确的判定。
6. 结论:可用价格要先放对小数点
加密研究的错误句式是「updatedAt 很新,所以这笔价格可以用」。更可靠的句子是「喂价 decimals;代币 decimals;answer;可读价格等于 answer 除以 10 的喂价刻度次方;若除数用了代币精度且两者不同,则不可用」。心跳、宽限期和断路器都不能修补刻度。两个 decimals 碰巧相等,只说明这一对现在相同,不说明模板可以省略其中一口。
本文是分析框架。BTC/USD 的 3030914000000 与 30309.14 来自 Chainlink 入门说明中的示例,其余行是教学假设,未对任何实时喂价做链上实测,也不构成投资建议。