什么是价格#
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
到最新的区块时间戳
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 预言机仍然依赖链下的定时触发,存在维护成本与中心化问题。