Skip to main content

TWAP

TWAP: Time Weighted Average Price(时间加权平均价格)

可用来创建有效防止价格操纵的链上价格预言机。

TWAP 的实现

TWAP 的实现在Pair合约里,通过下面方法来更新记录

uint public price0CumulativeLast;   // token0的累加价格
uint public price1CumulativeLast; // token1的累加价格
uint32 private blockTimestampLast; // 记录更新的区块时间

// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
// 价格更新时间间隔
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
// 更新两个token的累加价格
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
// 更新当前池子的储备金
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}

_update方法分别在以下 4 个方法里触发更新: mintburnswapsync 主要是以下几步操作

  1. 读取当前的区块时间 blockTimestamp
  2. 计算出与上一次更新的区块时间之间的时间差 timeElapsed
  3. 如果 timeElapsed > 0 且两个 token 的 reserve 都不为 0,则更新两个累加价格
  4. 更新两个 reserve 和区块时间 blockTimestampLast

举例: 设当前时刻,token0 和 token1 分别为 WETH 和 USDT. 两个 token 的当前价格计算为

price0 = reserve1 / reserve0
price1 = reserve0 / reserve1

设: 当前储备量分别为 10WETH 和 4000USDT,那么 WETH 和 USDT 的价格分别为

price0 = 40000/10 = 4000 USDT
price1 = 10/40000 = 0.00025 WETH

现在,再加上时间维度来考虑。比如,当前区块时间相比上一次更新的区块时间,过去了 5 秒,那就可以算出这 5 秒时间的累加价格:

price0Cumulative = reserve1 / reserve0 * timeElapsed = 40000/10*5 = 20000 USDT
price1Cumulative = reserve0 / reserve1 * timeElapsed = 10/40000*5 = 0.00125 WETH

假设之后再过了 6 秒,最新的 reserve 分别变成了 12 WETH 和 32000 USDT,则最新的累加价格变成了:

price0CumulativeLast = price0Cumulative + reserve1 / reserve0 * timeElapsed = 20000 + 32000/12*6 = 36000 USDT
price1CumulativeLast = price1Cumulative + reserve0 / reserve1 * timeElapsed = 0.00125 + 12/32000*6 = 0.0035 WETH

这就是合约里所记录的累加价格了。

另外,每次计算时因为有 timeElapsed 的判断,所以其实每次计算的是每个区块的第一笔交易。而且,计算累加价格时所用的 reserve 是更新前的储备量,所以,实际上所计算的价格是之前区块的,因此,想要操控价格的难度也就进一步加大了。

TWAP 是通过在所需间隔的开始和结束时读取 ERC20 代币对的累积价格来构建的。然后可以将此累积价格的差异除以间隔长度,以创建该时期的 TWAP.计算公式如下图:

在实际应用中,一般有两种计算方案, 一是: 固定时间窗口的 TWAP 二是: 移动时间窗口的 TWAP 在 uniswap-v2-periphery 项目中,examples 目录下提供了这两种方案的示例代码,分为是 ExampleOracleSimple.solExampleSlidingWindowOracle.sol

现在,Uniswap TWAP 已经被广泛应用于很多 DeFi 协议,很多时候会结合 Chainlink 一起使用。比如 Compound 就使用 Chainlink 进行喂价并加入 Uniswap TWAP 进行边界校验,防止价格波动太大。