You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Uniswap V3仓位铸造时computeAddress计算池地址错误排查求助

Uniswap V3 铸造仓位时池地址计算不一致问题排查

在Uniswap V3开发过程中,调用NonfungiblePositionManager.sol的mint函数铸造仓位时,出现PoolAddress.computeAddress计算出的池地址与UniswapV3PoolFactory.getPool返回地址不一致的问题,导致LiquidityManagement.sol的addLiquidity方法中后续调用池合约的操作全部失败。

已完成的排查项:

  • 确认代币传入顺序符合token0 < token1的要求
  • 通过Hardhat本地链日志验证,computeAddress与getPool所用参数完全一致
  • 未修改PoolAddress.sol中的POOL_INIT_CODE_HASH常量
  • 所有合约均使用0.7.6版本编译器编译

相关代码片段

NonfungiblePositionManager.sol

function mint(MintParams calldata params)
    external
    payable
    override
    checkDeadline(params.deadline)
    returns (
      uint256 tokenId,
      uint128 liquidity,
      uint256 amount0,
      uint256 amount1
    )
  {
    IUniswapV3Pool pool;
    
    // 此处调用失败
    (liquidity, amount0, amount1, pool) = addLiquidity(
      AddLiquidityParams({
        token0: params.token0,
        token1: params.token1,
        fee: params.fee,
        recipient: address(this),
        tickLower: params.tickLower,
        tickUpper: params.tickUpper,
        amount0Desired: params.amount0Desired,
        amount1Desired: params.amount1Desired,
        amount0Min: params.amount0Min,
        amount1Min: params.amount1Min
      })
    );
    
    _mint(params.recipient, (tokenId = _nextId++));

    bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
    (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
    
    // 幂等设置池ID
    uint80 poolId = cachePoolKey(
      address(pool),
      PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee})
    );
    
    _positions[tokenId] = Position({
      nonce: 0,
      operator: address(0),
      poolId: poolId,
      tickLower: params.tickLower,
      tickUpper: params.tickUpper,
      liquidity: liquidity,
      feeGrowthInside0LastX128: feeGrowthInside0LastX128,
      feeGrowthInside1LastX128: feeGrowthInside1LastX128,
      tokensOwed0: 0,
      tokensOwed1: 0
    });
    
    emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
  }

LiquidityManagement.sol

function addLiquidity(AddLiquidityParams memory params)
    internal
    returns (
      uint128 liquidity,
      uint256 amount0,
      uint256 amount1,
      IUniswapV3Pool pool
    )
  {
    PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({
      token0: params.token0,
      token1: params.token1,
      fee: params.fee
    });
    
    // 池地址计算逻辑
    pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

    // 后续操作全部失败
    {
      (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
      console.log("liquidity 4");
      uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
      console.log("liquidity 5");
      uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper);
      console.log("liquidity 6");

      liquidity = LiquidityAmounts.getLiquidityForAmounts(
        sqrtPriceX96,
        sqrtRatioAX96,
        sqrtRatioBX96,
        params.amount0Desired,
        params.amount1Desired
      );
      console.log("liquidity 7");
    }

    (amount0, amount1) = pool.mint(
      params.recipient,
      params.tickLower,
      params.tickUpper,
      liquidity,
      abi.encode(MintCallbackData({poolKey: poolKey, payer: msg.sender}))
    );
    
    require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, "Price slippage check");
  }

PoolAddress.sol

bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;


  function computeAddress(address factory, PoolKey memory key) internal view returns (address pool) {
    require(key.token0 < key.token1);
    pool = address(
      // uint160(
      uint256(
        keccak256(
          abi.encodePacked(
            hex"ff",
            factory,
            keccak256(abi.encode(key.token0, key.token1, key.fee)),
            POOL_INIT_CODE_HASH
          )
        )
      )
      // )
    );
  }

问题排查与解决方向

  1. 修复computeAddress的地址转换逻辑
    你提供的PoolAddress.sol中,注释掉了uint160()的转换步骤,这是核心错误。以太坊地址是160位的,官方实现中必须将keccak256计算出的256位哈希转换为uint160后再转为地址,否则生成的地址与Factory合约createPool方法生成的地址会不一致。

    修复后的代码:

    function computeAddress(address factory, PoolKey memory key) internal view returns (address pool) {
        require(key.token0 < key.token1);
        pool = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(abi.encode(key.token0, key.token1, key.fee)),
                            POOL_INIT_CODE_HASH
                        )
                    )
                )
            )
        );
    }
    
  2. 验证Factory合约的一致性
    确认部署的UniswapV3PoolFactory是官方未修改的版本,其内部使用的POOL_INIT_CODE_HASH必须与PoolAddress.sol中的常量完全匹配。如果Factory合约被修改过,会导致地址生成逻辑不一致。

  3. 检查Pool合约的字节码哈希
    如果自定义了Pool合约(哪怕是微小修改),其部署字节码的哈希会变化,需要重新计算并更新PoolAddress.sol中的POOL_INIT_CODE_HASH常量。

内容的提问来源于stack exchange,提问作者Володимир Севастьянов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 12:43:29