Uniswap V3:流动性提取和收集

作者:WongSSH

 引言

本系列文章将带领读者从零实现Uniswap V3核心功能,深入解析其设计与实现。 主要参考了 Constructor | Uniswap V3 Core Contract Explained 系列教程、 Uniswap V3 Development Book 和 Paco 博客中的相关内容。所有示例代码可在 clamm 代码库中找到,以便实践和探索。

流动性提取和收集

进行流动性的提取实际上就是 mint 函数的反向操作,我们可以直接使用  _modifyPosition 函数。此处,我们会使用到  Position.Info 内的 tokensOwed0 和 tokensOwed1 变量,该变量用于记录 Position 内部不作为流动性的代币。该函数实现如下:

function burn(int24 tickLower, int24 tickUpper, uint128 amount)
    external
    lock
    returns (uint256 amount0, uint256 amount1)
{
    (Position.Info storage position, int256 amount0Int, int256 amount1Int) = _modifyPosition(
        ModifyPositionParams({
            owner: msg.sender,
            tickLower: tickLower,
            tickUpper: tickUpper,
            liquidityDelta: -int256(uint256(amount)).toInt128()
        })
    );

    amount0 = uint256(-amount0Int);
    amount1 = uint256(-amount1Int);

    if (amount0 > 0 || amount1 > 0) {
        (position.tokensOwed0, position.tokensOwed1) =
            (position.tokensOwed0 + uint128(amount0), position.tokensOwed1 + uint128(amount1));
    }
}

由于用户需要提取流动性,所以此处的 liquidityDelta 为 amount 的负数。在 burn 函数的最后,我们将输出的 amount0 和 amount1 添加到用户的 position内部。

在上述流程内,我们并没有完成代币的提取。我们可以使用 collect函数实现代币的提取,代码如下:

function collect(
    address recipient,
    int24 tickLower,
    int24 tickUpper,
    uint128 amount0Requested,
    uint128 amount1Requested
) external lock returns (uint128 amount0, uint128 amount1) {
    Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);

    amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
    amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;

    if (amount0 > 0) {
        position.tokensOwed0 -= amount0;
        IERC20(token0).transfer(recipient, amount0);
    }
    if (amount1 > 0) {
        position.tokensOwed1 -= amount1;
        IERC20(token1).transfer(recipient, amount1);
    }
}

此处可以预告一下,在每次调用 _modifyPosition时都会完成代表价格区间的 position 更新。在这部分更新过程中,我们会重新计算区间获得手续费情况,这部分手续费就会被记录在 position.tokensOwed0 和 position.tokensOwed1内部。