主页 > imtoken官网下载1.0 > 将 Uniswap V2 预言机与存储证明结合使用
将 Uniswap V2 预言机与存储证明结合使用
译文来自:登联翻译计划[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 出了什么问题。
不要将 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 的问题在于它的价格流动是瞬时的,很容易在短时间内(包括瞬间)被操纵。 让我们看一下下面的伪代码示例:
// 发送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 _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”版本之间的唯一区别是价格的方向。
- `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)
将两个时间点的“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 查找进行比较,以验证原始区块数据是否有效。 一旦验证通过,我们就可以使用块所需的属性(时间戳和状态根)。
// 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
参考文献[1]
登联翻译计划:
[2]
歌曲薄荷:
[3]
小熊:
[4]
Uniswap V2 发布了许多新特性:
[5]
[优秀] Uniswap 文档:#how-it-all-works
[6]
Uniswap 文档有更多关于代币排序的信息:
[7]
在市面上运行sync():
[8]
自己调整值:
[9]
“Merkle Patricia Trie”的变量,点击链接了解更多:
[10]
rlp编码:
[11]
UniswapOracle.sol:
[12]
Uniswap-Oracle README.me:
[13]
Keydonix 不和谐:
[14]