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
合约中,流动性管理的函数名是:
而在Pool
合约中,管理流动性的函数名是:
这些函数名看起来清晰明了。但在 V4 中,为了让流动性操作有足够的灵活性,流动性管理的接口被重新设计。在PositionManager
中,所有的操作被集成到modifyLiquidities
函数中。在PoolManager
中,操作也被集中到了modifyLiquidity
函数。
注意:
PoolManager
也有mint
和burn
函数,但是他们是用来管理 ERC6909 余额的,和 V3 中mint
与burn
的作用不同
以PositionManager
为例,从modifyLiquidities
这个名字可以看出,它是支持一次性多个操作的。事实也正是如此。它的设计思路有点像线性脚本,你可以设置多个动作,并设置参数,然后送到这个函数里一股脑执行。这里以创建流动性仓位举例,
第一步:首先创建一个PositionManager
实例。
import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";
IPositionManager posm = IPositionManager(<address>);
第二步:设置动作
要创建一个新的流动性仓位,需要设置两个动作:
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
需要如下参数
poolKey | PoolKey | |
tickLower | int24 | |
tickUpper | int24 | |
liquidity | uint256 | |
amount0Max | uint128 | |
amount1Max | uint128 | |
recipient | address | |
hookData | bytes |
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
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。