预言机是连接区块链与链下服务的桥梁,这样就可以从智能合约中查询现实世界的数据。Chainlink 是最大的oracle网络之一,创建于 2017 年,如今已成为许多 DeFi 应用的重要组成部分。https://github.com/XuHugo/solidityproject
Uniswap 虽然是链上应用,但也可以充当oracle。交易者经常使用的每个 Uniswap pair合约也吸引着套利者,他们通过尽量缩小交易所之间的价格差异来赚钱。套利者使 Uniswap 的价格尽可能接近中心化交易所的价格,这也可以看作是将中心化交易所的价格反馈到区块链上。为什么不利用这一事实将pair合约变成价格oracle呢?Uniswap V2 就是这么做的。
在 Uniswap V2 中,价格oracle提供的价格被称为时间加权平均价格(TWAP)。它基本上可以获得两个时间点之间的平均价格。为了做到这一点,合约存储了累计价格:在每次交换之前,它都会计算当前的边际价格(不包括费用),然后乘以上次交换后的秒数,再把这个数字加到前一个数字上。
我在上一段提到了边际价格,这只是两个reserve的关系:
对于价格oracle功能,Uniswap V2 使用边际价格,它不包括滑点和交换费,也不取决于交换量。
由于 Solidity 不支持浮点除法,计算这种价格可能比较麻烦:例如,如果两个reserve的比率是 2/3, 那么价格是0;在计算边际价格时,我们需要提高精确度,Unsiwap V2 为此使用了 UQ112.112 数字。
UQ112.112 本质上是一个小数部分使用 112 位,整数部分使用 112 位的数字。选择 112 位是为了使储备状态变量的存储更优化,这也是变量使用 uint112 类型的原因。另一方面,reserve存储为 UQ112.112 数字的整数部分,这就是为什么在计算价格前要乘以 2**112 的原因。详情请查看 UQ112x112.sol,非常简单。
让我们来实现价格累积。我们只需要添加一个状态变量:
uint32 private blockTimestampLast;
它将存储最后一次交换的时间戳。然后我们需要修改储备更新函数:
function _update(
uint256 balance0,
uint256 balance1,
uint112 reserve0_,
uint112 reserve1_
) private {
...
unchecked {
uint32 timeElapsed = uint32(block.timestamp) - blockTimestampLast;
if (timeElapsed > 0 && reserve0_ > 0 && reserve1_ > 0) {
price0CumulativeLast +=
uint256(UQ112x112.encode(reserve1_).uqdiv(reserve0_)) *
timeElapsed;
price1CumulativeLast +=
uint256(UQ112x112.encode(reserve0_).uqdiv(reserve1_)) *
timeElapsed;
}
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = uint32(block.timestamp);
...
}
UQ112x112.encode 将 uint112 值乘以 2**112,使其成为 uint224 值。然后,将其除以其他reserve,再乘以 timeElapsed。结果会与当前存储的值相加,这就是累积值。