从零开始UniswapV2 | Part-2 预言机

什么是价格

Uniswap 作为链上的去中心化交易所，承载着价值发现的功能，即用户或其他链上合约可以通过 Uniswap 来获取代币的价格，Uniswap 在这其中承担链上预言机的功能。

假设当前交易池中有 1 Ether 和 2000 USDC，那么以太币的价格就是 2000/1=2000 USDC，反之就是 USDC 的价格，因此价格就是一个比率，由于智能合约尚不支持小数，所以在 Uniswap 的代码中拓展了新的数据类型来存储价格，关于新的数据类型后面会专门做介绍。

TWAP 价格机制

然而仅仅使用瞬时的代币数目之比作为价格是不安全的，存在人为操纵价格预言机的风险，由于 Uniswap 提供了闪电贷功能，因此在某个闪电贷交易的瞬间，交易对内的代币余额会产生剧烈波动。在 UniswapV2 中为了解决这个问题，采用了 TWAP（Time Weighted Average Price）即时间加权的价格预言机机制。

具体工作原理如下：

假设过去一天，资产在前 20 个小时的价格为 20$，最近 4 小时的价格为 10$，那么 TWAP=($20*20+10$*4)/24 = 18.33$

假设过去一天，资产在第一个小时的价格为 10$，最近 23 小时的价格为 15$，那么 TWAP=($10*1+15$*23)/24 = 14.79$

总结下来，TWAP 的公式如下，这里的 T 是时间段，P 是对应时间段的价格

在 UniswapV2 的合约中，只会记录分子部分，即记录每个时间段乘以单价的求和，而分母部分则需要使用方自行维护，交易对内有两个代币，所有有两个值来记录

通常我们只需关心某一时间区间内的代币价格，这是 TWAP 公式的历史价格公式：

假设我们的计价从 T4 开始，那么实际的计价公式应该如下：

前面已经提到，在合约内有一个变量会追踪分子的求和值，以 token0 的追踪计价变量 price0Cumulativelast 为例：

这个变量是记录了历史以来所有时间段的求和，那么我们只需要从 T4 开始的部分即可，计算方式也很简单，在 T3 时间点我们获取一个 price0Cumulativelast 变量的快照，在最新即 T6 时间点再获取一次，两次的差值即是 T4 - 最新时间段内 token0 的计价和

我们自己也维护了最近的窗口持续时间和，即：T4+T5+T6

那么这段时间内的 TWAP 价格即可计算得出：(price0Cumulativelast-UpToTime3)/(T4+T5+T6)

UniswapV2 的实现

具体到 Uniswap 的实现中，对于每个交易对都维护了两个变量 price0Cumulativelast 和 price1Cumulativelast，在之前提到的_update 方法中进行求和，具体的代码如下：

首先获取当前的区块时间戳blockTimestamp

通过与 blockTimestampLast 相减，计算出距离上次更新过去了多少时间timeElapsed

只有在timeElapsed >0 时，即进入下一个区块时，才会累加价格 * 时间段

注意这里的价格计算用到了UQ112x112 的特殊格式，了解它是为了记录小数即可，后面会专门讲解这里的优化

在处理完累加后，才会更新blockTimestampLast 到最新的区块时间戳

Copy
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
...
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
blockTimestampLast = blockTimestamp;
...
}

TWAP 的潜在问题

基于时间加权的价格计算，会提高攻击者的操纵成本，因为需要连续控制多个区块，这样的攻击成本是很高的。
当价格产生剧烈波动时，由于有时间作为加权因素，预言机的价格无法在较短时间内反映出价格的波动，反而提供出过时的价格，尤其是在市场发生剧烈动荡时，这样的情况会导致 Uniswap 中的价格与外部市场产生较大的差异。

使用 TWAP 预言机仍然依赖链下的定时触发，存在维护成本与中心化问题。