主页 > imtoken官网下载1.0 > 将 Uniswap V2 预言机与存储证明结合使用

将 Uniswap V2 预言机与存储证明结合使用

imtoken官网下载1.0 2024-01-26 05:15:52

译文来自:登联翻译计划[1]

译者:songmint[2]

校对:小熊[3]

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

在本文中,我们将讨论 Price Accumulation Oracle 的工作原理和使用方法。 我们将介绍一个 Solidity 库,用于将预言机集成到您自己的以太坊项目中。 本文将假设您对 Uniswap 等恒定产品市场有深入的了解。 如果您不清楚下面讨论的定价机制,请从这份 [优秀] Uniswap 文档 [5] 开始。

如果您已经知道本文的内容,可以在此处获得代码示例和 solidity 库:如果您想了解更多信息,请继续阅读!

我们通常认为预言机是一个(译者注:Information Transfer System),它从可信/绑定的市场参与者(例如 Maker Price Feed,ChainLink)的多次交易中获取链下信息,然后将这些信息发布到区块链上。 但是 Uniswap V2 预言机不需要任何特定的(译者注:和可信的外部参与者)交易来提供这些有用的信息。 相反,每个(译者注:uniswap)交易所交易都为这个预言机贡献信息。

为了说明带有新预言机的 Uniswap V2 解决了哪些问题,让我们首先看看 Uniswap V1 出了什么问题。

哈希值下载_哈希值 算法_usdt哈希值查询

不要将 Uniswap V1 用作预言机

Uniswap 团队从未将 Uniswap V1 宣传为可行的链上预言机。 正是 Uniswap 的简单、无需许可、链上和面向市场的特性吸引了创意人士将其作为一个整体来使用。 Uniswap V1 预言机的代码很简单:

uint256 tokenPrice = token.balanceOf(uniswapMarket) / address(uniswapMarket).balance

由于 Uniswap V1 市场当前的“价格”只是代币余额与以太币余额的比率,因此计算简单且节省 gas。 但是,问题是它非常不安全。 确实有许多使用 Uniswap V1 作为预言机的相关攻击,但最引人注目的攻击可能是 [bZx/Fulcrum/Compound 攻击,它在 24 小时内净赚了近 100 万美元。 ]()

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

usdt哈希值查询_哈希值下载_哈希值 算法

//  发送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 部署了一个聪明的(译者注:Oracle)系统,记录链上的价格-时间数据流。 因此(译者注:Attacker)在短时间内操纵价格的成本很高,并且不存在单笔交易内操纵价格的可能性。 通过使用“累积”的价格-时间值,价格可用的时间被加权成一个特殊的值,每次代币交换都会花费少量的 gas 来同步这些值。

这是 Uniswap 市场代码的片段:

注意:与 V1 不同,V2 是两个代币之间的市场。 在内部,令牌对的两个令牌分别表示为 token0 和 token1。 他们的余额是 reserve0 和 reserve1。 Uniswap 文档有更多关于代币排序的信息 [6]。

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 _reserve1private {
    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”版本之间的唯一区别是价格的方向。


`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 的值仅在块上的第一笔交易发生时更新。 这是通过获取 reserve0 和 reserve1 的最后已知值(token0 和 token1 的代币余额),计算它们的比率(价格),并将 Scale 与自上次更新“price0CumulativeLast”以来的秒数进行比较来完成的。 price0CumulativeLast 会按照每秒保留值的比例继续累加。 所以要将这个变量转换回价格,需要两个时间点的 price0CumulativeLast 的值,然后使用以下公式:

(price0CumulativeLATEST — price0CumulativeFIRST) / (timestampOfLATEST — timestampOfFIRST)

哈希值下载_哈希值 算法_usdt哈希值查询

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

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

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

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

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

你需要被激励使用bot来不断更新储值(bot的使用费来自系统其他地方的利润); 或者你要求用户发送两笔交易usdt哈希值查询,其中一笔用于快照当前的累计值,这就要求用户在执行交易前延迟一定的秒数,这样延迟的秒数才能满足要求的平均价格。

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

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

以太坊合约的状态存储在“Merkle Trie”中。 这是一种特殊的数据结构,允许一个 32 字节的哈希值来表示存储在每个以太坊合约中的值(交易数据和接收者是分开的)。 这个 32 字节的值称为“stateRoot”,是每个以太坊块都包含的属性(以及您可能更熟悉的属性,如块号、块哈希和时间戳)

(注:以太坊使用了一个名为“Merkle Patricia Trie”的变量,点击链接了解更多信息[9])。

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

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

usdt哈希值查询_哈希值下载_哈希值 算法

// 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;
}

像上面这样的函数可以验证一个完整块的详细信息,并使用从块中检索的 stateRoot(上面已验证)提供的证明(来自 JSON-RPC getProof 调用)确认块的所有字段都是正确的。 historical storage value 从 Uniswap 市场获取当前 price0CumulativeLast 值,计算提供的区块与当前区块的平均价格。 方法是将price0CumulativeLast的增量除以区块时间戳的差值(秒数)

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

注意:链上BLOCKHASH查找操作只适用于最后256个区块,交易上链时你用来存储证明的最早的区块必须包含在最后256个区块中。

介绍 Uniswap-Oracle 库

上述策略由少量客户端代码(处理证明)和大量相当复杂的 Solidity 组成,包括 YUL/assembly 和 Merkle Trie 验证。 [Micah Zoltu]( ------ "Micah Zoltu") 和我,作为 [Keydonix] 的一部分开发 (--------------------) The team, which developed and released [Uniswap-Oracle] (/ part of "Keydonix] (----------------------) development team, developed and release [ Uniswap-Oracle”),一个 Solidity 库,它使其他智能合约能够利用此 oracle 功能。

集成自己的合约,只需要继承基础合约UniswapOracle.sol[11](合约HelloWorld是UniswapOracle),你的合约会继承getPrice函数:

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] 上关注我们以获取更新和新项目! 谢谢迈卡佐尔图

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

哈希值 算法_usdt哈希值查询_哈希值下载

参考文献[1]

登联翻译计划:

[2]

歌曲薄荷:

[3]

小熊:

[4]

Uniswap V2 发布了许多新特性:

[5]

[优秀] Uniswap 文档:#how-it-all-works

[6]

Uniswap 文档有更多关于代币排序的信息:

哈希值下载_哈希值 算法_usdt哈希值查询

[7]

在市面上运行sync():

[8]

自己调整值:

[9]

“Merkle Patricia Trie”的变量,点击链接了解更多:

[10]

rlp编码:

[11]

UniswapOracle.sol:

[12]

Uniswap-Oracle README.me:

[13]

Keydonix 不和谐:

[14]