链资讯 链资讯
Ctrl+D收藏链资讯
首页 > 区块链 > 正文

IME:价格预言机的使用总结(二):UniswapV2篇_CUM币

作者:

时间:

前言

该系列的前一篇文章介绍了Chainlink价格预言机的使用,其目前也被大部分DeFi应用所使用,但依然存在局限性。首先是所支持的Token的覆盖率还不全,尤其是长尾资产,大多还未支持,比如SHIB,目前只在BSC主网有SHIB/USD的PriceFeed,而其它网络的都还没有,连Ethereum的都还没支持。其次,有些资产的偏差阈值较大,价格更新也比较慢,可能长达十几二十个小时才会更新价格,比如BNT。

这时候就需要考虑其它价格预言机了,而UniswapV2和UniswapV3都是不错的选择。

本篇先来聊聊如何使用UniswapV2作为价格预言机。

UniswapV2价格预言机

UniswapV2使用的价格预言机称为TWAP,即时间加权平均价格。不同于链下聚合的Chainlink取自多个不同交易所的数据作为数据源,TWAP的数据源来自于Uniswap自身的交易数据,价格的计算也都是在链上执行的,因此,TWAP属于链上预言机。

TWAP的原理比较简单,首先,在UniswapV2Pair合约中,会存储两个变量price0CumulativeLast和price1CumulativeLast,在_update()函数中会更新这两个变量,其相关代码如下:

contract?UniswapV2Pair?{

??...

??uint32?private?blockTimestampLast;

??uint?public?price0CumulativeLast;

??uint?public?price1CumulativeLast;

??...

??//?update?reserves?and,?on?the?first?call?per?block,?price?accumulators

??function?_update(uint?balance0,?uint?balance1,?uint112?_reserve0,?uint112?_reserve1)?private?{

????...

????uint32?blockTimestamp?=?uint32(block.timestamp?%?2**32);

????uint32?timeElapsed?=?blockTimestamp?-?blockTimestampLast;

????if?(timeElapsed?&ampgt;?0?&ampamp;&ampamp;?_reserve0?!=?0?&ampamp;&ampamp;?_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;

????...

??}

}

price0CumulativeLast和price1CumulativeLast分别记录了token0和token1的累计价格。所谓累计价格,其代表的是整个合约历史中每一秒的Uniswap价格总和。且只会在每个区块第一笔交易时执行累加计算,累加的值不是当前区块的第一笔交易的价格,而是在这之前的最后一笔交易的价格,所以至少也是上个区块的价格。取自之前区块的价格,可以大大提高操控价格的成本,所以自然也提高了安全性。

Curve发布禁用crvUSD价格预言机Chainlink限制提案:金色财经报道,Curve发布禁用crvUSD价格预言机Chainlink限制提案。提案称,目前,crvUSD中的所有价格预言机都具有Chainlink +/- 1.5% 的“安全限制”。这意味着,如果 Chainlink 价格与内部预言机价格 (EMA) 偏差太大(1.5%),它将使用 Chainlink 的来源。看来,当市场波动很大时,Chainlink价格会造成不必要的损失。如果波动足够高,使用 chainlink 限制的价格预言机仍然可能会出现偏差。

因此,我建议在所有市场的价格预言机合约中禁用Chainlink限制,除了旧的frxeth市场除外(很快就会被新的市场取代)。具体做法是通过set_use_chainlink(False)从控制器所有者(Ownership DAO)调用所有价格预言机合约来完成。[2023/8/20 18:11:02]

如上图所示,合约的第一个区块为Block122,这时候,价格和时间差都为0,所以累计价格也为?0。到了下一个区块Block123,这时候取自上个区块的最后一口价格10.2,且经过的时间差为7,因此就可以计算出累计价格priceCumulative=10.2*7=71.4。再到下个区块Block124,取自上一口价格10.3,两个区块间的时间差为8,那此时的累计价格就变成了71.4+(10.3*8)=153.8。Block125的时候也同理,上口价格为10.5,区块时间差为5,所以最新的累计价格就变成了153.8+(10.5*5)=206.3。

有了这个基础之后,就可以计算TWAP了。

固定时间窗口TWAP

计算TWAP的原理也是非常简单,如上图所示,这是计算时间间隔为1小时的TWAP,取自开始和结束时的累计价格和两区块当时的时间戳,两者的累计价格相减,再除以两者之间的时间差,就算出这1小时内的TWAP价格了。

这是TWAP最简单的计算方式,也称为固定时间窗口的TWAP。下面来讲讲具体如何实现。

Uniswap官方也有提供了一个示例代码来计算固定时间窗口的TWAP,其代码放在v2-periphery项目中:

https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleOracleSimple.sol

该示例代码也比较简单,我们直接贴上代码看看:

pragma?solidity?=0.6.6;

import?'

????function?update()?external?{

????????(uint?price0Cumulative,?uint?price1Cumulative,?uint32?blockTimestamp)?=

????????????UniswapV2OracleLibrary.currentCumulativePrices(address(pair));

????????uint32?timeElapsed?=?blockTimestamp?-?blockTimestampLast;?//?overflow?is?desired

????????//?ensure?that?at?least?one?full?period?has?passed?since?the?last?update

派盾:Sturdy Finance被攻击根本原因在于cB-stETH-STABLE价格预言机存在漏洞:6月12日消息,据派盾分析,Sturdy Finance被攻击的根本原因在于计算cB-stETH-STABLE资产价格的价格预言机存在漏洞。

今日早些时候消息,据派盾监测,DeFi借贷协议Sturdy遭黑客攻击,此次攻击或通过价格操纵实行,攻击者已将442.6枚ETH(价值约77万美元)转至Tornado Cash。[2023/6/12 21:31:15]

????????require(timeElapsed?&ampgt;=?PERIOD,?'ExampleOracleSimple:?PERIOD_NOT_ELAPSED');

????????//?overflow?is?desired,?casting?never?truncates

????????//?cumulative?price?is?in?(uq112x112?price?*?seconds)?units?so?we?simply?wrap?it?after?division?by?time?elapsed

????????price0Average?=?FixedPoint.uq112x112(uint224((price0Cumulative?-?price0CumulativeLast)?/?timeElapsed));

????????price1Average?=?FixedPoint.uq112x112(uint224((price1Cumulative?-?price1CumulativeLast)?/?timeElapsed));

????????price0CumulativeLast?=?price0Cumulative;

????????price1CumulativeLast?=?price1Cumulative;

????????blockTimestampLast?=?blockTimestamp;

????}

????//?note?this?will?always?return?0?before?update?has?been?called?successfully?for?the?first?time.

????function?consult(address?token,?uint?amountIn)?external?view?returns?(uint?amountOut)?{

????????if?(token?==?token0)?{

????????????amountOut?=?price0Average.mul(amountIn).decode144();

????????}?else?{

????????????require(token?==?token1,?'ExampleOracleSimple:?INVALID_TOKEN');

????????????amountOut?=?price1Average.mul(amountIn).decode144();

????????}

????}

}

PERIOD指定为了24小时,说明这个示例计算TWAP的固定时间窗口为24小时,即每隔24小时才更新一次价格。

该示例也只保存一个交易对的价格,即token0-token1的价格。price0Average和price1Average分别就是token0和token1的TWAP价格。比如,token0为WETH,token1为USDC,那price0Average就是WETH对USDC的价格,而price1Average则是USDC对WETH的价格。

欧易Web3钱包与去中心化价格预言机Nest Protocol达成合作:据官方消息,欧易Web3钱包宣布与去中心化价格预言机Nest Protocol达成合作。欧易Web3钱包用户可以通过Discover板块搜索并进入Nest Protocol,进行期权、期货、Swap、NFT等交易。

据悉,欧易Web3钱包是欧易交易所研发的Web3新产品,旨在为用户提供便捷、安全、易操作的一站式Web3入口,降低用户Web3学习成本。[2023/1/4 9:52:20]

update()函数就是更新TWAP价格的函数,这一般需要链下程序的定时任务来触发,按照这个示例的话,就是链下的定时任务需要每隔24小时就定时触发调用update()函数。

update()函数的实现逻辑也和上面所述的公式一致:

读取出当前最新的累计价格和当前的时间戳;

计算出当前时间和上一次更新价格时的时间差timeElapsed,要求该时间差需要达24小时;

根据公式TWAP=(priceCumulative-priceCumulativeLast)/timeElapsed计算得到最新的TWAP,即priceAverage;

更新priceCumulativeLast和blockTimestampLast为当前最新的累计价格和时间戳。

不过,有一点需要注意,因为priceCumulative本身计算存储时是做了左移112位的操作的,所以计算所得的priceAverage也是左移了112位的。

consult()函数则可查询出用TWAP价格计算可兑换的数量。比如,token0为WETH,token1为USDC,假设WETH的价格为3000USDC,查询consult()时,若传入的参数token为token0的地址,amountIn为2,那输出的amountOut则为3000*2=6000,可理解为若支付2WETH,就可根据价格换算成6000USDC。

滑动时间窗口TWAP

固定时间窗口TWAP的原理和实现,比较简单,但其最大的不足就是价格变化不够平滑,时间窗口越长,价格变化就可能会越陡峭。因此,在实际应用中,更多其实是用滑动时间窗口的TWAP。

所谓滑动时间窗口TWAP,就是说,计算TWAP的时间窗口并非固定的,而是滑动的。这种算法的主要原理就是将时间窗口划分为多个时间片段,每过一个时间片段,时间窗口就会往右滑动一格,如下图所示:

上图所示的时间窗口为1小时,划分为了6个时间片段,每个时间片段则为10分钟。那每过10分钟,整个时间窗口就会往右滑动一格。而计算TWAP时的公式则没有变,依然还是取自时间窗口的起点和终点。如果时间窗口为24小时,按照固定时间窗口算法,每隔24小时TWAP价格才会更新,但使用滑动时间窗口算法后,假设时间片段为1小时,则TWAP价格是每隔1小时就会更新。

Uniswap官方也同样提供了这种滑动时间窗口TWAP实现的示例代码,其Github地址为:

https://github.com/Uniswap/v2-periphery/blob/master/contracts/examples/ExampleSlidingWindowOracle.sol

我们也贴上代码看看:

pragma?solidity?=0.6.6;

import?'

????address?public?immutable?factory;

????//?the?desired?amount?of?time?over?which?the?moving?average?should?be?computed,?e.g.?24?hours

加密货币数据公司Nomics推出AI驱动的7天价格预测:加密货币数据公司Nomics正在将人工智能应用于通常混乱的加密货币交易。Nomics宣布推出可以进行7天加密货币价格预测的AI系统。该公司首席执行官克莱·柯林斯(Clay Collins)告诉Cointelegraph,这些预测仅适用于寻求预测的散户投资者,不应视为福音。(Cointelegraph)[2020/4/23]

????uint?public?immutable?windowSize;

????//?the?number?of?observations?stored?for?each?pair,?i.e.?how?many?price?observations?are?stored?for?the?window.

????//?as?granularity?increases?from?1,?more?frequent?updates?are?needed,?but?moving?averages?become?more?precise.

????//?averages?are?computed?over?intervals?with?sizes?in?the?range:

????//???

????//?e.g.?if?the?window?size?is?24?hours,?and?the?granularity?is?24,?the?oracle?will?return?the?average?price?for

????//???the?period:

????//???,?now]

????uint8?public?immutable?granularity;

????//?this?is?redundant?with?granularity?and?windowSize,?but?stored?for?gas?savings?&ampamp;?informational?purposes.

????uint?public?immutable?periodSize;

????//?mapping?from?pair?address?to?a?list?of?price?observations?of?that?pair

????mapping(address?=&ampgt;?Observation)?public?pairObservations;

????constructor(address?factory_,?uint?windowSize_,?uint8?granularity_)?public?{

????????require(granularity_?&ampgt;?1,?'SlidingWindowOracle:?GRANULARITY');

????????require(

????????????(periodSize?=?windowSize_?/?granularity_)?*?granularity_?==?windowSize_,

????????????'SlidingWindowOracle:?WINDOW_NOT_EVENLY_DIVISIBLE'

????????);

????????factory?=?factory_;

????????windowSize?=?windowSize_;

????????granularity?=?granularity_;

比特币2018年价格预计在6500-22000美元之间:据CNBC消息,济数据分析企业DataTrek Research联合创始人称,由于比特币和其他加密货币较难被估值,加上其使用渠道尚未完全确定其经济用途,所以波动性将会继续。当前加密货币的主要用途是保护个人财产,甚至包括,逃税等。市场上30余种加密货币总市值逾10亿美元。预计2018年比特币交易区间在6500-22000美元之间,估值中位14035美元接近当前比特币价格水平,说明这一估值较为准确。[2017/12/28]

????}

????//?returns?the?index?of?the?observation?corresponding?to?the?given?timestamp

????function?observationIndexOf(uint?timestamp)?public?view?returns?(uint8?index)?{

????????uint?epochPeriod?=?timestamp?/?periodSize;

????????return?uint8(epochPeriod?%?granularity);

????}

????//?returns?the?observation?from?the?oldest?epoch?(at?the?beginning?of?the?window)?relative?to?the?current?time

????function?getFirstObservationInWindow(address?pair)?private?view?returns?(Observation?storage?firstObservation)?{

????????uint8?observationIndex?=?observationIndexOf(block.timestamp);

????????//?no?overflow?issue.?if?observationIndex?+?1?overflows,?result?is?still?zero.

????????uint8?firstObservationIndex?=?(observationIndex?+?1)?%?granularity;

????????firstObservation?=?pairObservations;

????}

????//?update?the?cumulative?price?for?the?observation?at?the?current?timestamp.?each?observation?is?updated?at?most

????//?once?per?epoch?period.

????function?update(address?tokenA,?address?tokenB)?external?{

????????address?pair?=?UniswapV2Library.pairFor(factory,?tokenA,?tokenB);

????????//?populate?the?array?with?empty?observations?(first?call?only)

????????for?(uint?i?=?pairObservations.length;?i?&amplt;?granularity;?i++)?{

????????????pairObservations.push();

????????}

????????//?get?the?observation?for?the?current?period

????????uint8?observationIndex?=?observationIndexOf(block.timestamp);

????????Observation?storage?observation?=?pairObservations;

????????//?we?only?want?to?commit?updates?once?per?period?(i.e.?windowSize?/?granularity)

????????uint?timeElapsed?=?block.timestamp?-?observation.timestamp;

????????if?(timeElapsed?&ampgt;?periodSize)?{

????????????(uint?price0Cumulative,?uint?price1Cumulative,)?=?UniswapV2OracleLibrary.currentCumulativePrices(pair);

????????????observation.timestamp?=?block.timestamp;

????????????observation.price0Cumulative?=?price0Cumulative;

????????????observation.price1Cumulative?=?price1Cumulative;

????????}

????}

????//?given?the?cumulative?prices?of?the?start?and?end?of?a?period,?and?the?length?of?the?period,?compute?the?average

????//?price?in?terms?of?how?much?amount?out?is?received?for?the?amount?in

????function?computeAmountOut(

????????uint?priceCumulativeStart,?uint?priceCumulativeEnd,

????????uint?timeElapsed,?uint?amountIn

????)?private?pure?returns?(uint?amountOut)?{

????????//?overflow?is?desired.

????????FixedPoint.uq112x112?memory?priceAverage?=?FixedPoint.uq112x112(

????????????uint224((priceCumulativeEnd?-?priceCumulativeStart)?/?timeElapsed)

????????);

????????amountOut?=?priceAverage.mul(amountIn).decode144();

????}

????//?returns?the?amount?out?corresponding?to?the?amount?in?for?a?given?token?using?the?moving?average?over?the?time

????//?range?,?now]

????//?update?must?have?been?called?for?the?bucket?corresponding?to?timestamp?`now?-?windowSize`

????function?consult(address?tokenIn,?uint?amountIn,?address?tokenOut)?external?view?returns?(uint?amountOut)?{

????????address?pair?=?UniswapV2Library.pairFor(factory,?tokenIn,?tokenOut);

????????Observation?storage?firstObservation?=?getFirstObservationInWindow(pair);

????????uint?timeElapsed?=?block.timestamp?-?firstObservation.timestamp;

????????require(timeElapsed?&amplt;=?windowSize,?'SlidingWindowOracle:?MISSING_HISTORICAL_OBSERVATION');

????????//?should?never?happen.

????????require(timeElapsed?&ampgt;=?windowSize?-?periodSize?*?2,?'SlidingWindowOracle:?UNEXPECTED_TIME_ELAPSED');

????????(uint?price0Cumulative,?uint?price1Cumulative,)?=?UniswapV2OracleLibrary.currentCumulativePrices(pair);

????????(address?token0,)?=?UniswapV2Library.sortTokens(tokenIn,?tokenOut);

????????if?(token0?==?tokenIn)?{

????????????return?computeAmountOut(firstObservation.price0Cumulative,?price0Cumulative,?timeElapsed,?amountIn);

????????}?else?{

????????????return?computeAmountOut(firstObservation.price1Cumulative,?price1Cumulative,?timeElapsed,?amountIn);

????????}

????}

}

要实现滑动时间窗口算法,就需要将时间分段,还需要保存每个时间段的priceCumulative。在这实现的示例代码中,定义了结构体Observation,用来保存每个时间片段的数据,包括两个token的priceCumulative和记录的时间点timestamp。还定义了pairObservations用来存储每个pair的Observation数组,而数组实际的长度取决于将整个时间窗口划分为多少个时间片段。

windowSize表示时间窗口大小,比如24小时,granularity是划分的时间片段数量,比如24段,periodSize则是每时间片段的大小,比如1小时,是由windowSize/granularity计算所得。这几个值都在构造函数中进行了初始化。

触发update()函数则更新存储最新时间片段的observation,如时间片段大小为1小时,即每隔1小时就要触发update()函数一次。因为这个示例中是支持多个pair的,所以update()时需要指定所要更新的两个token。

而查询当前TWAP价格的计算就在consult()函数里实现了。首先,先获取到当前时间窗口里的第一个时间片段的observation,也算出当前时间与第一个observation时间的时间差,且读取出当前最新的priceCumulative,之后就在computeAmountOut()函数里计算得到最新的TWAP价格priceAverage,且根据amountIn算出了amountOut并返回。

总结

本文我们主要介绍了被广泛使用的一种链上预言机TWAP,且介绍了固定时间窗口和滑点时间窗口两种算法的TWAP。虽然,TWAP是由Uniswap推出的,但因为很多其他DEX也采用了和Uniswap一样的底层实现,如SushiSwap、PancakeSwap等,所以这些DEX也可以用同样的算法计算出对应的TWAP。

但使用UniswapV2的TWAP,其主要缺陷就是需要链下程序定时触发update()函数,存在维护成本。UniswapV3的TWAP则解决了这个问题,下一篇会来聊聊其具体是如何实现的。

文章首发于「Keegan小钢」公众号:

https://mp.weixin.qq.com/s?__biz=MzA5OTI1NDE0Mw==&mid=2652494441&idx=1&sn=57a97690390b93770c5a906dce4157c8&chksm=8b685079bc1fd96f9ab60cc1b41b8642abf807a13a37c12f05a280be2e03f3a9288a047b5739&token=1584634265&lang=zh_CN#rd

标签:IMECUMTOKENSOCepikprime币行情CUM币Oscar TokenSOCKS

区块链热门资讯
数字艺术:除了当头像 NFT还有这九大用途_数字艺术大赛官网

我们先来回顾一下NFT市场,NFT市场在2021年增长了200倍以上,NFT交易的总价值在2020年仅有8250万美元,但到2021年就超过了170亿美元.

EARN:如何打造可持续的 X to Earn 经济体系_NFTMart Token

加密经济最大的优势之一,是可以灵活的设计激励结构,从而几乎不受制约的建造任何形式的经济系统。在加密经济系统中,Token是生产关系的核心,也是重要的资源协调工具.

CUM:SocialFi:改变区块链行业全球化的关键技术_BLO

SocialFi:改变区块链行业全球化的关键技术2021年上半年,社交DAOFriendswithBenefits开始融资,截至9月初以1亿美元的估值完成千万美元的融资.

NFT:晚间必读5篇 | Tornado Cash 黑客的天堂?_VENT

1.金色观察|Layer1扩容:分片和可组合性以太坊和其他公链,都在尝试利用多链结构扩容,例如以太坊2.0可能实现的同构分片、波卡正在实施的异构分片、COSMOS的跨链结构.

数字钱包:PayPal首席执行官:我们将在数字钱包方面加倍努力_RES

周三,PayPal的首席执行官DanSchulman在公司季度财报电话会议上强调,数字钱包将在这家数字支付公司的未来增长中发挥关键作用。Schulman表示:“我们需要在数字钱包上加倍努力.

元宇宙:晚间必读5篇 | 加密VC都在投什么?_MetaMask安卓安装包

1.Optimism将发行代币OP并公布代币经济学4月27日消息,以太坊扩容方案Optimism正式宣布将发行代币OP并公布代币经济学,代币初始总供应量为4,294,967,296个OP代币.