一、问题的结构:外部数据的校验边界
合约使用外部价格进行计价时,提供该数据的接口就承担了预言机的作用。 直接查询交易池同样属于引入外部数据:储备比值反映特定市场的即时状态,不自动代表适合所有业务的参考价格。 需要同时核对来源、采样时间、计价方向和单位。
独立来源交叉校验的关键不是接口数量,而是来源是否依赖同一组上游数据、运营节点或市场条件。 先梳理依赖关系,再比较同一时段、同一口径的数据;出现分歧时应保留异常信息,而不是直接将所有数值平均后使用。
二、即时数据:不能只读一个数值
以下展示独立来源的校验流程。概念示意,非可部署代码:
读取经过依赖关系评估的多个来源
逐项校验:数值有效、更新时间有效、资产与计价方向一致
核对各来源精度,将数值归一化到统一单位
若有效且具有足够独立性的来源不足:暂停依赖该数据的操作
计算参考中位数与各来源相对偏差
若偏差超过预设阈值:记录异常,进入复核或暂停流程
否则:按既定规则输出参考值及时间、来源信息
用归一化数值识别偏差
假设三个来源已经统一单位和采样时段,得到虚构的无量纲参考数值 100、101、120。 其中位数为 101;示例阈值设为 5%,仅用于演示校验,不代表实际参数建议。
相对偏差 = |来源数值 − 参考中位数| / 参考中位数
来源 A:|100 − 101| / 101 ≈ 0.99%
来源 B:|101 − 101| / 101 = 0%
来源 C:|120 − 101| / 101 ≈ 18.81%
在这组假设下,来源 C 触发偏差提示,应进入预先定义的复核或暂停流程。 这并不证明 C 一定错误,也不证明另外两个来源正确:来源之间的共同依赖、采样差异或整体滞后都可能影响判断。 阈值需要结合数据特征、更新频率与业务容错能力评估。
三、TWAP:把时间维度加进去
Uniswap V2 在池子中额外维护两个累计量:price0CumulativeLast 与 price1CumulativeLast,
每次更新储备前把「上一个区间价格 × 区间时长」累加进去。
取两个采样时刻的累计值之差,除以以秒计的时间戳差,得到区间内的按秒加权算术平均价:
twap = (cumulativeNow − cumulativeBefore) / (timestampNow − timestampBefore)
以上公式省略定点数缩放与计数器回绕处理,要求两个累计值对应各自采样时刻,且时间差为正。 窗口中的偏差贡献取决于偏差幅度与持续时间的权重;局部区间的异常也会影响结果, 并不需要覆盖整个窗口。时间平均通常能平滑短时波动,但不能单独保证数据可靠性。
使用 TWAP 时需注意三个前提:
- 窗口与用途匹配。短窗口平滑作用有限,长窗口可能增加滞后;两者都需结合更新频率和业务需求评估。
- 检查窗口内的偏差。均值可能掩盖局部异常,应结合独立来源、采样覆盖与异常阈值判断是否可用。
- 区分平均方式。算术平均直接累计价格,几何平均可通过累计对数价格再还原;二者对波动的响应不同,均不能替代来源可靠性检查。
四、聚合喂价:把信任移到签名方
一种常见做法是由链下网络从多个交易所采集数据并聚合,再由一组节点签名后写入链上合约。 读取方拿到的是「若干签名者共同声明的价格」。这减少了对单一链上交易池的直接依赖, 但同时引入采集方与签名方的信任假设,底层市场的数据质量仍然重要。
几个关键机制同时决定了数据的可信边界:
| 机制 | 作用 | 副作用 |
|---|---|---|
| 偏差阈值 | 通常在变化超过阈值时触发更新,并配合心跳 | 阈值内的变化可能暂未反映 |
| 心跳间隔 | 按配置周期尝试更新 | 更新可能延迟,仍需检查时间戳 |
| 中位数聚合 | 在有效独立来源充足时降低离群值影响 | 共同来源异常仍可能影响聚合结果 |
| 链上验证 | 由合约校验签名与报告格式等条件 | 签名有效不等于数据准确,仍需检查来源与时效 |
首个坏报告者规则
评估报告接纳规则时,应关注初始参考值如何影响后续数据。 例如,若某个聚合器只接受上一次已写入值附近的报价,超出区间的报价被丢弃, 那么异常参考值可能使后续有效数据也被拒绝,导致偏差持续。 是否能够恢复取决于具体的更新与重置规则,不能假定偏差扩大后就会自动纠正。
跨多轮一致性检查、独立参考与异常时的人工或延迟确认,可作为需要评估的缓解措施, 但也带来滞后与可用性约束。任何带锚点的更新机制,都应明确如何检测异常锚点、 由谁执行恢复,以及恢复前哪些操作应暂停。
五、两类交叉形态
1. 与只读重入的组合
只读重入也可能影响预言机读取:当协议的关联字段尚未完成更新时, 查询得到的比值未必描述一个可用于结算的一致状态。 除检查数据来源外,还需确认读取边界,例如由读入口检查现有写锁,或返回带时间与版本信息的已提交快照。
2. 资产与份额的一致性
封装型金库(vault)常用「总资产 ÷ 总份额」表达份额价值,两者必须采用一致的记账口径与快照时点。 实际资产余额可能因非预期转入、手续费或代币余额调整而与内部台账不同,不能将每次余额变化都直接视为可分配收益。 实现应明确这些输入如何记账、何时纳入估值,并检查初始份额、舍入和异常变化的处理规则。 外部价格校验可以辅助发现分歧,但不能替代资产与份额不变量的验证;延迟确认也需评估滞后影响。
六、读取价格时的检查清单
以下仅列出数值、时间与单位校验的基本顺序。概念示意,非可部署代码; 实际接入仍需核对具体接口、异常返回与来源配置。
从 feed.latestRoundData() 的报告中读取 value(answer)与 updatedAt
检查 value > 0
检查 updatedAt > 0 && updatedAt <= now
在上述检查通过后,检查 now - updatedAt <= maxAge
单独读取 feed.decimals(),核对已审核配置中的预期精度
核对资产标识、计价方向与目标单位
按已验证精度归一化 value,检查缩放溢出与舍入误差
任一检查失败:拒绝使用本次数据,进入预设异常处理流程
- 拒绝零与负值:异常数值参与计算可能导致回滚或额度计算错误。
- 检查时效:拒绝零时间戳、未来时间戳与超过最大允许时长的数据;过时数值在快速变化的环境中可能不再适用。
- 核对小数位:
latestRoundData()不返回精度,应独立读取并核对;1e8 与 1e18 混用会产生十个数量级的缩放误差。 - 限制单笔与总敞口:结合业务设置上限,可减少单点失效的影响,但不保证消除全部损失。
- 准备异常处理:评估暂停机制的触发条件、权限和恢复流程,并测试数据源中断时的行为。
预言机的数据可靠性取决于来源、时间窗口、签名与估值规则等多层假设。 即时数据、TWAP、聚合喂价与份额价格各有适用边界;将校验、监测和异常处理结合起来, 才能更清楚地界定系统在什么条件下可以继续使用数据。
- Junjie Liu 等,关于去中心化金融攻击的分类学研究,其中价格操纵被单列为最大损失类别。
- Uniswap V2 Oracle 库源码与文档,累计价格的设计说明。
- Chainlink 文档:Deviation threshold and heartbeat、First-bad-reporter rule,及其后的验证层调整公告。
- Empty Square Sloth 关于只读重入与金库估值操纵的技术披露(2023)。