最近在学习UniswapV2,今天尝试将UniswapV2的源码在本地编译和测试,过程中遇到了一个关于pair地址的问题,在此记录一下发现原因并解决的全过程。
最近在学习 Uniswap V2,今天尝试将 Uniswap V2 的源码在本地编译和测试,过程中遇到了一个关于 pair 地址的问题,在此记录一下发现原因并解决的全过程。
由于 Uniswap V2 是 4 年前发布的,当时的 solidity 版本较低,和现在主流版本有较大差异,为了在本地使用 0.8 以上的版本,需要对 Uniswap V2 的源码不兼容的地方进行一系列修改。修改完成并 build 通过后,就可以开始测试了。但是在测试的过程中,发现每个测试用例都报错,仔细排查后,发现是 UniswapV2Factory.sol 中getPair方法获取的 pair 地址和 UniswapV2Library.sol 中的pairFor 方法计算的 pair 地址不一致导致的。按理说,同一个 factory 创建的关于同一对 token 的 pair,地址肯定是一样的。那问题出在哪呢?
如下图所示,UniswapV2Factory.sol 中有个createPair方法,它通过 create2方法创建了关于 token0 和 token1 的 pair 合约,然后将该合约地址保存到了名为 getPair 的 mapping 中。我们可以通过factory.getPair(token0Address, token1Address) 获取到 pair 地址:

而 UniswapV2Library.sol 中通过pairFor 方法获取的 pair 地址,是纯计算得来的。其利用了 create2的特性:新地址 = hash(0xff ++ senderAddress ++ salt ++ hash(bytecode))。只要知道创建者地址(即 factory)、salt(即 token0 和 token1 的 hash)、bytecode(即 UniswapV2Pair 的 字节码),就能计算得 token0 和 token1 的 pair 地址:

问题就出在 bytecode 上。Uniswap V2 在写 pairFor 方法时,为了节约 gas,直接给出了 bytecode 的 hash 值,即上图中的 hex'96e8ac...' 。这串 hash 的计算方式为:
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 hash = keccak256(abi.encodePacked(bytecode));
但是,由于 solidity 版本、源代码、优化次数等会有差异,我们的本地代码和主网上 Uniswap V2 的代码大概率是不完全一样的,这就导致计算的 hash 值会不一样。而 pairFor 里用的是 Uniswap V2 写死的 hash 值,计算出来的地址肯定就和我们真实创建的地址不一样了。
我们需要自己计算出我们本地 UniswapV2Pair 代码的 hash 值,然后对 pairFor 方法中的那串 hash 进行替换。

如上图所示,在UniswapV2Factory.sol加上这两句,运行后在控制台拿到打印的 hash 结果,替换掉 UniswapV2Library.sol 的 pairFor 函数里的那一串 hash 值(注意不要带0x),这样,pairFor 计算的 pair 地址就和真实创建的地址一致了。
很多优秀的链上项目,在其代码优化过程中,可能会使用一些“黑科技”,以达到提高运行效率、节约 gas 成本等效果。在我们学习源码的过程中,需要特别注意这些优化之处,一方面是学习他们的优化技巧和背后的思想,另一方面也要小心不要“踩坑”。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!