本文copy自https://liaoph.com/uniswap-v3-4/
以普通用戶(hù)的視角來(lái)看,對(duì)比 Uniswap v2厉膀,Uniswap v3 在手續(xù)費(fèi)方面做了如下改動(dòng):
添加流動(dòng)性時(shí)豹芯,手續(xù)費(fèi)可以有 3個(gè)級(jí)別供選擇:0.05%, 0.3% 和 1%付魔,未來(lái)可以通過(guò)治理加入更多可選的手續(xù)費(fèi)率
Uniswap v2 中手續(xù)費(fèi)會(huì)在收取后自動(dòng)復(fù)投稱(chēng)為 LP 的一部分,即每次手續(xù)費(fèi)都自動(dòng)變成流動(dòng)性加入池子中孙蒙,而 Uniswap v3 中收取的手續(xù)費(fèi)不會(huì)自動(dòng)復(fù)投(主要是為了方便合約的計(jì)算),需要手動(dòng)取出手續(xù)費(fèi)
不同手續(xù)費(fèi)級(jí)別悲雳,在添加流動(dòng)性時(shí)挎峦,價(jià)格可選值的最小粒度也不一樣(這個(gè)是因?yàn)?tick spacing 的影響),一般來(lái)說(shuō)合瓢,手續(xù)費(fèi)越低坦胶,價(jià)格可選值越精細(xì),因此官方推薦價(jià)格波動(dòng)小的交易對(duì)使用低費(fèi)率(例如穩(wěn)定幣交易對(duì))
以開(kāi)發(fā)者的視角來(lái)看晴楔,Uniswap v3 的手續(xù)費(fèi)計(jì)算相對(duì)會(huì)比較復(fù)雜顿苇, 因?yàn)樗枰槍?duì)每一個(gè) position 來(lái)進(jìn)行單獨(dú)的計(jì)算,為了方便計(jì)算税弃,在代碼中會(huì)將手續(xù)費(fèi)相關(guān)的元數(shù)據(jù)記錄在 position 的邊界 tick 上(這些 tick 上還存儲(chǔ)了 ΔL 等元數(shù)據(jù))纪岁。
手續(xù)費(fèi)的計(jì)算和存儲(chǔ)
在之前的文章中說(shuō)過(guò),一個(gè)交易對(duì)池的流動(dòng)性则果,是由不同的流動(dòng)性組合而成幔翰,每一個(gè)流動(dòng)性的提供者都可以設(shè)置獨(dú)立的價(jià)格范圍區(qū)間,這個(gè)被稱(chēng)為 positon. 當(dāng)我們計(jì)算交易的手續(xù)費(fèi)時(shí)西壮,我們需要計(jì)算如下值:
每一個(gè) position 收取的手續(xù)費(fèi)(token0, token1 需要分別單獨(dú)計(jì)算)
用戶(hù)如果提取了手續(xù)費(fèi)遗增,需要記錄用戶(hù)已提取的數(shù)值
v3 中有以下幾個(gè)關(guān)于手續(xù)費(fèi)的變量:
交易池中手續(xù)費(fèi)的費(fèi)率值,這里記錄的值時(shí)以 1000000 為基數(shù)的值茸时,例如當(dāng)手續(xù)費(fèi)為 0.03% 時(shí)贡定,費(fèi)率值為 300
全局狀態(tài)變量 feeGrowthGlobal0X128 和 feeGrowthGlobal1X128 ,分別表示 token0 和 token1 所累計(jì)的手續(xù)費(fèi)總額可都,使用了 Q128.128 浮點(diǎn)數(shù)來(lái)記錄
對(duì)于每個(gè) tick缓待,記錄了 feeGrowthOutside0X128 和 feeGrowthOutside1X128,這兩個(gè)變量記錄了發(fā)生在此 tick 「外側(cè)」的手續(xù)費(fèi)總額渠牲,那么什么「外側(cè)」呢旋炒,后文會(huì)詳細(xì)說(shuō)明
對(duì)于每個(gè) position,記錄了此 position 內(nèi)的手續(xù)費(fèi)總額 feeGrowthInside0LastX128 和 feeGrowthInside1LastX128签杈,這個(gè)值不需要每次都更新瘫镇,它只會(huì)在 position 發(fā)生變動(dòng),或者用戶(hù)提取手續(xù)費(fèi)時(shí)更新
需要注意的時(shí)答姥,上面這些手續(xù)費(fèi)狀態(tài)變量都是每一份 LP 所對(duì)應(yīng)的手續(xù)費(fèi)铣除,在計(jì)算真正的手續(xù)費(fèi)時(shí),需要使用 LP 數(shù)相乘來(lái)得出實(shí)際手續(xù)費(fèi)數(shù)額鹦付,又因?yàn)?LP 數(shù)在不同價(jià)格可能時(shí)不同的(因?yàn)榱鲃?dòng)性深度不同)尚粘,所以在計(jì)算手續(xù)費(fèi)時(shí)只能針對(duì) position 進(jìn)行計(jì)算(同一個(gè) position 內(nèi) LP 總量不變)。
計(jì)算過(guò)程
我們用 fg 表示代幣池收取的手續(xù)費(fèi)總額敲长,對(duì)于一個(gè) tick郎嫁,其索引為 i秉继,使用 fo(i) 表示此 tick 「外側(cè)」的手續(xù)費(fèi)總額,使用 fb(i) 表示低于此 tick 價(jià)格發(fā)生的交易的手續(xù)費(fèi)總額泽铛,使用 fa(i) 表示高于此 tick 價(jià)格發(fā)生的交易的手續(xù)費(fèi)總額尚辑。
流動(dòng)性?xún)r(jià)格區(qū)間中的手續(xù)費(fèi)計(jì)算
fo 的更新
我們知道了某一個(gè) tick 的 fo(i) 的值,與當(dāng)前 tick ic 和 i 之間的位置關(guān)系有關(guān)(大于或者小于)盔腔,在發(fā)生交易時(shí)杠茬,當(dāng)前價(jià)格的 ic 是會(huì)不斷變化的。因此弛随,當(dāng) ic 和 i 的位置關(guān)系發(fā)生了變化時(shí)澈蝙,我們需要更新 fo(i) 的值。
具體來(lái)說(shuō)撵幽,當(dāng)前價(jià)格穿過(guò)某一個(gè) tick 時(shí),需要更新此 tick 上的 fo(i)礁击,更新的方式時(shí)將其值修改為另一側(cè)的手續(xù)費(fèi)總和盐杂,即:
fo(i):=fg?fo(i)
core 倉(cāng)庫(kù)代碼分析
手續(xù)費(fèi)計(jì)算的代碼的計(jì)算主要出現(xiàn)在以下行為相關(guān)的代碼中:
提供流動(dòng)性時(shí),需要初始化 tick 對(duì)應(yīng)的 fo 值
發(fā)生交易時(shí)哆窿,需要更新 fg
當(dāng)交易過(guò)程中链烈,當(dāng)前價(jià)格穿過(guò)某一個(gè) tick 時(shí),需要更新此 tick 上的 fo 值
當(dāng)流動(dòng)性發(fā)生變動(dòng)時(shí)挚躯,更新此 position 中手續(xù)費(fèi)的總和
提供流動(dòng)性
在添加流動(dòng)性時(shí)强衡,我們會(huì)初始化或更新此 position 對(duì)應(yīng)的 lower/upper tick,在 Tick.update 函數(shù)中:
function update(
mapping(int24 => Tick.Info) storage self,
int24 tick,
int24 tickCurrent,
int128 liquidityDelta,
uint256 feeGrowthGlobal0X128,
uint256 feeGrowthGlobal1X128,
bool upper,
uint128 maxLiquidity
) internal returns (bool flipped) {
Tick.Info storage info = self[tick];
// 獲取此 tick 更新之前的流動(dòng)性
uint128 liquidityGrossBefore = info.liquidityGross;
uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta);
...
// 如果 tick 在更新之前的 liquidityGross 為 0码荔,那么表示我們本次為初始化操作
// 這里會(huì)初始化 tick 中的 f_o
if (liquidityGrossBefore == 0) {
// by convention, we assume that all growth before a tick was initialized happened _below_ the tick
if (tick <= tickCurrent) {
info.feeGrowthOutside0X128 = feeGrowthGlobal0X128;
info.feeGrowthOutside1X128 = feeGrowthGlobal1X128;
}
}
...
info.liquidityGross = liquidityGrossAfter;
...
}
交易過(guò)程中的手續(xù)費(fèi)
[上一篇]文章中講過(guò)整個(gè)交易過(guò)程時(shí)分步進(jìn)行的漩勤,每一步都在一個(gè)相同的流動(dòng)性區(qū)間,那么手續(xù)費(fèi)的計(jì)算也需要在每一步的交易中計(jì)算出這一步的手續(xù)費(fèi)總數(shù)缩搅。在交易步驟的結(jié)構(gòu)體中有定義:
struct StepComputations {
...
// 當(dāng)前交易步驟的手續(xù)費(fèi)
uint256 feeAmount;
}
只計(jì)算一個(gè)值時(shí)因?yàn)樵桨埽掷m(xù)費(fèi)只會(huì)在輸入的 token 中收取,而不會(huì)在輸出的 token 中重復(fù)收取硼瓣。
計(jì)算過(guò)程在 SwapMath.computeSwapStep 中:
function computeSwapStep(
...
)
internal
pure
returns (
...
uint256 feeAmount
)
{
...
if (exactIn) {
// 在交易之前究飞,先計(jì)算當(dāng)價(jià)格移動(dòng)到交易區(qū)間邊界時(shí),所需要的手續(xù)費(fèi)
// 即此步驟最多需要的手續(xù)費(fèi)數(shù)額
uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6);
...
} else {
...
}
...
// 根據(jù)交易是否移動(dòng)到價(jià)格邊界來(lái)計(jì)算手續(xù)費(fèi)的數(shù)額
if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) {
// 當(dāng)沒(méi)有移動(dòng)到價(jià)格邊界時(shí)(即余額不足以讓價(jià)格移動(dòng)到邊界)堂鲤,直接把余額中剩余的資金全部作為手續(xù)費(fèi)
feeAmount = uint256(amountRemaining) - amountIn;
} else {
// 當(dāng)價(jià)格移動(dòng)到邊界時(shí)亿傅,計(jì)算相應(yīng)的手續(xù)費(fèi)
feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips);
}
}
// 交易步驟的循環(huán)
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
// 計(jì)算這一步的手續(xù)費(fèi)總額
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
...
);
// 更新交易的 f_g,這里需要除以流動(dòng)性 L
if (state.liquidity > 0)
state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity);
...
}
...
// 在交易步驟完成后瘟栖,更新合約的 f_g
if (zeroForOne) {
feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee;
} else {
feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee;
}
...
更新時(shí)使用此步驟的手續(xù)費(fèi)總額除以此步驟的流動(dòng)性 L 狮荔,以得出每一份流動(dòng)性所對(duì)應(yīng)的手續(xù)費(fèi)數(shù)值。
當(dāng) tick 被穿過(guò)時(shí)
前面說(shuō)過(guò)汉操,當(dāng) tick 被穿過(guò)時(shí),需要更新這個(gè) tick 對(duì)應(yīng)的 fo奔穿,這部分操作也是在 UniswapV3Pool.swap 中:
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
...
// 當(dāng)價(jià)格到達(dá)當(dāng)前步驟價(jià)格區(qū)間的邊界時(shí),可能需要穿過(guò)下一個(gè) tick
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
// 查看下一個(gè) tick 是否初始化
if (step.initialized) {
int128 liquidityNet =
// 在這里需要更新 tick 的 f_o
ticks.cross(
step.tickNext,
(zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128),
(zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128)
);
// if we're moving leftward, we interpret liquidityNet as the opposite sign
// safe because liquidityNet cannot be type(int128).min
...
}
...
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
...
}
...
}
這里通過(guò) ticks.cross 來(lái)更新被穿過(guò)的 tick:
function cross(
mapping(int24 => Tick.Info) storage self,
int24 tick,
uint256 feeGrowthGlobal0X128,
uint256 feeGrowthGlobal1X128
) internal returns (int128 liquidityNet) {
Tick.Info storage info = self[tick];
info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128;
info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128;
liquidityNet = info.liquidityNet;
}
position 維度
position 由 lower tick 和 upper tick 兩個(gè) tick 組成敏晤,當(dāng) positino 更新時(shí)贱田,就可以更新從上次更新以來(lái)此 position 中累積的手續(xù)費(fèi)數(shù)額。只在 position 的流動(dòng)性更新時(shí)才更新 position 中的手續(xù)費(fèi)可以讓交易過(guò)程不用更新過(guò)多的變量嘴脾,節(jié)省交易所消耗的 gas 費(fèi)用男摧。在 UniswapV3Pool._updatePosition 中:
function _updatePosition(
address owner,
int24 tickLower,
int24 tickUpper,
int128 liquidityDelta,
int24 tick
) private returns (Position.Info storage position) {
...
// 計(jì)算出此 position 中的手續(xù)費(fèi)總額
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128);
// 更新 position 中記錄的值
position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);
...
}
通過(guò) ilower, iupper, icurrent 和 fg,調(diào)用 ticks.getFeeGrowthInside 可以計(jì)算出 position 中的手續(xù)費(fèi)總額译打,代碼為:
function getFeeGrowthInside(
mapping(int24 => Tick.Info) storage self,
int24 tickLower,
int24 tickUpper,
int24 tickCurrent,
uint256 feeGrowthGlobal0X128,
uint256 feeGrowthGlobal1X128
) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) {
Info storage lower = self[tickLower];
Info storage upper = self[tickUpper];
// 計(jì)算 f_b(i)
uint256 feeGrowthBelow0X128;
uint256 feeGrowthBelow1X128;
if (tickCurrent >= tickLower) {
feeGrowthBelow0X128 = lower.feeGrowthOutside0X128;
feeGrowthBelow1X128 = lower.feeGrowthOutside1X128;
} else {
feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128;
feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128;
}
// 計(jì)算 f_a(i)
uint256 feeGrowthAbove0X128;
uint256 feeGrowthAbove1X128;
if (tickCurrent < tickUpper) {
feeGrowthAbove0X128 = upper.feeGrowthOutside0X128;
feeGrowthAbove1X128 = upper.feeGrowthOutside1X128;
} else {
feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128;
feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128;
}
feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128;
feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128;
}
這部分代碼使用前面說(shuō)過(guò)的[公式耗拓,這里不再詳述。
在 Position.update
函數(shù)中:
function update(
Info storage self,
int128 liquidityDelta,
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) internal
...
// 計(jì)算 token0 和 token1 的手續(xù)費(fèi)總數(shù)
uint128 tokensOwed0 =
uint128(
FullMath.mulDiv(
feeGrowthInside0X128 - _self.feeGrowthInside0LastX128,
_self.liquidity,
FixedPoint128.Q128
)
);
uint128 tokensOwed1 =
uint128(
FullMath.mulDiv(
feeGrowthInside1X128 - _self.feeGrowthInside1LastX128,
_self.liquidity,
FixedPoint128.Q128
)
);
// update the position
if (liquidityDelta != 0) self.liquidity = liquidityNext;
self.feeGrowthInside0LastX128 = feeGrowthInside0X128;
self.feeGrowthInside1LastX128 = feeGrowthInside1X128;
if (tokensOwed0 > 0 || tokensOwed1 > 0) {
// overflow is acceptable, have to withdraw before you hit type(uint128).max fees
self.tokensOwed0 += tokensOwed0;
self.tokensOwed1 += tokensOwed1;
}
}
這里計(jì)算了此 position 自上次更新以來(lái) token0 和 token1 的手續(xù)費(fèi)總數(shù)奏司,計(jì)算時(shí)使用的 feeGrowthInside0X128 的含義時(shí)每一份流動(dòng)性所對(duì)應(yīng)的手續(xù)費(fèi)份額乔询,因此在計(jì)算總額時(shí)需要使用此值乘以 position 的流動(dòng)性總數(shù)。最后將這些手續(xù)費(fèi)總數(shù)更新到 tokensOwed0 和 tokensOwed0 字段中韵洋。
手續(xù)費(fèi)的提取
手續(xù)費(fèi)的提取也是以 position 為單位進(jìn)行提取的竿刁。使用 UniswapV3Pool.collect 提取手續(xù)費(fèi):
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external override lock returns (uint128 amount0, uint128 amount1) {
// 獲取 position 數(shù)據(jù)
Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);
// 根據(jù)參數(shù)調(diào)整需要提取的手續(xù)費(fèi)
amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;
// 將手續(xù)費(fèi)發(fā)送給用戶(hù)
if (amount0 > 0) {
position.tokensOwed0 -= amount0;
TransferHelper.safeTransfer(token0, recipient, amount0);
}
if (amount1 > 0) {
position.tokensOwed1 -= amount1;
TransferHelper.safeTransfer(token1, recipient, amount1);
}
emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
}
這個(gè)函數(shù)比較簡(jiǎn)單,即根據(jù) position 中已經(jīng)記錄的手續(xù)費(fèi)和用戶(hù)請(qǐng)求的數(shù)額搪缨,發(fā)送指定數(shù)額的手續(xù)費(fèi)給用戶(hù)食拜。
但是這里 posiiton 中的手續(xù)費(fèi)可能并不是最新的(上面說(shuō)過(guò)手續(xù)費(fèi)總數(shù)只會(huì)在 position 的流動(dòng)性更新時(shí)更新)。因此在提取手續(xù)費(fèi)前副编,需要主動(dòng)觸發(fā)一次手續(xù)費(fèi)的更新负甸,這些操作已經(jīng)在 uniswap-v3-periphery 倉(cāng)庫(kù)中進(jìn)行了封裝。
peirphery 倉(cāng)庫(kù)代碼分析
流動(dòng)性對(duì)應(yīng)手續(xù)費(fèi)的更新
NonfungiblePositionManager 中保存了用戶(hù)提供的流動(dòng)性痹届,并使用 NFT token 將這個(gè)流動(dòng)性代幣化呻待。在更新流動(dòng)性時(shí),也會(huì)更新其累積的手續(xù)費(fèi)數(shù)額队腐,例如增加流動(dòng)性時(shí):
function increaseLiquidity(
uint256 tokenId,
uint128 amount,
uint256 amount0Max,
uint256 amount1Max,
uint256 deadline
) external payable override checkDeadline(deadline) returns (uint256 amount0, uint256 amount1) {
...
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
// 更新 token0 和 tokne1 累積的手續(xù)費(fèi)
position.tokensOwed0 += uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.tokensOwed1 += uint128(
FullMath.mulDiv(
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
position.liquidity += amount;
}
因此一個(gè)流動(dòng)性對(duì)應(yīng) NFT token 的手續(xù)費(fèi)也會(huì)在流動(dòng)性變化時(shí)更新带污。
提取手續(xù)費(fèi)
提取手續(xù)費(fèi)使用 NonfungiblePositionManager.collet:
function collect(
uint256 tokenId,
address recipient,
uint128 amount0Max,
uint128 amount1Max
) external payable override isAuthorizedForToken(tokenId) returns (uint256 amount0, uint256 amount1) {
require(amount0Max > 0 || amount1Max > 0);
// 查詢(xún) postion 信息
Position storage position = _positions[tokenId];
PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
(uint128 tokensOwed0, uint128 tokensOwed1) = (position.tokensOwed0, position.tokensOwed1);
// 這里會(huì)再次更新一次手續(xù)費(fèi)累計(jì)總額
if (position.liquidity > 0) {
// 使用 pool.burn() 來(lái)觸發(fā)手續(xù)費(fèi)的更新
pool.burn(position.tickLower, position.tickUpper, 0);
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) =
pool.positions(PositionKey.compute(address(this), position.tickLower, position.tickUpper));
tokensOwed0 += uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
tokensOwed1 += uint128(
FullMath.mulDiv(
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
}
// 提取手續(xù)費(fèi)的最大值,不能超過(guò)手續(xù)費(fèi)總額
(amount0Max, amount1Max) = (
amount0Max > tokensOwed0 ? tokensOwed0 : amount0Max,
amount1Max > tokensOwed1 ? tokensOwed1 : amount1Max
);
// 調(diào)用 pool.collect 將手續(xù)費(fèi)發(fā)送給 recipient
(amount0, amount1) = pool.collect(recipient, position.tickLower, position.tickUpper, amount0Max, amount1Max);
// sometimes there will be a few less wei than expected due to rounding down in core, but we just subtract the full amount expected
// instead of the actual amount so we can burn the token
(position.tokensOwed0, position.tokensOwed1) = (tokensOwed0 - amount0Max, tokensOwed1 - amount1Max);
}
這個(gè)函數(shù)就是先用 pool.burn 函數(shù)來(lái)觸發(fā) pool 中 position 內(nèi)手續(xù)費(fèi)總額的更新香到,使其更新為當(dāng)前的最新值鱼冀。調(diào)用時(shí)傳入?yún)?shù)的 Liquidity 為 0,表示只是用來(lái)觸發(fā)手續(xù)費(fèi)總額的更新悠就,并沒(méi)有進(jìn)行流動(dòng)性的更新千绪。更新完成后,再調(diào)用 pool.collect 提取手續(xù)費(fèi)梗脾。
至此手續(xù)費(fèi)相關(guān)的管理就全部介紹完了荸型。Uniswap v3 還記錄了一個(gè) position 中發(fā)生交易的總時(shí)長(zhǎng),這個(gè)值可以用來(lái)計(jì)算一個(gè) position 處于活躍狀態(tài)的總時(shí)間數(shù)炸茧,用于 position 倉(cāng)位調(diào)整參考瑞妇,這部分計(jì)算因?yàn)楹唾M(fèi)率計(jì)算類(lèi)似稿静,內(nèi)容本文不再贅述,感興趣的讀者可以自行研究辕狰。