Uniswap / v3-core

🦄 🦄 🦄 Core smart contracts of Uniswap v3
https://uniswap.org
Other
4.38k stars 2.68k forks source link

On the relationship between the pool and the NFT position manager #636

Closed 09tangriro closed 1 year ago

09tangriro commented 1 year ago

Hi! I am trying to understand deeper the relationship between the UniswapV3Pool.sol contract in this repo and the NonfungiblePositionManager.sol contract in the v3-periphery repo. As a summary, my question is: How does the pool contract know which position to burn if the message sender owns more than 1 position in a single tick range?

Minting Liquidity

I mint liquidity via the NFT manager since it implements a uniswapV3MintCallback. The NFT contract calls the addLiquidity method with parameters

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
})

In particular, note how the recipient is defined as the NFT manager contract. This maps up to the mint on the pool contract:

function mint(
      address recipient,
      int24 tickLower,
      int24 tickUpper,
      uint128 amount,
      bytes calldata data
  ) external override lock returns (uint256 amount0, uint256 amount1)

which then goes on to modify a position of the NFT contract

_modifyPosition(
    ModifyPositionParams({
        owner: recipient,
        tickLower: tickLower,
        tickUpper: tickUpper,
        liquidityDelta: int256(amount).toInt128()
    })
);

Burning Liquidity

I can burn liquidity directly on the pool contract without going through the NFT contract. I just need to supply the tick range and the amount of liquidity I'd like to burn:

function burn(
    int24 tickLower,
    int24 tickUpper,
    uint128 amount
) external override lock returns (uint256 amount0, uint256 amount1)

But how does the pool know which position to burn? I believe this comes in the modify position section of the code:

_modifyPosition(
    ModifyPositionParams({
        owner: msg.sender,
        tickLower: tickLower,
        tickUpper: tickUpper,
        liquidityDelta: -int256(amount).toInt128()
    })
);

so the pool burns liquidity of the position in the tick range owned by the owner of the message, so I'd need to send the transaction from the NFT contract address which is the owner of the liquidity minted in the first section. This is reinforced by the NFT contract which calls the following code in the decreaseLiquidity method

(amount0, amount1) = pool.burn(position.tickLower, position.tickUpper, params.liquidity);

This implies that the pool knows the NFT contract has sent the message so knows the NFT contract liquidity should be burned.

Problem

However, the NFT position manager contract has loads of positions, since the majority of NFT positions are owned by the NFT manager contract. The position is obtained by the following line

position = positions.get(owner, tickLower, tickUpper);

But there will be multiple NFTs that have the same tick range, and so this method should have multiple valid responses of positions with the NFT contract owner with that tick range. So how does the pool contract know which particular position to remove liquidity from?

09tangriro commented 1 year ago

I've resolved this myself :)