oracle链式存储,Uniswap V2 Oracle和链上累计价格存储证明的结合使用

Uniswap V2发布了许多新功能,包括:

· 令牌:令牌流动性对(代替ETH/DAI和ETH/MKR,Uniswap V2现在原生支持MKR/DAI)

· 内置多跳路由(ETH->DAI->MKR->USDT,如果这是ETH->USDT的价格)

· ERC777兼容性

· 累积价格Oracle

在本文中,我们将讨论此“累加价格Oracle”其工作方式,如何使用它,并介绍一个Solidity库,可以用来将这个Oracle集成到您自己的以太坊项目中。本文将假设您对Uniswap等defi产品市场有深入的了解。

如果您已经了解本文主要的想法,你可以在这里找到代码示例和solidity库:https://github.com/Keydonix/uniswap-oracle。

虽然我们通常认为oracle是一个通过来自受信任/绑定方的交易(例如Maker Price Feed,ChainLink)将链下信息馈入区块链的系统,但Uniswap V2 oracle不需要任何特定的交互即可提供此数据。取而代之的是,每个交换事务都向该oracle提供信息。

为了说明Uniswap V2通过这一新的Oracle功能解决的问题,让我们首先探讨Uniswap V1的问题。

57dab4602948d4ecdd84a85228c51b48.png

不要将Uniswap V1用作Oracle

Uniswap团队从未将Uniswap V1推广为可行的链上Oracle。正是由于Uniswap简单、无许可、链上、面向市场的功能,才吸引了有创造力的人将其作为一个整体来使用。Uniswap V1 oracle的警报器很简单:

uint256 tokenPrice = token.balanceOf(uniswapMarket) /

address(uniswapMarket).balance

由于目前Uniswap V1市场的“价格”只是代币和以太余额的比率,因此计算这些项目的gas效率非常高而且非常简单。但是问题在于它非常不安全。有许多攻击与使用Uniswap V1作为Oracle的项目有关,但最引人注目的攻击可能是bZx/Fulcrum/Compound攻击,它在24小时内净赚近100万美元。

Uniswap V1的问题在于价格反馈是即时的,并且很容易在很短的时间内进行操作。让我们看看下面的psuedo代码示例:

// send 100 ether and receive some number of tokens

uniswapMarket.ethToTokenSwapInput.value(100 ether)(100 ether);

exploitTarget.doSomethingThatUsesUniswapV1AsOracle();

// send all the tokens we received above back

uniswapMarket.tokenToEthSwapInput(token.balanceOf(address(this));

在上面的攻击中,您将向流动性提供者支付非常少量的以太币(约0.6 ETH)的费用(双向为0.3%)。但是当调用exploitTarget时,它会认为令牌比实际值钱得多。如果exploitTarget使用Uniswap V1 oracle来确保您存入的抵押品价值足以提取其他代币,那么该系统将允许您提取比存款凭单多得多的借出代币。

Uniswap V2如何像Oracle一样工作

在上面的例子中,Uniswap V1价格读取是有问题的,因为它们是即时的。V2部署了一个聪明的系统,用于将价格时间数据记录在链上,这种方式在短时间内操作成本很高,并且不可能在单个交易中进行操作。通过使用“累计”价格时间值,可以将价格的可用时间加权为一个特殊值,每次代币掉期都会花费少量的天然气来保持这些值的同步。

以下是Uniswap市场代码片段:

注意:与V1不同,V2是两个代币之间的市场。在内部,这些令牌中的一个需要表示为token0,另一个表示为token1。他们的余额由相应的reserve0和reserve1跟踪。Uniswap Docs具有有关令牌排序的更多信息。

contract UniswapV2Pair {

// Contract Storage Variables:

uint public price0CumulativeLast;

uint public price1CumulativeLast;

// The only place these storage variables are updated:

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

uint32 timeElapsed = blockTimestamp – blockTimestampLast;

if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {

price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;

price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;

}

blockTimestampLast = blockTimestamp;

}

}

price(0 | 1)CumulativeLast是独立的存储变量,它们累积“ price-time”。UQ112x112使代码有点难以阅读,但在概念上并不重要;它只充当高精度除法的包装器。这些累积弹性值的“0”和“1”版本之间的唯一区别是价格的方向。

price0CumulativeLast是“以token1计价的token0的价格”

price1CumulativeLast是“以token0计价的token1的价格”

由于在进行这种累加时数学运算的方式,price0CumulativeLast不是price1CumulativeLast的倒数。对于本文档的其余部分,我们将仅引用price0CumulativeLast,但同样适用于这两个值。另外,price0CumulativeLast不一定在每个区块上都是最新的,因此您要么需要在市场上运行sync(),要么自己调整值。

price0CumulativeLast是一个值,它只更新区块上的第一个事务,取最后一个已知的reserve0和reserve1值(token0和token1的令牌余额),计算它们的比率(price),并按price0CumulativeLast上次更新后的秒数进行缩放。price0CumulativeLast是一个每秒钟按两个储量的比率递增的值。要将此值转换回价格,需要两个时间点值price0CumulativeLast,使用以下公式:

(price0CumulativeLATEST — price0CumulativeFIRST) /

(timestampOfLATEST — timestampOfFIRST)

通过将两个样本中价格累计的差值除以这两个样本之间的秒数,过程被逆转,结果就是该时段的时间加权价格。您选择的窗口是一个重要的安全考虑因素:

· 两个样本之间的秒数越少,更新的时间就越多,但更易于操作。

· 两个样本之间的秒数越长,更新的时间越少,但操作起来就越困难。

在防篡改和最新之间找到正确的平衡点应该仔细考虑您的项目。

现在我们已经有了计算这个价格的公式,但仍然存在一个问题:如何检索链上的历史价格累积信息?

使用智能合约检索历史累计值

利用V2作为链上的Oracle,需要“证明”以下各项的先验值:price0CumulativeLast及其对应的块时间戳。

检索每个值的当前值都很简单(block.timstamp和uniswapMarket.price0CumulativeLast()),但是如何检索旧值呢?最直接的方法是部署一个智能合约,该合约将price0CumulativeLast和时间戳的当前值记录到其自己的存储中,以供以后调用以作为历史值。尽管这可行,但它有一些缺点:

· 必须定期调用以存储快照值,如果您希望该价格馈送在将来持续可用。

· 如果不定期调用,则必须提前计划事务,首先存储当前值,等待一段时间,然后启动使用该历史值的事务。

你总是在某种程度上激励机器人不断更新存储值(机器人费用来自系统中其他地方的利润),或者要求用户发送两个事务,一个用来获取累计值,将他们希望执行的事务延迟一些非常重要的时间,以达到价格馈送平均值所需的秒数。

如果你对为机器人设计经济系统不感兴趣,并且你怀疑用户是否愿意等待发送两个交易,有一个更好的方法来利用Uniswap V2作为价格来源:Merkle Patricia Proof!

利用存储证明检索历史累计值

以太坊合约状态存储在“ Merkle Trie”中,这是一种特殊的数据结构,它允许一个32字节的哈希值代表每个以太坊合约中的每个存储值(具有单独的收据和交易数据尝试)。这个32字节的值称为stateRoot,是每个以太坊块(以及您可能更熟悉的块,例如块号,块哈希和时间戳)的属性。

使用以太坊节点的JSON-RPC接口,您可以调用eth_getProof来检索有效负载,当与该stateRoot值结合使用时,可以证明存储插槽B上的地址A的值为C。

使用链上逻辑,可以将stateRoot和存储证明结合起来以验证存储插槽的值。如果我们针对Uniswap V2市场和price0CumulativeLast的存储插槽,则可以实现所需的基于证明的历史查找。

但是stateRoot查找不能作为EVM操作码使用。唯一相关的操作码是BLOCKHASH,它采用blockNumber并返回32字节的区块哈希。块的blockhash是一个简单的Keccak256哈希,它具有rlp编码的所有各种属性。通过提供一个区块的所有属性(包括stateRoot),我们可以通过散列并将其与链上blockHash查找进行比较,从而验证原始块数据是否有效。验证之后,我们便可以使用区块的必需属性(时间戳和stateRoot)。

// NOTE: Non-functional pseudo code

function verifyBlock(parentBlock, stateRoot, blockNumber, timestamp, …) returns (bool) {

bytes32 _realBlockHash = blockhash(blockNumber);

bytes32 _proposedBlockHash = keccek256(rlpEncode(parentBlock, stateRoot, blockNumber, timestamp, …));

return _proposedBlockHash == _realBlockHash;

}

1. 像上面这样的函数可以验证完整块的详细信息,并确认该块的所有字段都是正确的

2. 使用stateRoot(上面已验证)解析提供的证明(来自JSON-RPC getProof调用)从该块检索历史存储值

3. 从Uniswap market获取当前价格0累积最新值

4. 通过将price0CumulativeLast中的增量除以自验证时间戳以来的秒数,计算所提供块(来自验证的时间戳)和现在之间的平均价格。

此时,您可以在纯粹可根据市场动态从完全去中心化的系统中,在一段可配置的时间内平均获得价格。为了让这个价格反馈被操纵,攻击者不仅需要向一个方向推动这个价格,他们还需要在区块之间长时间保持价格不变,让任何买家都有机会购买价格偏低的资产,这反过来又会纠正价格上涨。

注意:链上BLOCKHASH查找仅适用于过去256个区块,可用于存储证明的最旧块必须在事务落入链上时位于最后256个区块之内。

介绍Uniswap Oracle库

以上策略由少量客户端代码(用于处理证明)和大量相当复杂的Solidity组成,包括YUL/assembly和Merkle Trie验证。作为Keydonix开发团队的一员,Micah Zoltu和我开发并发布了Uniswap-Oracle,这是一个Solidity库,使其他智能合约可以利用此oracle功能。

要与您自己的智能合约集成,只需简单地从基本合约UniswapOracle.sol(contract HelloWorld is UniswapOracle)继承,您的合约将继承getPrice函数:

function getPrice(

IUniswapV2Pair uniswapV2Pair,

address denominationToken,

uint8 minBlocksBack,

uint8 maxBlocksBack,

ProofData memory proofData)

public view

returns (

uint256 price,

uint256 blockNumber

)

需要访问此Uniswap price的您自己的函数将需要接收此证明数据作为参数传递给此内部getPrice函数调用。请参阅Uniswap Oracle自述文件.me集成文档。

Uniswap-Oracle库未经审核。任何对主网有价值的应用程序都应该进行全面审计;请确保您的应用程序的审计也包括Uniswap的Oracle代码。

声明: 本文由入驻基智地平台的作者撰写,观点仅代表作者本人,不代表基智地立场;基智地发布此信息的目的在于传播更多信息,与本站立场无关。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值