前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用带有存储证明的Uniswap V2 预言机

使用带有存储证明的Uniswap V2 预言机

作者头像
Tiny熊
发布2023-01-09 17:53:23
1.1K0
发布2023-01-09 17:53:23
举报
文章被收录于专栏:深入浅出区块链技术

译文出自:登链翻译计划[1] 译者:songmint[2] 校对:Tiny 熊[3]

Uniswap V2 发布了许多新特性[4], 包括:

  • 代币:代币流动性对(不再需要 ETH/DAI 和 ETH/MKR 这种方式接力, Uniswap V2 可以原生支持 MKR/DAI 了)
  • 内建对多跳兑换路由的支持(例如可以通过 ETH->DAI->MKR->USDT, 获取 ETH->USDT 的价格)
  • 兼容 ERC777
  • 价格累积预言机

在本文中,我们将讨论“价格累积预言机”的工作原理和使用方法。并且我们将介绍一个可将预言机集成到你自己以太坊项目中的 Solidity 库。本文将假设你对 Uniswap 此类恒定乘积市场有深入的了解。如果你不清楚下面即将讨论的定价机制,请从这篇[优秀]的 Uniswap 文档[5]开始。

如果您已经了解本文的主旨所在,可以在此处获得代码示例和 solidity 库:https://github.com/Keydonix/uniswap-oracle 如果您想了解更多信息,请继续阅读!

我们通常认为, 预言机可以看做一个(译者注:信息转移系统), 它从可信的/被绑定的市场参与者(例如 Maker Price Feed、ChainLink)的多笔交易中获取链下信息, 然后将这些信息公布到区块链上. 但是 Uniswap V2 预言机提供这些有用的信息时, 不需要任何特定的(译者注:和可信外部参与者的)交易。相反,每个(译者注:uniswap 上的)兑换交易都会为这个预言机贡献信息。

为了说明带有新预言机的 Uniswap V2 解决了什么问题,我们首先看看 Uniswap V1 的问题所在.

别把 Uniswap V1 用作预言机

Uniswap 团队从未将 Uniswap V1 宣传为可行的链上预言机。正是由于 Uniswap 简单、无需许可、链上且面向市场的功能,才吸引了富有创造力的人将其作为一个整体使用。Uniswap V1 预言机的代码很简单:

代码语言:javascript
复制
uint256 tokenPrice = token.balanceOf(uniswapMarket) / address(uniswapMarket).balance

由于 Uniswap V1 市场的当前“价格”只是代币余额和以太币余额的比率,因此计算简单且节省燃料。然而,问题在于它非常地不安全。事实已经有许多因使用 Uniswap V1 作为预言机而导致的相关攻击,但最引人注目的攻击可能是 [bZx/Fulcrum/Compound 攻击,该攻击在 24 小时内净赚了近 100 万美元。](https://cointelegraph.com/news/are-the-bzx-flash-loan-attacks-signaling-the-end-of-defi)

Uniswap V1 的问题在于,其价格流是瞬间的,并且很容易在短时间内(包括瞬间)被操纵。让我们看看下面的伪代码示例:

代码语言:javascript
复制
//  发送100个 ether, 接受一些token
uniswapMarket.ethToTokenSwapInput.value(100 ether)(100 ether);exploitTarget.doSomethingThatUsesUniswapV1AsOracle();

// 返还上一步我们接收到的token
uniswapMarket.tokenToEthSwapInput(token.balanceOf(address(this));

在上述攻击中,你将向流动性提供者支付非常小的以太币费用,大约 0.6 ETH(双向 0.3%)。然而,当调用 exploitTarget 时,它会认为代币比实际更有价值。如果 exploitTarget 使用 Uniswap V1 预言机 来确保你存入的抵押品的价值足以提取其他一些代币,那么该系统将允许你提取比存款凭证多得多的借出代币。

Uniswap V2 如何扮演预言机

在上面的例子中,Uniswap V1 读取的价格瞬间就会发生变化, 因此存在问题。V2 部署了一个聪明的(译者注:预言机)系统,它把价格-时间数据流记录在链上. 因而(译者注:攻击者)在短时间内操纵价格的成本很高,而且不可能在单个交易中进行价格操纵。通过使用“累积”的价格-时间值,价格的可用时间被加权到一个特殊的值中,每次代币交换都会花费少量燃料来同步这些值。

这是 Uniswap market 代码的一个片段:

注意:与 V1 不同,V2 是两个代币之间的市场。在内部,一对代币中的两个代币被分别表示为 token0token1。它们的余额是 reserve0reserve1. Uniswap Docs 有更多关于代币排序的信息[6].

代码语言:javascript
复制
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 是累积“价格-时间”的独立存储变量。UQ112x112 使代码有点难以阅读,但在概念上并不重要;它仅作为高精度除法的包装器。这些 cumulativeLast 值的“0”和“1”版本之间的唯一区别是价格的方向。

代码语言:javascript
复制

- `price0CumulativeLast` is “the price of `token0` denominated in `token1`”
- `price1CumulativeLast` is “the price of `token1` denominated in `token0`”

由于执行加法时的数学运算方法,price0CumulativeLast _ 不是 _ price1CumulativeLast_ 的倒数。对于本文档的其余部分,我们将仅参考_ price0CumulativeLast,但同样适用于这两个值。此外, price0CumulativeLast 不一定在每个区块上都是最新的,因此您要么需要 在市场上运行 sync()[7],或 自己调整值[8].

price0CumulativeLast 的值仅在块上的第一笔交易发生时进行更新. 方法是采用上一个已知的 reserve0reserve1 值(token0token1 的代币余额),计算它们的比率(价格),并对其进行缩放,缩放比例来自于上次更新“price0CumulativeLast”后历经的秒数。price0CumulativeLast会不断累加每秒 reserve 值的比率。因此要将此变量重新转换为价格,需要 price0CumulativeLast 在两个时间点上的值,然后使用以下公式:

代码语言:javascript
复制
(price0CumulativeLATEST — price0CumulativeFIRST) / (timestampOfLATEST — timestampOfFIRST)

通过将两个时间点中“price0CumulativeLast”的差值除以这两个样本之间的秒数,得到了该时间段的时间加权价格。在这个计算的过程中,选择的时间窗口会是一个重要的安全因素:

  • 样板时间点的间隔越小, 价格越新, 但也越容易被操纵
  • 样板时间点的间隔越大, 价格越不那么新,但也更加难以操纵

您需要为自己项目仔细考虑这个值, 在防篡改和价格及时之间找到适当的平衡。有了这个价格的计算公式,还剩一个问题:如何在链上获取历史价格累计信息?

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

利用 V2 作为链上预言机需要“证明”以下先验值:price0CumulativeLast及其对应的块时间戳

检索以上先验值的当前值是非常简单的(block.timstamp & uniswapMarket.price0CumulativeLast())但是你如何检索旧值?最直接的方法是部署一个智能合约,将price0CumulativeLast 的当前值和时间戳记录到自己的存储中,以便稍后作为历史值调用。虽然这是可行的,但它有一些缺点:

  • 如果希望价格源持续可用, 那么你必须定期调用以存储快照值
  • 如果是不定期调用,您必须提前计划好您的交易,首先存储当前值,等待一段时间,然后触发使用该历史值的交易

您需要被激励使用机器人去不断更新存储值(机器人的使用费来自系统其他地方的利润); 或者您要求用户发送两笔交易,其中一笔用于快照当下的累积值,并且这种做法需要用户延迟一定秒数再执行交易,使得延迟的秒数能够满足平均价格所需要求.

如果您对为机器人设计经济系统不感兴趣,或者您怀疑用户会愿意等待发送两笔交易,那么有一种更好的方法可以利用 Uniswap V2 作为价格源:Merkle Patricia Proofs!

使用存储证明检索历史累积值

以太坊合约的状态被存储在“Merkle Trie”中. 这是一种特殊的数据结构,允许一个 32 字节哈希值代表每个以太坊合约中存储的值(交易数据和接受方会单独分开)。这个 32 字节的值被称为为“stateRoot”,是每个以太坊区块都会包含的属性(还有你可能更熟悉的那些,比如区块号、区块哈希和时间戳)

(Note:以太坊使用一种被称为“Merkle Patricia Trie” 的变量, 点击链接你可以了解更多[9]).

使用以太坊节点的 JSON-RPC 接口,您可以调用 eth_getProof 来检索有效负载,当结合此 stateRoot 值时,可以证明位于存储槽 B 的地址 A 的值是 C。使用链上逻辑,可以结合 stateRoot 和存储证明来验证存储槽的值。如果我们以 Uniswap V2 市场和price0CumulativeLast的存储槽为目标,我们就可以实现基于证明的历史查找。

但是,“stateRoot”的查找操作并没有 EVM 操作码;唯一相关的操作码是“BLOCKHASH”,它接受一个块号并返回 32 字节的块哈希值。一个区块的块哈希值是其所有属性的 Keccak256 哈希值,rlp-encoded[10]。通过提供区块的所有属性,包括“stateRoot”,我们先 hash, 然后与链上 blockHash 查找进行比较来验证原始区块数据是否有效。一旦验证通过,我们就可以使用块所需的属性(时间戳和 stateRoot)。

代码语言:javascript
复制
// 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 市场获取当前的 price0CumulativeLast
  4. 计算所提供区块与当前区块之间的平均价格,做法是price0CumulativeLast 的增量除以区块时间戳的差异(秒数)

此时,内存中的价格是某个可配置时间段内的平均价格,它来自于一个完全去中心化的系统。为了操纵这个价格,攻击者不仅需要将价格推向一个方向,他们还需要在区块之间长时间保持价格。这反而让其他买家都有机会购买价格过低的资产,从而纠正错误的价格。

注意:链上 BLOCKHASH查找操作仅适用于最近的 256 个区块,您用于存储证明的最早的区块必须包含在 交易上链 时的最近 256 个区块内。

介绍 Uniswap-Oracle 库

上述策略包括少量客户端代码(用于处理证明)和大量相当复杂的 Solidity,包括 YUL/assembly 和 Merkle Trie 验证。[Micah Zoltu](https://medium.com/u/9e15b5664ca?source=post_page-----3530e699e1d3------------------------ ------ "Micah Zoltu") 和我,作为 [Keydonix] 的一部分(https://medium.com/u/f605e3324ca4?source=post_page-----3530e699e1d3-------- ----------------------)开发团队,开发并发布了[Uniswap-Oracle](https://github.com/Keydonix/uniswap-oracle / "Keydonix] 的一部分(https://medium.com/u/f605e3324ca4?source=post_page-----3530e699e1d3-------- ----------------------)开发团队,开发并发布了[Uniswap-Oracle"),一个 Solidity 库,它使其他智能合约能够利用此 oracle 功能。

要与您自己的合约集成,您只需继承基础合约 UniswapOracle.sol[11]contract HelloWorld is UniswapOracle),你的合约将继承 getPrice 函数:

代码语言:javascript
复制
function getPrice(
    IUniswapV2Pair uniswapV2Pair,
    address denominationToken,
    uint8 minBlocksBack,
    uint8 maxBlocksBack,
    ProofData memory proofData)

  public view

  returns (
    uint256 price,
    uint256 blockNumber
  )

若您需要访问 Uniswap 价格, 则需将 proofData 作为参数传递给内部“getPrice”函数调用。请参阅 Uniswap-Oracle README.me[12] 以获取集成文档。

Uniswap-Oracle 库是未经审计的。任何对主网上的价值负责的应用都应该被全面审计;请确保您的应用程序的审核也涵盖 Uniswap-Oracle 代码。

通过 Keydonix Discord[13] 提出问题并获得集成帮助,并在 Twitter 上关注我们[14] 以获取更新和新项目!感谢 Micah Zoltu

原文链接:https://medium.com/@epheph/using-uniswap-v2-oracle-with-storage-proofs-3530e699e1d3

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

songmint: https://learnblockchain.cn/people/13263

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

Uniswap V2 发布了许多新特性: https://uniswap.org/blog/launch-uniswap-v2/

[5]

[优秀]的Uniswap 文档: https://uniswap.org/docs/v2/#how-it-all-works

[6]

Uniswap Docs 有更多关于代币排序的信息: https://uniswap.org/docs/v2/technical-considerations/pair-addresses/

[7]

在市场上运行 sync(): https://github.com/Uniswap/v2-core/blob/4dd59067c76dea4a0e8e4bfdda41877a6b16dedc/contracts/UniswapV2Pair.sol

[8]

自己调整值: https://github.com/Keydonix/uniswap-oracle/blob/1c739f0ea575b15c1a52b15c1a525/contracts/source/UniswapOracle.sol

[9]

“Merkle Patricia Trie” 的变量, 点击链接你可以了解更多: https://medium.com/codechain/modified-merkle-patricia-trie-how-ethereum-saves-a-state-e6d7555078dd

[10]

rlp-encoded: https://ethereum.org/en/

[11]

UniswapOracle.sol: https://github.com/Keydonix/uniswap-oracle/blob/master/contracts/source/UniswapOracle.sol

[12]

Uniswap-Oracle README.me: https://github.com/Keydonix/uniswap-oracle/blob/master/README.md

[13]

Keydonix Discord: https://github.com/Keydonix/uniswap-oracle/blob/master/README.md

[14]

并在 Twitter 上关注我们: https://twitter.com/keydonix

Twitter : https://twitter.com/NUpchain Discord : https://discord.gg/pZxy3CU8mh

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 别把 Uniswap V1 用作预言机
  • Uniswap V2 如何扮演预言机
  • 使用智能合约检索历史累积值
  • 使用存储证明检索历史累积值
  • 介绍 Uniswap-Oracle 库
    • 参考资料
    相关产品与服务
    对象存储
    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档