Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

预言机操纵与定价机制进阶

预言机向业务提供外部价格或状态。AMM 储备、金库汇率和外部喂价都可能成为估值输入;风险取决于输入可被谁改变、何时读取,以及错误报价能够影响多大的资产敞口。

报价来源与攻击边界

来源风险主要检查
AMM 即时储备或价格交易可改变现货比例,低深度下偏移成本较低深度、滑点、读价时机与业务用途
LP 或金库汇率捐赠、舍入或回调中的暂态状态可能改变计算结果会计不变量、份额公式、状态同步
时间加权报价窗口过短、历史不足或持续操纵仍可能造成偏移窗口、观察数据、流动性与敞口
外部价格 Feed陈旧、配置错误、报价单位不匹配或服务故障更新时间、资产对、精度与故障处理

合约读取其他合约的 view 函数,不代表返回值必然稳定或不能被间接影响。相同的状态来源用于交换报价和用于借贷抵押估值时,要求也不同。

只读重入与中间状态

流动性退出过程中,若 LP 总供应已减少,而估值使用的资产余额尚未完成相应更新,回调中的查询可能返回失真的每份资产价值。外部借贷协议依据该值放款,就会将局部中间状态变为持久债务。

sequenceDiagram
  participant A as 回调合约
  participant P as 流动性池
  participant L as 借贷协议
  A->>P: 移除流动性
  P->>P: 更新部分会计状态
  P->>A: 外部转账或回调
  A->>L: 发起依赖池估值的操作
  L->>P: 查询份额价值
  P-->>L: 返回尚未同步的中间状态
  A-->>P: 回调结束
  P->>P: 完成剩余状态更新

具体受影响字段和调用顺序依赖池版本。池侧可在外部交互前维持会计一致性,或在不安全窗口拒绝估值;集成方需要确认读价接口的保证。普通写操作互斥锁并不会自动限制所有 view 入口。

Uniswap V3 的时间加权价格

V3 记录累计 tick。令累计值为 \(C(t)\),窗口长度为 \(T\),则平均 tick 为:

$$ \overline{\mathrm{tick}}=\frac{C(t)-C(t-T)}{T} $$

以最小代币单位计价的近似报价为:

$$ P_{\mathrm{TWAP}}\approx 1.0001^{\overline{\mathrm{tick}}} $$

平均 tick 对应几何时间加权价格。tick 可以为负数或零,不能再对 tick 取对数。将报价转换为通常的 token1/token0 单位价格,还需要按双方 decimals 调整,并确认报价方向。

实际实现使用离散 tick、整数累计值及舍入规则。观察记录必须覆盖所需窗口;不能以单个即时 slot0 值代替历史 TWAP。

操纵持续时间与成本

同一交易内的瞬时价格变化没有持续时间,通常不会改写已经累计的历史时间权重。但偿还闪电贷只表示贷款已还,不保证 AMM 储备或价格自动恢复;交易后的偏离可能持续并影响后续观察。

窗口拉长通常增加维持偏离的成本,也增加报价滞后。成本与区间流动性、套利竞争、控制出块能力、手续费、可获得资金和持续时间有关,不能仅用窗口分钟数给出通用安全结论。

操纵成本高于可提取收益是一项经济分析目标,不是仅由 TWAP 名称保证的不变量。敞口评估需要考虑重复操作、多个市场与组合资产,而非只计算一次借贷。

latestRoundData() 提供报价和更新时间。价格场景通常需要正值校验、新鲜度检查,以及明确的 Feed、网络、资产对和 decimals。以下是使用该接口的简化读取器:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface PriceFeed {
    function latestRoundData() external view returns (
        uint80 roundId, int256 answer, uint256 startedAt,
        uint256 updatedAt, uint80 answeredInRound
    );
}

contract PriceReader {
    function read(PriceFeed feed, uint256 maxAge)
        external view returns (uint256)
    {
        (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
        require(answer > 0, "Invalid price");
        require(updatedAt != 0 && updatedAt <= block.timestamp, "Invalid time");
        require(block.timestamp - updatedAt <= maxAge, "Stale price");
        return uint256(answer);
    }
}

maxAge 需要匹配具体 Feed 的更新规则及业务容忍度,不能对所有资产采用同一固定值。返回整数尚未完成精度归一化;读取成功也不等于适合当前业务估值。

Chainlink 当前文档将 answeredInRound 标为弃用字段;旧轮次语义需按实现判断。多数 Feed 不再使用 minAnswer/maxAnswer,应用断路器不能假定底层聚合器一定提供有效的价格上下限。

L2 集成还需考虑定序器停机与恢复宽限期。报价异常时暂停新增风险、允许减仓、切换数据源或进入其他降级状态,需要业务状态机明确约定。

多源与闪电贷边界

闪电贷受可用流动性、费用和偿还条件约束,可降低单笔交易的资金门槛,但不是无限或零成本资本。无闪电贷的攻击者也可能通过自有资金或跨区块融资影响价格。

多个价格源需要比较报价单位、更新时间和共同依赖。两个来源不自动构成抗单点异常的中位数机制;取最小值对抵押资产和债务资产的风险方向也不同,聚合策略须对应业务含义。

参考资料