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 个方法里触发更新: mint
、burn
、swap
、sync
主要是以下几步操作
- 读取当前的区块时间 blockTimestamp
- 计算出与上一次更新的区块时间之间的时间差
timeElapsed
- 如果
timeElapsed > 0
且两个 token 的 reserve 都不为 0,则更新两个累加价格 - 更新两个 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.sol
和 ExampleSlidingWindowOracle.sol
现在,Uniswap TWAP 已经被广泛应用于很多 DeFi 协议,很多时候会结合 Chainlink 一起使用。比如 Compound 就使用 Chainlink 进行喂价并加入 Uniswap TWAP 进行边界校验,防止价格波动太大。