diff --git a/.forge-snapshots/decreaseLiquidity_erc20.snap b/.forge-snapshots/decreaseLiquidity_erc20.snap index be10dbf2..1e089f81 100644 --- a/.forge-snapshots/decreaseLiquidity_erc20.snap +++ b/.forge-snapshots/decreaseLiquidity_erc20.snap @@ -1 +1 @@ -114113 \ No newline at end of file +114275 \ No newline at end of file diff --git a/.forge-snapshots/decreaseLiquidity_erc6909.snap b/.forge-snapshots/decreaseLiquidity_erc6909.snap index 510f90cd..4a28d829 100644 --- a/.forge-snapshots/decreaseLiquidity_erc6909.snap +++ b/.forge-snapshots/decreaseLiquidity_erc6909.snap @@ -1 +1 @@ -112380 \ No newline at end of file +112542 \ No newline at end of file diff --git a/.forge-snapshots/increaseLiquidity_erc20.snap b/.forge-snapshots/increaseLiquidity_erc20.snap index ea276824..f8f00e7d 100644 --- a/.forge-snapshots/increaseLiquidity_erc20.snap +++ b/.forge-snapshots/increaseLiquidity_erc20.snap @@ -1 +1 @@ -74115 \ No newline at end of file +74130 \ No newline at end of file diff --git a/.forge-snapshots/increaseLiquidity_erc6909.snap b/.forge-snapshots/increaseLiquidity_erc6909.snap index 78a659ce..d6934799 100644 --- a/.forge-snapshots/increaseLiquidity_erc6909.snap +++ b/.forge-snapshots/increaseLiquidity_erc6909.snap @@ -1 +1 @@ -77907 \ No newline at end of file +77922 \ No newline at end of file diff --git a/.forge-snapshots/mintWithLiquidity.snap b/.forge-snapshots/mintWithLiquidity.snap index 1df963dc..c81b8ef6 100644 --- a/.forge-snapshots/mintWithLiquidity.snap +++ b/.forge-snapshots/mintWithLiquidity.snap @@ -1 +1 @@ -475882 \ No newline at end of file +475868 \ No newline at end of file diff --git a/contracts/NonfungiblePositionManager.sol b/contracts/NonfungiblePositionManager.sol index b8a84a78..f2acdbc1 100644 --- a/contracts/NonfungiblePositionManager.sol +++ b/contracts/NonfungiblePositionManager.sol @@ -16,6 +16,7 @@ import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDe import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidityManagement, ERC721 { using CurrencyLibrary for Currency; @@ -23,6 +24,7 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit using PoolIdLibrary for PoolKey; using LiquidityRangeIdLibrary for LiquidityRange; using StateLibrary for IPoolManager; + using SafeCast for uint256; /// @dev The ID of the next token that will be minted. Skips 0 uint256 private _nextId = 1; @@ -45,7 +47,7 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit address recipient, bytes calldata hookData ) public payable returns (uint256 tokenId, BalanceDelta delta) { - delta = _increaseLiquidity(range, liquidity, hookData, false, msg.sender); + delta = modifyLiquidity(range, liquidity.toInt256(), hookData, false); // mint receipt token _mint(recipient, (tokenId = _nextId++)); @@ -77,7 +79,7 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit isAuthorizedForToken(tokenId) returns (BalanceDelta delta) { - delta = _increaseLiquidity(tokenPositions[tokenId].range, liquidity, hookData, claims, msg.sender); + delta = modifyLiquidity(tokenPositions[tokenId].range, liquidity.toInt256(), hookData, claims); } function decreaseLiquidity(uint256 tokenId, uint256 liquidity, bytes calldata hookData, bool claims) @@ -85,7 +87,7 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit isAuthorizedForToken(tokenId) returns (BalanceDelta delta) { - delta = _decreaseLiquidity(tokenPositions[tokenId].range, liquidity, hookData, claims, msg.sender); + delta = modifyLiquidity(tokenPositions[tokenId].range, -(liquidity.toInt256()), hookData, claims); } function burn(uint256 tokenId, address recipient, bytes calldata hookData, bool claims) @@ -113,7 +115,7 @@ contract NonfungiblePositionManager is INonfungiblePositionManager, BaseLiquidit external returns (BalanceDelta delta) { - delta = _collect(tokenPositions[tokenId].range, hookData, claims, msg.sender); + delta = modifyLiquidity(tokenPositions[tokenId].range, 0, hookData, claims); } function feesOwed(uint256 tokenId) external view returns (uint256 token0Owed, uint256 token1Owed) { diff --git a/contracts/base/BaseLiquidityHandler.sol b/contracts/base/BaseLiquidityHandler.sol deleted file mode 100644 index 7790bffc..00000000 --- a/contracts/base/BaseLiquidityHandler.sol +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {SafeCallback} from "./SafeCallback.sol"; -import {ImmutableState} from "./ImmutableState.sol"; -import {FeeMath} from "../libraries/FeeMath.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; - -import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; -import {CurrencySenderLibrary} from "../libraries/CurrencySenderLibrary.sol"; -import {CurrencyDeltas} from "../libraries/CurrencyDeltas.sol"; -import {LiquiditySaltLibrary} from "../libraries/LiquiditySaltLibrary.sol"; - -import {LiquidityRange, LiquidityRangeId, LiquidityRangeIdLibrary} from "../types/LiquidityRange.sol"; - -abstract contract BaseLiquidityHandler is SafeCallback { - using LiquidityRangeIdLibrary for LiquidityRange; - using CurrencyLibrary for Currency; - using CurrencySettleTake for Currency; - using CurrencySenderLibrary for Currency; - using CurrencyDeltas for IPoolManager; - using StateLibrary for IPoolManager; - using LiquiditySaltLibrary for IHooks; - using PoolIdLibrary for PoolKey; - using SafeCast for uint256; - - // details about the liquidity position - struct Position { - // the nonce for permits - uint96 nonce; - // the address that is approved for spending this token - address operator; - uint256 liquidity; - // the fee growth of the aggregate position as of the last action on the individual position - uint256 feeGrowthInside0LastX128; - uint256 feeGrowthInside1LastX128; - // how many uncollected tokens are owed to the position, as of the last computation - uint128 tokensOwed0; - uint128 tokensOwed1; - } - - mapping(address owner => mapping(LiquidityRangeId rangeId => Position)) public positions; - - error LockFailure(); - - constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} - - function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { - (bool success, bytes memory returnData) = address(this).call(data); - if (success) return returnData; - if (returnData.length == 0) revert LockFailure(); - // if the call failed, bubble up the reason - /// @solidity memory-safe-assembly - assembly { - revert(add(returnData, 32), mload(returnData)) - } - } - - function handleIncreaseLiquidity( - address sender, - LiquidityRange calldata range, - uint256 liquidityToAdd, - bytes calldata hookData, - bool claims - ) external returns (BalanceDelta delta) { - Position storage position = positions[sender][range.toId()]; - - { - BalanceDelta feeDelta; - (delta, feeDelta) = poolManager.modifyLiquidity( - range.key, - IPoolManager.ModifyLiquidityParams({ - tickLower: range.tickLower, - tickUpper: range.tickUpper, - liquidityDelta: int256(liquidityToAdd), - salt: range.key.hooks.getLiquiditySalt(sender) - }), - hookData - ); - // take fees not accrued by user's position - (uint256 token0Owed, uint256 token1Owed) = _updateFeeGrowth(range, position); - BalanceDelta excessFees = feeDelta - toBalanceDelta(token0Owed.toInt128(), token1Owed.toInt128()); - range.key.currency0.take(poolManager, address(this), uint128(excessFees.amount0()), true); - range.key.currency1.take(poolManager, address(this), uint128(excessFees.amount1()), true); - } - - { - // get remaining deltas: the user pays additional to increase liquidity OR the user collects fees - delta = poolManager.currencyDeltas(address(this), range.key.currency0, range.key.currency1); - if (delta.amount0() < 0) { - range.key.currency0.settle(poolManager, sender, uint256(int256(-delta.amount0())), claims); - } - if (delta.amount1() < 0) { - range.key.currency1.settle(poolManager, sender, uint256(int256(-delta.amount1())), claims); - } - if (delta.amount0() > 0) { - range.key.currency0.take(poolManager, address(this), uint256(int256(delta.amount0())), true); - } - if (delta.amount1() > 0) { - range.key.currency1.take(poolManager, address(this), uint256(int256(delta.amount1())), true); - } - } - - { - positions[sender][range.toId()].liquidity += liquidityToAdd; - - // collected fees are credited to the position OR zero'd out - delta.amount0() > 0 ? position.tokensOwed0 += uint128(delta.amount0()) : position.tokensOwed0 = 0; - delta.amount1() > 0 ? position.tokensOwed1 += uint128(delta.amount1()) : position.tokensOwed1 = 0; - } - return delta; - } - - function handleDecreaseLiquidity( - address owner, - LiquidityRange calldata range, - uint256 liquidityToRemove, - bytes calldata hookData, - bool useClaims - ) external returns (BalanceDelta) { - (BalanceDelta delta, BalanceDelta feesAccrued) = poolManager.modifyLiquidity( - range.key, - IPoolManager.ModifyLiquidityParams({ - tickLower: range.tickLower, - tickUpper: range.tickUpper, - liquidityDelta: -int256(liquidityToRemove), - salt: range.key.hooks.getLiquiditySalt(owner) - }), - hookData - ); - - // take all tokens first - // do NOT take tokens directly to the owner because this contract might be holding fees - // that need to be paid out (position.tokensOwed) - if (delta.amount0() > 0) { - range.key.currency0.take(poolManager, address(this), uint128(delta.amount0()), true); - } - if (delta.amount1() > 0) { - range.key.currency1.take(poolManager, address(this), uint128(delta.amount1()), true); - } - - uint128 token0Owed; - uint128 token1Owed; - { - Position storage position = positions[owner][range.toId()]; - (token0Owed, token1Owed) = _updateFeeGrowth(range, position); - - BalanceDelta principalDelta = delta - feesAccrued; - token0Owed += position.tokensOwed0 + uint128(principalDelta.amount0()); - token1Owed += position.tokensOwed1 + uint128(principalDelta.amount1()); - - position.tokensOwed0 = 0; - position.tokensOwed1 = 0; - position.liquidity -= liquidityToRemove; - } - { - delta = toBalanceDelta(int128(token0Owed), int128(token1Owed)); - - // sending tokens to the owner - if (token0Owed > 0) range.key.currency0.send(poolManager, owner, token0Owed, useClaims); - if (token1Owed > 0) range.key.currency1.send(poolManager, owner, token1Owed, useClaims); - } - - return delta; - } - - function handleCollect(address owner, LiquidityRange calldata range, bytes calldata hookData, bool takeClaims) - external - returns (BalanceDelta) - { - PoolKey memory key = range.key; - Position storage position = positions[owner][range.toId()]; - - (, BalanceDelta feesAccrued) = poolManager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: range.tickLower, - tickUpper: range.tickUpper, - liquidityDelta: 0, - salt: key.hooks.getLiquiditySalt(owner) - }), - hookData - ); - - // take all fees first then distribute - if (feesAccrued.amount0() > 0) { - key.currency0.take(poolManager, address(this), uint128(feesAccrued.amount0()), true); - } - if (feesAccrued.amount1() > 0) { - key.currency1.take(poolManager, address(this), uint128(feesAccrued.amount1()), true); - } - - (uint128 token0Owed, uint128 token1Owed) = _updateFeeGrowth(range, position); - token0Owed += position.tokensOwed0; - token1Owed += position.tokensOwed1; - - if (token0Owed > 0) key.currency0.send(poolManager, owner, token0Owed, takeClaims); - if (token1Owed > 0) key.currency1.send(poolManager, owner, token1Owed, takeClaims); - - position.tokensOwed0 = 0; - position.tokensOwed1 = 0; - - return toBalanceDelta(uint256(token0Owed).toInt128(), uint256(token1Owed).toInt128()); - } - - function _updateFeeGrowth(LiquidityRange memory range, Position storage position) - internal - returns (uint128 token0Owed, uint128 token1Owed) - { - (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = - poolManager.getFeeGrowthInside(range.key.toId(), range.tickLower, range.tickUpper); - - (token0Owed, token1Owed) = FeeMath.getFeesOwed( - feeGrowthInside0X128, - feeGrowthInside1X128, - position.feeGrowthInside0LastX128, - position.feeGrowthInside1LastX128, - position.liquidity - ); - - position.feeGrowthInside0LastX128 = feeGrowthInside0X128; - position.feeGrowthInside1LastX128 = feeGrowthInside1X128; - } -} diff --git a/contracts/base/BaseLiquidityManagement.sol b/contracts/base/BaseLiquidityManagement.sol index 862b4734..1d9b71c6 100644 --- a/contracts/base/BaseLiquidityManagement.sol +++ b/contracts/base/BaseLiquidityManagement.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; @@ -12,58 +13,227 @@ import {SafeCallback} from "./SafeCallback.sol"; import {ImmutableState} from "./ImmutableState.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; +import {CurrencySenderLibrary} from "../libraries/CurrencySenderLibrary.sol"; +import {CurrencyDeltas} from "../libraries/CurrencyDeltas.sol"; + import {FeeMath} from "../libraries/FeeMath.sol"; -import {BaseLiquidityHandler} from "./BaseLiquidityHandler.sol"; +import {LiquiditySaltLibrary} from "../libraries/LiquiditySaltLibrary.sol"; -abstract contract BaseLiquidityManagement is BaseLiquidityHandler { +contract BaseLiquidityManagement is SafeCallback { using LiquidityRangeIdLibrary for LiquidityRange; using CurrencyLibrary for Currency; using CurrencySettleTake for Currency; + using CurrencySenderLibrary for Currency; + using CurrencyDeltas for IPoolManager; using PoolIdLibrary for PoolKey; using StateLibrary for IPoolManager; using TransientStateLibrary for IPoolManager; + using SafeCast for uint256; + using LiquiditySaltLibrary for IHooks; - constructor(IPoolManager _poolManager) BaseLiquidityHandler(_poolManager) {} + // details about the liquidity position + struct Position { + // the nonce for permits + uint96 nonce; + // the address that is approved for spending this token + address operator; + uint256 liquidity; + // the fee growth of the aggregate position as of the last action on the individual position + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // how many uncollected tokens are owed to the position, as of the last computation + uint128 tokensOwed0; + uint128 tokensOwed1; + } - function _increaseLiquidity( - LiquidityRange memory range, - uint256 liquidityToAdd, - bytes calldata hookData, - bool claims, - address owner - ) internal returns (BalanceDelta delta) { + mapping(address owner => mapping(LiquidityRangeId rangeId => Position)) public positions; + + error LockFailure(); + + constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} + + function modifyLiquidity(LiquidityRange memory range, int256 liquidityDelta, bytes calldata hookData, bool claims) + internal + returns (BalanceDelta delta) + { delta = abi.decode( poolManager.unlock( - abi.encodeCall(this.handleIncreaseLiquidity, (msg.sender, range, liquidityToAdd, hookData, claims)) + abi.encodeCall(this.handleModifyLiquidity, (msg.sender, range, liquidityDelta, hookData, claims)) ), (BalanceDelta) ); } - function _decreaseLiquidity( - LiquidityRange memory range, - uint256 liquidityToRemove, + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).call(data); + if (success) return returnData; + if (returnData.length == 0) revert LockFailure(); + // if the call failed, bubble up the reason + /// @solidity memory-safe-assembly + assembly { + revert(add(returnData, 32), mload(returnData)) + } + } + + function handleModifyLiquidity( + address sender, + LiquidityRange calldata range, + int256 liquidityDelta, bytes calldata hookData, - bool claims, - address owner - ) internal returns (BalanceDelta delta) { - delta = abi.decode( - poolManager.unlock( - abi.encodeCall(this.handleDecreaseLiquidity, (owner, range, liquidityToRemove, hookData, claims)) - ), - (BalanceDelta) + bool claims + ) external returns (BalanceDelta delta) { + (BalanceDelta _delta, BalanceDelta _feesAccrued) = poolManager.modifyLiquidity( + range.key, + IPoolManager.ModifyLiquidityParams({ + tickLower: range.tickLower, + tickUpper: range.tickUpper, + liquidityDelta: liquidityDelta, + salt: range.key.hooks.getLiquiditySalt(sender) + }), + hookData ); + + if (liquidityDelta > 0) { + delta = _settleIncreaseLiquidity(_delta, _feesAccrued, sender, range, uint256(liquidityDelta), claims); + } else if (liquidityDelta < 0) { + delta = _settleDecreaseLiquidity(_delta, _feesAccrued, sender, range, uint256(-liquidityDelta), claims); + } else { + delta = _settleCollect(_feesAccrued, sender, range, claims); + } + } + + function _settleIncreaseLiquidity( + BalanceDelta delta, + BalanceDelta feesAccrued, + address sender, + LiquidityRange calldata range, + uint256 liquidityToAdd, + bool claims + ) internal returns (BalanceDelta) { + Position storage position = positions[sender][range.toId()]; + + // take fees not accrued by user's position + (uint256 token0Owed, uint256 token1Owed) = _updateFeeGrowth(range, position); + BalanceDelta excessFees = feesAccrued - toBalanceDelta(token0Owed.toInt128(), token1Owed.toInt128()); + range.key.currency0.take(poolManager, address(this), uint128(excessFees.amount0()), true); + range.key.currency1.take(poolManager, address(this), uint128(excessFees.amount1()), true); + + // get remaining deltas: the user pays additional to increase liquidity OR the user collects their fees + delta = poolManager.currencyDeltas(address(this), range.key.currency0, range.key.currency1); + + // TODO: use position.tokensOwed0 to pay the delta? + if (delta.amount0() < 0) { + range.key.currency0.settle(poolManager, sender, uint256(int256(-delta.amount0())), claims); + } + if (delta.amount1() < 0) { + range.key.currency1.settle(poolManager, sender, uint256(int256(-delta.amount1())), claims); + } + if (delta.amount0() > 0) { + range.key.currency0.take(poolManager, address(this), uint256(int256(delta.amount0())), true); + } + if (delta.amount1() > 0) { + range.key.currency1.take(poolManager, address(this), uint256(int256(delta.amount1())), true); + } + + positions[sender][range.toId()].liquidity += liquidityToAdd; + + // collected fees are credited to the position OR zero'd out + delta.amount0() > 0 ? position.tokensOwed0 += uint128(delta.amount0()) : position.tokensOwed0 = 0; + delta.amount1() > 0 ? position.tokensOwed1 += uint128(delta.amount1()) : position.tokensOwed1 = 0; + + return delta; + } + + function _settleDecreaseLiquidity( + BalanceDelta delta, + BalanceDelta feesAccrued, + address owner, + LiquidityRange calldata range, + uint256 liquidityToRemove, + bool claims + ) internal returns (BalanceDelta) { + // take all tokens first + // do NOT take tokens directly to the owner because this contract might be holding fees + // that need to be paid out (position.tokensOwed) + if (delta.amount0() > 0) { + range.key.currency0.take(poolManager, address(this), uint128(delta.amount0()), true); + } + if (delta.amount1() > 0) { + range.key.currency1.take(poolManager, address(this), uint128(delta.amount1()), true); + } + + // when decreasing liquidity, the user collects: 1) principal liquidity, 2) new fees, 3) old fees (position.tokensOwed) + + Position storage position = positions[owner][range.toId()]; + (uint128 token0Owed, uint128 token1Owed) = _updateFeeGrowth(range, position); + BalanceDelta principalDelta = delta - feesAccrued; + + // new fees += old fees + principal liquidity + token0Owed += position.tokensOwed0 + uint128(principalDelta.amount0()); + token1Owed += position.tokensOwed1 + uint128(principalDelta.amount1()); + + position.tokensOwed0 = 0; + position.tokensOwed1 = 0; + position.liquidity -= liquidityToRemove; + + delta = toBalanceDelta(int128(token0Owed), int128(token1Owed)); + + // sending tokens to the owner + if (token0Owed > 0) range.key.currency0.send(poolManager, owner, token0Owed, claims); + if (token1Owed > 0) range.key.currency1.send(poolManager, owner, token1Owed, claims); + + return delta; } - function _collect(LiquidityRange memory range, bytes calldata hookData, bool claims, address owner) + function _settleCollect(BalanceDelta feesAccrued, address owner, LiquidityRange calldata range, bool takeClaims) internal - returns (BalanceDelta delta) + returns (BalanceDelta) { - delta = abi.decode( - poolManager.unlock(abi.encodeCall(this.handleCollect, (owner, range, hookData, claims))), (BalanceDelta) + PoolKey memory key = range.key; + Position storage position = positions[owner][range.toId()]; + + // take all fees first then distribute + if (feesAccrued.amount0() > 0) { + key.currency0.take(poolManager, address(this), uint128(feesAccrued.amount0()), true); + } + if (feesAccrued.amount1() > 0) { + key.currency1.take(poolManager, address(this), uint128(feesAccrued.amount1()), true); + } + + // collecting fees: new fees and old fees + (uint128 token0Owed, uint128 token1Owed) = _updateFeeGrowth(range, position); + token0Owed += position.tokensOwed0; + token1Owed += position.tokensOwed1; + + if (token0Owed > 0) key.currency0.send(poolManager, owner, token0Owed, takeClaims); + if (token1Owed > 0) key.currency1.send(poolManager, owner, token1Owed, takeClaims); + + position.tokensOwed0 = 0; + position.tokensOwed1 = 0; + + return toBalanceDelta(uint256(token0Owed).toInt128(), uint256(token1Owed).toInt128()); + } + + function _updateFeeGrowth(LiquidityRange memory range, Position storage position) + internal + returns (uint128 token0Owed, uint128 token1Owed) + { + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + poolManager.getFeeGrowthInside(range.key.toId(), range.tickLower, range.tickUpper); + + (token0Owed, token1Owed) = FeeMath.getFeesOwed( + feeGrowthInside0X128, + feeGrowthInside1X128, + position.feeGrowthInside0LastX128, + position.feeGrowthInside1LastX128, + position.liquidity ); + + position.feeGrowthInside0LastX128 = feeGrowthInside0X128; + position.feeGrowthInside1LastX128 = feeGrowthInside1X128; } // --- View Functions --- //