探索 Uniswap V4: 流动性管理
2024-12-30 12:15
Antalpha Labs
2024-12-30 12:15
订阅此专栏
收藏此文章

AMM 的核心是流动性管理。尽管 AMM 并不是 Uniswap 第一个提出的,Uniswap 也不是第一个链上 AMM 协议,但 Uniswap 是第一个将 AMM 推向主流的项目。在 V3 引入集中流动性之后,资金利用率的痛点得以解决,这让 AMM 的模式趋于成熟。

在有了成熟的模型之后,V4 将重点放在了对流动性的优化上,这主要表现在以下几个方面:

一、手续费再投资

在 V3 中,手续费会一直在流动性头寸中单独累计,除非进行收集操作,才会将手续费转入到 LP 的账户中。因此,如果想将手续费再投资,需要先收集手续费,再添加流动性;或者干脆移除旧的流动性并重新投入。

在 V4 中,这一点得到了改进,在用户添加和移除流动性时,可以选择手续费收入的处理方式:添加流动性时,可以将手续费收入转换为头寸中的流动性;在移除流动性时,则会自动要求提取未领取的手续费收益。

二、流动性标记

在 V3 中,从Pool的角度看,对于每一个 Position(头寸),Pool都记录了持有人,上下界。如果用户通过NonfungiblePositionManager添加流动性,在Pool中,会将持有人记录为NonfungiblePositionManager的地址,而且NonfungiblePositionManager会管理好每个流动性头寸。通常,用户会与NonfungiblePositionManager交互,这样没有问题。但是对于高级用户来说,他们通常会直接与Pool合约交互,这就引入了一个问题,如果同一个用户投资了多个头寸,而且这些头寸的上下界相同,他们会被流动性池看作同一个流动性头寸。对于个人 LP 这不是问题,但是对于基金类型的 LP 就很不方便,因为他们需要像NonfungiblePositionManager合约一样,自己记录并分配每个用户的投资和手续费。

V4 作出了一些改进,创建流动性时,可以提供一个额外的参数 salt,这使得用户在直接通过Pool合约添加流动性时,拥有一个额外的维度区分每个流动性。因此基金 LP 能够通过 salt 清楚的区分每个用户的头寸,不用计算每个用户到底需要分多少手续费。

三、快速记账

快速记账是 V4 中另一个强大的功能,可以在不发生实际转账的情况下,完成多跳交易。添加流动性的过程也可以从快速记账中受益。例如,用户希望为ETH <> DAI 添加流动性,但手头没有DAI。用户可以先将一部分ETH 兑换为DAI,以便使用两种代币添加流动性。此外,用户还可以通过多跳交换(multi-hop swap)实现,例如从 ETH 兑换为USDC 再兑换为DAI。如果集成得当,用户只需要一次性转移ETH 即可完成整个操作。

在添加了如此多的改进之后,流动性管理的接口也发生了变化。在 V3 中,流动性管理的接口非常清晰,每一个操作都有对应的操作。比如在NonfungiblePositionManager合约中,流动性管理的函数名是:

  • mint
  • increaseLiquidity
  • decreaseLiquidity
  • collect
  • burn

而在Pool合约中,管理流动性的函数名是:

  • mint
  • collect
  • burn

这些函数名看起来清晰明了。但在 V4 中,为了让流动性操作有足够的灵活性,流动性管理的接口被重新设计。在PositionManager中,所有的操作被集成到modifyLiquidities函数中。在PoolManager中,操作也被集中到了modifyLiquidity函数。

注意:PoolManager也有mintburn函数,但是他们是用来管理 ERC6909 余额的,和 V3 中mintburn的作用不同

PositionManager为例,从modifyLiquidities这个名字可以看出,它是支持一次性多个操作的。事实也正是如此。它的设计思路有点像线性脚本,你可以设置多个动作,并设置参数,然后送到这个函数里一股脑执行。这里以创建流动性仓位举例,

第一步:首先创建一个PositionManager实例。

import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";

IPositionManager posm = IPositionManager(<address>);

第二步:设置动作

要创建一个新的流动性仓位,需要设置两个动作:

  • mint: 用来创建流动性仓位
  • 交易对: 设置添加哪两个 token
import {Actions} from "v4-periphery/src/libraries/Actions.sol";

bytes memory actions = abi.encodePacked(Actions.MINT_POSITION, Actions.SETTLE_PAIR);

第三步:设置参数,这两个参数对应上面的两个动作

bytes[] memory params = new bytes[](2);

其中MINT_POSITION需要如下参数

参数
类型
描述
poolKeyPoolKey
池的 ID
tickLowerint24
Position 的 lower tick
tickUpperint24
Position 的 upper tick
liquidityuint256
流动性的数量
amount0Maxuint128
msg.sender 可支付 token0 的最大数量
amount1Maxuint128
msg.sender 可支付 token1 的最大数量
recipientaddress
接收流动性 NFT(ERC-721) 的地址
hookDatabytes
hook 所需参数,这些参数将被直接转发给 Hook
Currency currency0 = Currency.wrap(<tokenAddress1>); // 填写 0 地址表示使用 ETH
Currency currency1 = Currency.wrap(<tokenAddress2>);
PoolKey poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(hook));

params[0] = abi.encode(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);

SETTLE_PAIR 动作需要两个参数:

  • currency0 - msg.sender 将支付的 token 0 的地址
  • currency1 - msg.sender 将支付的 token 1 的地址
params[1] = abi.encode(currency0, currency1);

第四步:提交,这里还可以设置一个过期时间。

uint256 deadline = block.timestamp + 60;

uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;

posm.modifyLiquidities{value: valueToPass}(
    abi.encode(actions, params),
    deadline
);

其它的操作(增加、移除流动性,收集手续费等)也与之类似。

在流动性的操作上,V4 的改进并不大。这些改进更像是为了适应其它新功能而进行的升级。但即便如此,我们可以发现在 V4 的流动性管理上,功能更强,操作更方便。这些细致入微的改进,为流动性管理带来了更强的可操作性,同时为未来更复杂的 DeFi 场景奠定了基础。




笔者:Steven Sun,Zelos




Antalpha Labs 是一个非盈利的 Web3 开发者社区,致力于通过发起和支持开源软件推动 Web3 技术的创新和应用。

官网:https://labs.antalpha.com

Twitter:https://twitter.com/Antalpha_Labs

Youtube:https://www.youtube.com/channel/UCNFowsoGM9OI2NcEP2EFgrw

联系我们:hello.labs@antalpha.com

点击左下方「阅读原文」/「Read More」,获取更多相关信息

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

Antalpha Labs
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开