Uniswap V2发布了许多新功用,包括:
·
· 内置多跳路由(ETH->DAI->MKR->USDT,假如这是ETH->USDT的
· ERC777兼容性
· 累积价钱Oracle
在本文中,我们将讨论此“累加价钱Oracle”其任务方式,如何运用它,并引见一个Solidity库,可以用来将这个Oracle集成到您本人的
假如您曾经理解本文次要的想法,你可以在这里找到代码示例和solidity库:https://github.com/Keydonix/uniswap-oracle。
虽然我们通常以为oracle
为了阐明Uniswap V2经过这一新的Oracle功用处理的成绩,让我们首先讨论Uniswap V1的成绩。
不要将Uniswap V1用作Oracle
Uniswap团队从未将Uniswap V1推行为可行的链上Oracle。正是由于Uniswap复杂、无答应、链上、面向市场的功用,才吸引了有发明力的人将其作为一个全体来运用。Uniswap V1 oracle的警报器很复杂:
uint256 tokenPrice = token.balanceOf(uniswapMarket) /
address(uniswapMarket).balance
由于目前Uniswap V1市场的“价钱”只是
Uniswap V1的成绩在于价钱反应是即时的,并且很容易在很短的
// 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,需求“
检索每个值的以后值都很复杂(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代码。
原创文章,作者:区块腾,如若转载,请注明出处:http://www.qukuaiteng.com/3531840.html 《Uniswap V2 Oracle和链上累计价格存储证明的结合使用》